Ticket #804: dockbuildifx-07232011.patch

File dockbuildifx-07232011.patch, 31.1 KB (added by historic_bruno, 13 years ago)
  • binaries/data/mods/public/gui/session/input.js

     
    8080
    8181    if (placementEntity && placementPosition)
    8282    {
    83         Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
     83        return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
    8484            "template": placementEntity,
    8585            "x": placementPosition.x,
    8686            "z": placementPosition.z,
    8787            "angle": placementAngle
    8888        });
    8989    }
     90   
     91    return false;
    9092}
    9193
    9294function resetPlacementEntity()
     
    328330    var selection = g_Selection.toList();
    329331
    330332    // Use the preview to check it's a valid build location
    331     var ok = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
    332         "template": placementEntity,
    333         "x": placementPosition.x,
    334         "z": placementPosition.z,
    335         "angle": placementAngle
    336     });
    337     if (!ok)
     333    if (!updateBuildingPlacementPreview())
    338334    {
    339335        // invalid location - don't build it
    340336        // TODO: play a sound?
     
    561557                placementAngle = defaultPlacementAngle;
    562558            }
    563559
    564             Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
     560            var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
    565561                "template": placementEntity,
    566562                "x": placementPosition.x,
    567                 "z": placementPosition.z,
    568                 "angle": placementAngle
     563                "z": placementPosition.z
    569564            });
    570 
     565            if (snapData.snapped)
     566                placementAngle = snapData.angle;
     567           
     568            updateBuildingPlacementPreview();
    571569            break;
    572570
    573571        case "mousebuttonup":
     
    821819        {
    822820        case "mousemotion":
    823821            placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
    824             Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
     822            var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
    825823                "template": placementEntity,
    826824                "x": placementPosition.x,
    827                 "z": placementPosition.z,
    828                 "angle": placementAngle
     825                "z": placementPosition.z
    829826            });
     827            if (snapData.snapped)
     828                placementAngle = snapData.angle;
     829           
     830            updateBuildingPlacementPreview();
    830831
    831832            return false; // continue processing mouse motion
    832833
     
    855856            {
    856857            case "session.rotate.cw":
    857858                placementAngle += rotation_step;
    858                 Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
    859                     "template": placementEntity,
    860                     "x": placementPosition.x,
    861                     "z": placementPosition.z,
    862                     "angle": placementAngle
    863                 });
     859                updateBuildingPlacementPreview();
    864860                break;
    865861            case "session.rotate.ccw":
    866862                placementAngle -= rotation_step;
    867                 Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
    868                     "template": placementEntity,
    869                     "x": placementPosition.x,
    870                     "z": placementPosition.z,
    871                     "angle": placementAngle
    872                 });
     863                updateBuildingPlacementPreview();
    873864                break;
    874865            }
    875866
  • binaries/data/mods/public/simulation/components/BuildRestrictions.js

     
    55        "<choice>" +
    66            "<value>standard</value>" +
    77            "<value>settlement</value>" +
    8         "</choice>" + // TODO: add special types for fields, docks, walls
     8            "<value>shore</value>" +
     9        "</choice>" + // TODO: add special types for fields, walls
    910    "</element>" +
    1011    "<element name='Territory'>" +
    1112        "<choice>" +
     
    4849 * docks must be on shores), which affects the UI and the build permissions
    4950 */
    5051
     52BuildRestrictions.prototype.Init = function()
     53{
     54};
     55 
    5156BuildRestrictions.prototype.OnOwnershipChanged = function(msg)
    5257{
    5358    if (this.template.Category)
     
    6671    }
    6772};
    6873
     74BuildRestrictions.prototype.CheckPlacement = function()
     75{
     76    // First check obstructions and terrain passability
     77    var passClassName = "";
     78    switch(this.template.PlacementType)
     79    {
     80    case "shore":
     81        passClassName = "building-shore"
     82        break;
     83       
     84    case "standard":
     85    case "settlement":
     86        passClassName = "building-land"
     87        break;
     88       
     89    default:
     90        error("Unknown placement type: "+this.template.PlacementType);
     91    }
     92
     93    var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     94    if (!cmpObstruction || !cmpObstruction.IsValidFoundation(passClassName))
     95    {
     96        return false;   // Fail
     97    }
     98   
     99    // Check special requirements
     100    if (this.template.Category == "Dock")
     101    {
     102        // Dock must be oriented from land facing into water
     103        var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
     104        if (!cmpFootprint)
     105            return false;   // Fail
     106       
     107        var shape = cmpFootprint.GetShape();
     108       
     109        // Get building's footprint
     110        var halfX = 0;
     111        var halfZ = 0;
     112        if (shape.type == "square")
     113        {
     114            halfX = shape.width/2;
     115            halfZ = shape.depth/2;
     116        }
     117        else if (shape.type == "circle")
     118        {   // Approximate as square?
     119            halfX = shape.radius;
     120            halfZ = shape.radius;
     121        }
     122
     123        var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     124        var cmpWaterMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
     125        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     126       
     127        if (!cmpTerrain || !cmpPosition || !cmpWaterMgr)
     128            return false;   // Fail
     129       
     130        var pos = cmpPosition.GetPosition();
     131        var ang = cmpPosition.GetRotation().y;
     132        var s = Math.sin(ang);
     133        var c = Math.cos(ang);
     134        if ((cmpTerrain.GetGroundLevel(pos.x + s*halfX, pos.z + c*halfZ)
     135                > cmpWaterMgr.GetWaterLevel(pos.x + s*halfX, pos.z + c*halfZ)) || // front
     136            (cmpTerrain.GetGroundLevel(pos.x - s*halfX, pos.z - c*halfZ)
     137                <= cmpWaterMgr.GetWaterLevel(pos.x - s*halfX, pos.z - c*halfZ)) // back
     138        )
     139            return false;   // Fail
     140    }
     141   
     142    // Success
     143    return true;
     144};
     145
    69146BuildRestrictions.prototype.GetCategory = function()
    70147{
    71148    return this.template.Category;
  • binaries/data/mods/public/simulation/components/GarrisonHolder.js

     
    155155        // For now, just move the unit into the middle of the building where it'll probably get stuck
    156156        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    157157        pos = cmpPosition.GetPosition();
    158         warn("Can't find free space to ungarrison unit");
     158       
     159        var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     160        var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Can't find free space to ungarrison unit"};
     161        var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     162        cmpGUIInterface.PushNotification(notification);
    159163    }
    160164
    161165    var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    468468            pos.SetYRotation(cmd.angle);
    469469        }
    470470
    471         // Check whether it's obstructed by other entities
    472         var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    473         var colliding = (cmpObstruction && cmpObstruction.CheckFoundationCollisions());
     471        // Check whether it's obstructed by other entities or invalid terrain
     472        var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
     473        if (!cmpBuildRestrictions)
     474            error("cmpBuildRestrictions null");
     475        var placementValid = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement());
    474476
    475477        // Check whether it's in a visible region
    476478        var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    477         var visible = (cmpRangeManager.GetLosVisibility(ent, player) == "visible");
     479        var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player) == "visible");
    478480
    479         var ok = (!colliding && visible);
     481        var ok = (placementValid && visible);
    480482
    481483        // Set it to a red shade if this is an invalid location
    482484        var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
     
    494496    return false;
    495497};
    496498
     499GuiInterface.prototype.GetFoundationSnapData = function(player, data)
     500{
     501    var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     502    var template = cmpTemplateMgr.GetTemplate(data.template);
     503
     504    if (template.BuildRestrictions.Category == "Dock")
     505    {
     506        // Autorotate to shore
     507        var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     508        var norm = cmpTerrain.CalcNormal(data.x, data.z);
     509       
     510        if (norm.y != 1.0)
     511        {
     512            return { "snapped": true, "x": data.x, "z": data.z, "angle": Math.atan2(norm.x, norm.z) };
     513        }
     514    }
     515
     516    return {"snapped": false};
     517};
     518
    497519GuiInterface.prototype.PlaySound = function(player, data)
    498520{
    499521    // Ignore if no entity was passed
     
    583605    "SetStatusBars": 1,
    584606    "DisplayRallyPoint": 1,
    585607    "SetBuildingPlacementPreview": 1,
     608    "GetFoundationSnapData": 1,
    586609    "PlaySound": 1,
    587610    "FindIdleUnit": 1,
    588611
  • binaries/data/mods/public/simulation/components/TrainingQueue.js

     
    217217            // What should we do here?
    218218            // For now, just move the unit into the middle of the building where it'll probably get stuck
    219219            pos = cmpPosition.GetPosition();
    220             warn("Can't find free space to spawn trained unit");
     220           
     221            var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     222            var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Can't find free space to spawn trained unit"};
     223            var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     224            cmpGUIInterface.PushNotification(notification);
    221225        }
    222226
    223227        var cmpNewPosition = Engine.QueryInterface(ent, IID_Position);
  • binaries/data/mods/public/simulation/data/pathfinder.xml

     
    1414    <ship>
    1515      <MinWaterDepth>1</MinWaterDepth>
    1616    </ship>
    17     <!-- Building construction classes: -->
     17    <!-- Building construction classes:
     18    * Land is used for most buildings, which must be away
     19        from water and not on cliffs or mountains.
     20    * Shore is used for docks, which must be near water and
     21        land, yet shallow enough for builders to approach.
     22    -->
    1823    <building-land>
    1924      <MaxWaterDepth>0</MaxWaterDepth>
     25      <MinShoreDistance>2.0</MinShoreDistance>
    2026      <MaxTerrainSlope>1.0</MaxTerrainSlope>
    2127    </building-land>
     28    <building-shore>
     29      <MaxShoreDistance>4.0</MaxShoreDistance>
     30      <MaxTerrainSlope>1.0</MaxTerrainSlope>
     31    </building-shore>
    2232
    2333  </PassabilityClasses>
    2434 
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    131131
    132132        // Tentatively create the foundation (we might find later that it's a invalid build command)
    133133        var ent = Engine.AddEntity("foundation|" + cmd.template);
    134         // TODO: report errors (e.g. invalid template names)
     134        if (ent == INVALID_ENTITY)
     135        {
     136            // Error (e.g. invalid template names)
     137            error("Error creating foundation for '" + cmd.template + "'");
     138            break;
     139        }
    135140
    136141        // Move the foundation to the right place
    137142        var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    138143        cmpPosition.JumpTo(cmd.x, cmd.z);
    139144        cmpPosition.SetYRotation(cmd.angle);
    140145
    141         // Check whether it's obstructed by other entities
    142         var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
    143         if (cmpObstruction && cmpObstruction.CheckFoundationCollisions())
     146        // Check whether it's obstructed by other entities or invalid terrain
     147        var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
     148        if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement())
    144149        {
    145             // TODO: report error to player (the building site was obstructed)
    146             print("Building site was obstructed\n");
     150            var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     151            cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" });
    147152
    148153            // Remove the foundation because the construction was aborted
    149154            Engine.DestroyEntity(ent);
  • binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

     
    77    <Icon>structures/dock.png</Icon>
    88  </Identity>
    99  <BuildRestrictions>
     10    <PlacementType>shore</PlacementType>
    1011    <Category>Dock</Category>
    1112  </BuildRestrictions>
    1213  <Cost>
  • source/simulation2/components/CCmpFootprint.cpp

     
    2222
    2323#include "ICmpObstruction.h"
    2424#include "ICmpObstructionManager.h"
     25#include "ICmpPathfinder.h"
    2526#include "ICmpPosition.h"
     27#include "ICmpUnitMotion.h"
    2628#include "simulation2/MessageTypes.h"
    2729#include "maths/FixedVector2D.h"
    2830
     
    146148        }
    147149        // else use zero radius
    148150
     151        // Get passability class from UnitMotion
     152        CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), spawned);
     153        if (cmpUnitMotion.null())
     154            return error;
     155
     156        u16 spawnedPass = cmpUnitMotion->GetPassabilityClass();
     157        CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
     158        if (cmpPathfinder.null())
     159            return error;
     160
    149161        // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
    150162        entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2);
    151163
     
    168180                CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
    169181
    170182                SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
    171                 if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, spawnedRadius, NULL))
     183                if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass))
    172184                    return pos; // this position is okay, so return it
    173185            }
    174186        }
     
    177189            fixed s, c;
    178190            sincos_approx(initialAngle, s, c);
    179191
    180             for (size_t edge = 0; edge < 4; ++edge)
     192            // Expand outwards to a max of 4 tiles from foundation
     193            for (size_t dist = 0; dist <= 4; ++dist)
    181194            {
    182                 // Try equally-spaced points along the edge, starting from the middle and expanding outwards in alternating directions
    183                 const ssize_t numPoints = 9;
    184 
    185                 // Compute the direction and length of the current edge
    186                 CFixedVector2D dir;
    187                 fixed sx, sy;
    188                 switch (edge)
     195                for (size_t edge = 0; edge < 4; ++edge)
    189196                {
    190                 case 0:
    191                     dir = CFixedVector2D(c, -s);
    192                     sx = m_Size0;
    193                     sy = m_Size1;
    194                     break;
    195                 case 1:
    196                     dir = CFixedVector2D(-s, -c);
    197                     sx = m_Size1;
    198                     sy = m_Size0;
    199                     break;
    200                 case 2:
    201                     dir = CFixedVector2D(s, c);
    202                     sx = m_Size1;
    203                     sy = m_Size0;
    204                     break;
    205                 case 3:
    206                     dir = CFixedVector2D(-c, s);
    207                     sx = m_Size0;
    208                     sy = m_Size1;
    209                     break;
    210                 }
    211                 CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
    212                 dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
     197                    // Try equally-spaced points along the edge in alternating directions, starting from the middle
     198                    const ssize_t numPoints = 9 + 2*dist;
    213199
    214                 for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
    215                 {
    216                     CFixedVector2D pos (center + dir*i);
     200                    // Compute the direction and length of the current edge
     201                    CFixedVector2D dir;
     202                    fixed sx, sy;
     203                    switch (edge)
     204                    {
     205                    case 0:
     206                        dir = CFixedVector2D(c, -s);
     207                        sx = m_Size0;
     208                        sy = m_Size1;
     209                        break;
     210                    case 1:
     211                        dir = CFixedVector2D(-s, -c);
     212                        sx = m_Size1;
     213                        sy = m_Size0;
     214                        break;
     215                    case 2:
     216                        dir = CFixedVector2D(s, c);
     217                        sx = m_Size1;
     218                        sy = m_Size0;
     219                        break;
     220                    case 3:
     221                        dir = CFixedVector2D(-c, s);
     222                        sx = m_Size0;
     223                        sy = m_Size1;
     224                        break;
     225                    }
     226                    dir.Normalize(fixed::FromFloat(1.0f + 0.25f*(float)dist));
     227                    CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
     228                    dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
    217229
    218                     SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
    219                     if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, spawnedRadius, NULL))
    220                         return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
     230                    for (ssize_t i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2]
     231                    {
     232                        CFixedVector2D pos (center + dir*i);
     233
     234                        SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
     235                        if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass))
     236                            return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
     237                    }
    221238                }
    222239            }
    223240        }
  • source/simulation2/components/CCmpObstruction.cpp

     
    328328            return entity_pos_t::Zero();
    329329    }
    330330
    331     virtual bool CheckFoundationCollisions()
     331    virtual bool IsValidFoundation(std::string className)
    332332    {
    333333        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
    334334        if (cmpPosition.null())
     
    339339
    340340        CFixedVector2D pos = cmpPosition->GetPosition2D();
    341341
    342         CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    343         if (cmpObstructionManager.null())
     342        CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
     343        if (cmpPathfinder.null())
    344344            return false; // error
    345345
     346        // Get passability class
     347        u16 passClass = cmpPathfinder->GetPassabilityClass(className);
     348
    346349        // Ignore collisions with self, or with non-foundation-blocking obstructions
    347350        SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
    348351
    349352        if (m_Type == STATIC)
    350             return cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL);
     353            return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, passClass);
    351354        else
    352             return cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, NULL);
     355            return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Size0, passClass);
    353356    }
    354357
    355358    virtual std::vector<entity_id_t> GetConstructionCollisions()
  • source/simulation2/components/CCmpPathfinder.cpp

     
    374374
    375375        CTerrain& terrain = GetSimContext().GetTerrain();
    376376
     377        // Compute influence map for shore distance
     378        Grid<u16> shoreGrid(m_MapSize, m_MapSize);
    377379        for (u16 j = 0; j < m_MapSize; ++j)
    378380        {
    379381            for (u16 i = 0; i < m_MapSize; ++i)
    380382            {
    381383                fixed x, z;
    382384                TileCenter(i, j, x, z);
     385               
     386                fixed height = terrain.GetExactGroundLevelFixed(x, z);
     387                fixed water;
     388                if (!cmpWaterMan.null())
     389                    water = cmpWaterMan->GetWaterLevel(x, z);
     390                fixed depth = water - height;
    383391
     392                if (depth > fixed::Zero() && depth < fixed::FromFloat(1.f))
     393                {   // Shore (allowing for precision error)
     394                    shoreGrid.set(i, j, 0);
     395                }
     396                else
     397                {
     398                    shoreGrid.set(i, j, 32767);
     399                }
     400            }
     401        }
     402
     403        // Expand influences
     404        for (size_t y = 0; y < m_MapSize; ++y)
     405        {
     406            u16 min = 32767;
     407            for (size_t x = 0; x < m_MapSize; ++x)
     408            {
     409                u16 g = shoreGrid.get(x, y);
     410                if (g > min)
     411                    shoreGrid.set(x, y, min);
     412                else if (g < min)
     413                    min = g;
     414
     415                ++min;
     416            }
     417            for (size_t x = m_MapSize; x > 0; --x)
     418            {
     419                u16 g = shoreGrid.get(x-1, y);
     420                if (g > min)
     421                    shoreGrid.set(x-1, y, min);
     422                else if (g < min)
     423                    min = g;
     424
     425                ++min;
     426            }
     427        }
     428        for (size_t x = 0; x < m_MapSize; ++x)
     429        {
     430            u16 min = 32767;
     431            for (size_t y = 0; y < m_MapSize; ++y)
     432            {
     433                u16 g = shoreGrid.get(x, y);
     434                if (g > min)
     435                    shoreGrid.set(x, y, min);
     436                else if (g < min)
     437                    min = g;
     438
     439                ++min;
     440            }
     441            for (size_t y = m_MapSize; y > 0; --y)
     442            {
     443                u16 g = shoreGrid.get(x, y-1);
     444                if (g > min)
     445                    shoreGrid.set(x, y-1, min);
     446                else if (g < min)
     447                    min = g;
     448
     449                ++min;
     450            }
     451        }
     452
     453        for (u16 j = 0; j < m_MapSize; ++j)
     454        {
     455            for (u16 i = 0; i < m_MapSize; ++i)
     456            {
     457                fixed x, z;
     458                TileCenter(i, j, x, z);
     459
    384460                TerrainTile t = 0;
    385461
    386462                u8 obstruct = m_ObstructionGrid->get(i, j);
    387463
    388                 fixed height = terrain.GetVertexGroundLevelFixed(i, j); // TODO: should use tile centre
     464                fixed height = terrain.GetExactGroundLevelFixed(x, z);
    389465
    390466                fixed water;
    391467                if (!cmpWaterMan.null())
     
    395471
    396472                fixed slope = terrain.GetSlopeFixed(i, j);
    397473
     474                fixed shoredist = fixed::FromInt(shoreGrid.get(i, j));
     475
    398476                if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
    399477                    t |= 1;
    400478
     
    411489                {
    412490                    for (size_t n = 0; n < m_PassClasses.size(); ++n)
    413491                    {
    414                         if (!m_PassClasses[n].IsPassable(depth, slope))
     492                        if (!m_PassClasses[n].IsPassable(depth, slope, shoredist))
    415493                            t |= m_PassClasses[n].m_Mask;
    416494                    }
    417495                }
  • source/simulation2/components/CCmpPathfinder_Common.h

     
    9696            m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed();
    9797        else
    9898            m_MaxSlope = std::numeric_limits<fixed>::max();
     99
     100        if (node.GetChild("MinShoreDistance").IsOk())
     101            m_MinShore = node.GetChild("MinShoreDistance").ToFixed();
     102        else
     103            m_MinShore = std::numeric_limits<fixed>::min();
     104
     105        if (node.GetChild("MaxShoreDistance").IsOk())
     106            m_MaxShore = node.GetChild("MaxShoreDistance").ToFixed();
     107        else
     108            m_MaxShore = std::numeric_limits<fixed>::max();
     109
    99110    }
    100111
    101     bool IsPassable(fixed waterdepth, fixed steepness)
     112    bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist)
    102113    {
    103         return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope));
     114        return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope) && (m_MinShore <= shoredist && shoredist <= m_MaxShore));
    104115    }
    105116
    106117    ICmpPathfinder::pass_class_t m_Mask;
     
    108119    fixed m_MinDepth;
    109120    fixed m_MaxDepth;
    110121    fixed m_MaxSlope;
     122    fixed m_MinShore;
     123    fixed m_MaxShore;
    111124};
    112125
    113126typedef u16 TerrainTile;
     
    247260
    248261    virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass);
    249262
     263    virtual bool CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass);
     264
     265    virtual bool CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, pass_class_t passClass);
     266
    250267    virtual void FinishAsyncRequests();
    251268
    252269    void ProcessLongRequests(const std::vector<AsyncLongPathRequest>& longRequests);
  • source/simulation2/components/CCmpPathfinder_Vertex.cpp

     
    892892
    893893    return visible;
    894894}
     895
     896bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter,
     897    entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass)
     898{
     899    // Check unit obstruction
     900    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
     901    if (cmpObstructionManager.null())
     902        return false;
     903
     904    if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL))
     905        return false;
     906
     907    // Test against terrain:
     908
     909    UpdateGrid();
     910
     911    // TODO: Probably a better way to test this
     912    u16 i0, j0, i1, j1;
     913    NearestTile(x - r, z - r, i0, j0);
     914    NearestTile(x + r, z + r, i1, j1);
     915    for (u16 i = i0; i <= i1; ++i)
     916    {
     917        for (u16 j = j0; j <= j1; ++j)
     918        {
     919            if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
     920            {
     921                return false;
     922            }
     923        }
     924    }
     925    return true;
     926}
     927
     928bool CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter,
     929    entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w,
     930    entity_pos_t h, pass_class_t passClass)
     931{
     932    // Check unit obstruction
     933    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
     934    if (cmpObstructionManager.null())
     935        return false;
     936
     937    if (cmpObstructionManager->TestStaticShape(filter, x, z, a, w, h, NULL))
     938        return false;
     939
     940    // Test against terrain:
     941
     942    UpdateGrid();
     943
     944    // TODO: Probably a better way to test this
     945    u16 i0, j0, i1, j1;
     946    NearestTile(x - w/2, z - h/2, i0, j0);
     947    NearestTile(x + w/2, z + h/2, i1, j1);
     948    for (u16 i = i0; i <= i1; ++i)
     949    {
     950        for (u16 j = j0; j <= j1; ++j)
     951        {
     952            if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
     953            {
     954                return false;
     955            }
     956        }
     957    }
     958    return true;
     959}
  • source/simulation2/components/CCmpTemplateManager.cpp

     
    453453    permittedComponentTypes.insert("Footprint");
    454454    permittedComponentTypes.insert("Obstruction");
    455455    permittedComponentTypes.insert("Decay");
     456    permittedComponentTypes.insert("BuildRestrictions");
    456457
    457458    // Need these for the Actor Viewer:
    458459    permittedComponentTypes.insert("Attack");
  • source/simulation2/components/CCmpUnitMotion.cpp

     
    400400        return m_RunSpeed;
    401401    }
    402402
     403    virtual u16 GetPassabilityClass()
     404    {
     405        return m_PassClass;
     406    }
     407
    403408    virtual void SetSpeed(fixed speed)
    404409    {
    405410        m_Speed = speed;
  • source/simulation2/components/ICmpObstruction.cpp

     
    2323
    2424BEGIN_INTERFACE_WRAPPER(Obstruction)
    2525DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius)
    26 DEFINE_INTERFACE_METHOD_0("CheckFoundationCollisions", bool, ICmpObstruction, CheckFoundationCollisions)
     26DEFINE_INTERFACE_METHOD_1("IsValidFoundation", bool, ICmpObstruction, IsValidFoundation, std::string)
    2727DEFINE_INTERFACE_METHOD_0("GetConstructionCollisions", std::vector<entity_id_t>, ICmpObstruction, GetConstructionCollisions)
    2828DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)
    2929DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool)
  • source/simulation2/components/ICmpObstruction.h

     
    4444    /**
    4545     * Test whether this entity is colliding with any obstruction that are set to
    4646     * block the creation of foundations.
    47      * @return true if there is a collision
     47     * @return true if foundation is valid (not obstructed)
    4848     */
    49     virtual bool CheckFoundationCollisions() = 0;
     49    virtual bool IsValidFoundation(std::string className) = 0;
    5050
    5151    /**
    5252     * Returns a list of entities that are colliding with this entity, and that
    5353     * are set to block construction.
    54      * @return true if there is a collision
     54     * @return vector of blocking entities
    5555     */
    5656    virtual std::vector<entity_id_t> GetConstructionCollisions() = 0;
    5757
  • source/simulation2/components/ICmpPathfinder.h

     
    150150    virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass) = 0;
    151151
    152152    /**
     153     * Check whether a unit placed here is valid and doesn't hit any obstructions
     154     * or impassable terrain.
     155     * Returns true if the placement is okay.
     156     */
     157    virtual bool CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass) = 0;
     158
     159    /**
     160     * Check whether a building placed here is valid and doesn't hit any obstructions
     161     * or impassable terrain.
     162     * Returns true if the placement is okay.
     163     */
     164    virtual bool CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, pass_class_t passClass) = 0;
     165
     166    /**
    153167     * Toggle the storage and rendering of debug info.
    154168     */
    155169    virtual void SetDebugOverlay(bool enabled) = 0;
  • source/simulation2/components/ICmpUnitMotion.cpp

     
    8686        return m_Script.Call<fixed>("GetRunSpeed");
    8787    }
    8888
     89    virtual u16 GetPassabilityClass()
     90    {
     91        return m_Script.Call<u16>("GetPassabilityClass");
     92    }
     93
    8994    virtual void SetUnitRadius(fixed radius)
    9095    {
    9196        m_Script.CallVoid("SetUnitRadius", radius);
  • source/simulation2/components/ICmpUnitMotion.h

     
    9393    virtual fixed GetRunSpeed() = 0;
    9494
    9595    /**
     96     * Get the unit's passability class.
     97     */
     98    virtual u16 GetPassabilityClass() = 0;
     99
     100    /**
    96101     * Override the default obstruction radius, used for planning paths and checking for collisions.
    97102     * Bad things may happen if this entity has an active Obstruction component with a larger
    98103     * radius. (This is intended primarily for formation controllers.)