This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

Changeset 9970 for ps


Ignore:
Timestamp:
08/06/11 10:11:05 (13 years ago)
Author:
ben
Message:

Implements building restrictions (by terrain, territory, category, and distance). See #41. Fixes #804, #287.
Implements build limits. See #687.
Implements autorotation for dock placement.
Fixes unit spawning to consider terrain passability. See #893.
Adds new passability criteria based on distance from shore.
Updates build restrictions on some templates.
Changes unit spawning search to 4 tiles away from foundation.
Changes garrison/training spawn failure to nicer UI notification.

Location:
ps/trunk
Files:
28 edited

Legend:

Unmodified
Added
Removed
  • ps/trunk/binaries/data/mods/public/gui/session/input.js

    r9791 r9970  
    8181    if (placementEntity && placementPosition)
    8282    {
    83         Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
     83        return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
    8484            "template": placementEntity,
    8585            "x": placementPosition.x,
     
    8888        });
    8989    }
     90   
     91    return false;
    9092}
    9193
     
    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
     
    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
     
    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
     
    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            }
  • ps/trunk/binaries/data/mods/public/simulation/components/BuildLimits.js

    r8836 r9970  
    22
    33BuildLimits.prototype.Schema =
    4     "<a:help></a:help>" +
     4    "<a:help>Specifies per category limits on number of buildings that can be constructed for each player.</a:help>" +
     5    "<a:example>" +
     6        "<Limits>" +
     7          "<CivilCentre/>" +
     8          "<ScoutTower>20</ScoutTower>" +
     9          "<Fortress>5</Fortress>" +
     10          "<Special>" +
     11            "<LimitPerCivCentre>1</LimitPerCivCentre>" +
     12          "</Special>" +
     13        "</Limits>" +
     14    "</a:example>" +
    515    "<element name='LimitMultiplier'>" +
    616        "<ref name='positiveDecimal'/>" +
     
    818    "<element name='Limits'>" +
    919        "<zeroOrMore>" +
    10             "<element>" +
     20            "<element a:help='Specifies a category of building on which to apply this limit. See BuildRestrictions for list of categories.'>" +
    1121                "<anyName />" +
    12                 "<text />" +
     22                "<choice>" +
     23                    "<text />" +
     24                    "<element name='LimitPerCivCentre' a:help='Specifies that this limit is per number of civil centres.'>" +
     25                        "<data type='nonNegativeInteger'/>" +
     26                    "</element>" +
     27                "</choice>" +
    1328            "</element>" +
    1429        "</zeroOrMore>" +
    1530    "</element>";
    1631
     32/*
     33 *  TODO: Use an inheriting player_{civ}.xml template for civ-specific limits
     34 */
     35
    1736BuildLimits.prototype.Init = function()
    1837{
    19     this.limits = [];
    20     this.unitCount = [];
     38    this.limit = [];
     39    this.count = [];
    2140    for (var category in this.template.Limits)
    2241    {
    23         this.limits[category] = this.template.Limits[category];
    24         this.unitCount[category] = 0;
     42        this.limit[category] = this.template.Limits[category];
     43        this.count[category] = 0;
    2544    }
    2645};
     
    2847BuildLimits.prototype.IncrementCount = function(category)
    2948{
    30     if (this.unitCount[category] !== undefined)
    31         this.unitCount[category]++;
     49    if (this.count[category] !== undefined)
     50    {
     51        this.count[category]++;
     52    }
    3253};
    3354
    3455BuildLimits.prototype.DecrementCount = function(category)
    3556{
    36     if (this.unitCount[category] !== undefined)
    37         this.unitCount[category]--;
     57    if (this.count[category] !== undefined)
     58    {
     59        this.count[category]--;
     60    }
    3861};
    3962
    4063BuildLimits.prototype.AllowedToBuild = function(category)
    4164{
    42     if (this.unitCount[category])
     65    // TODO: The UI should reflect this before the user tries to place the building,
     66    //          since the limits are independent of placement location
     67
     68    // Allow unspecified categories and those with no limit
     69    if (this.count[category] === undefined || this.limit[category] === undefined)
    4370    {
    44         if (this.unitCount[category] >= this.limits[category])
     71        return true;
     72    }
     73   
     74    // Rather than complicating the schema unecessarily, just handle special cases here
     75    if (this.limit[category].LimitPerCivCentre !== undefined)
     76    {
     77        if (this.count[category] >= this.count["CivilCentre"] * this.limit[category].LimitPerCivCentre)
    4578        {
    4679            var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
    4780            var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
    48             var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Build limit reached for this building"};
     81            var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category].LimitPerCivCentre+" per civil centre reached"};
    4982            var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
    5083            cmpGUIInterface.PushNotification(notification);
     84           
    5185            return false;
    5286        }
    53         else
    54             return true;
    5587    }
    56     else
     88    else if (this.count[category] >= this.limit[category])
    5789    {
    58         //Check here for terrain
    59         return true;
     90        var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     91        var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
     92        var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category]+ " reached"};
     93        var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     94        cmpGUIInterface.PushNotification(notification);
     95       
     96        return false;
    6097    }
     98   
     99    return true;
    61100};
    62101
  • ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js

    r8836 r9970  
    22
    33BuildRestrictions.prototype.Schema =
    4     "<element name='PlacementType'>" +
     4    "<a:help>Specifies building placement restrictions as they relate to terrain, territories, and distance.</a:help>" +
     5    "<a:example>" +
     6        "<BuildRestrictions>" +
     7            "<PlacementType>land</PlacementType>" +
     8            "<Territory>own</Territory>" +
     9            "<Category>Special</Category>" +
     10            "<Distance>" +
     11                "<FromCategory>CivilCentre</FromCategory>" +
     12                "<MaxDistance>40</MaxDistance>" +
     13            "</Distance>" +
     14        "</BuildRestrictions>" +
     15    "</a:example>" +
     16    "<element name='PlacementType' a:help='Specifies the terrain type restriction for this building.'>" +
    517        "<choice>" +
    6             "<value>standard</value>" +
    7             "<value>settlement</value>" +
    8         "</choice>" + // TODO: add special types for fields, docks, walls
    9     "</element>" +
    10     "<element name='Territory'>" +
    11         "<choice>" +
    12             "<value>all</value>" +
    13             "<value>allied</value>" +
     18            "<value>land</value>" +
     19            "<value>shore</value>" +
    1420        "</choice>" +
    1521    "</element>" +
    16     "<element name='Category'>" +
     22    "<element name='Territory' a:help='Specifies territory type restrictions for this building.'>" +
     23        "<list>" +
     24            "<oneOrMore>" +
     25                "<choice>" +
     26                    "<value>own</value>" +
     27                    "<value>ally</value>" +
     28                    "<value>neutral</value>" +
     29                    "<value>enemy</value>" +
     30                "</choice>" +
     31            "</oneOrMore>" +
     32        "</list>" +
     33    "</element>" +
     34    "<element name='Category' a:help='Specifies the category of this building, for satisfying special constraints.'>" +
    1735        "<choice>" +
    1836            "<value>CivilCentre</value>" +
     
    3351            "<value>Special</value>" +
    3452        "</choice>" +
    35     "</element>";
     53    "</element>" +
     54    "<optional>" +
     55        "<element name='Distance' a:help='Specifies distance restrictions on this building, relative to buildings from the given category.'>" +
     56            "<interleave>" +
     57                "<element name='FromCategory'>" +
     58                    "<choice>" +
     59                        "<value>CivilCentre</value>" +
     60                    "</choice>" +
     61                "</element>" +
     62                "<optional><element name='MinDistance'><data type='positiveInteger'/></element></optional>" +
     63                "<optional><element name='MaxDistance'><data type='positiveInteger'/></element></optional>" +
     64            "</interleave>" +
     65        "</element>" +
     66    "</optional>";
    3667    // TODO: add phases, prerequisites, etc
    3768
    38 /*
    39  * TODO: the vague plan for Category is to add some BuildLimitManager which
    40  * specifies the limit per category, and which can determine whether you're
    41  * allowed to build more (based on the number in total / per territory / per
    42  * civ center as appropriate)
    43  */
    44 
    45 /*
    46  * TODO: the vague plan for PlacementType is that it may restrict the locations
    47  * and orientations of new construction work (e.g. civ centers must be on settlements,
    48  * docks must be on shores), which affects the UI and the build permissions
    49  */
     69BuildRestrictions.prototype.Init = function()
     70{
     71    this.territories = this.template.Territory.split(/\s+/);
     72};
    5073
    5174BuildRestrictions.prototype.OnOwnershipChanged = function(msg)
    5275{
     76    // This automatically updates building counts
    5377    if (this.template.Category)
    5478    {
    55         var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
    5679        if (msg.from != -1)
    5780        {
    58             var fromPlayerBuildLimits = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(msg.from), IID_BuildLimits);
     81            var fromPlayerBuildLimits = QueryPlayerIDInterface(msg.from, IID_BuildLimits);
    5982            fromPlayerBuildLimits.DecrementCount(this.template.Category);
    6083        }
    6184        if (msg.to != -1)
    6285        {
    63             var toPlayerBuildLimits = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(msg.to), IID_BuildLimits);
     86            var toPlayerBuildLimits = QueryPlayerIDInterface(msg.to, IID_BuildLimits);
    6487            toPlayerBuildLimits.IncrementCount(this.template.Category);
    6588        }
     
    6790};
    6891
     92BuildRestrictions.prototype.CheckPlacement = function(player)
     93{
     94    // TODO: Return error code for invalid placement, which can be handled by the UI
     95
     96    // Check obstructions and terrain passability
     97    var passClassName = "";
     98    switch (this.template.PlacementType)
     99    {
     100    case "shore":
     101        passClassName = "building-shore";
     102        break;
     103       
     104    case "land":
     105    default:
     106        passClassName = "building-land";
     107    }
     108
     109    var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     110    if (!cmpObstruction || !cmpObstruction.CheckFoundation(passClassName))
     111    {
     112        return false;   // Fail
     113    }
     114   
     115    // Check territory restrictions
     116    var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
     117    var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
     118    var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     119    if (!(cmpTerritoryManager && cmpPlayer && cmpPosition && cmpPosition.IsInWorld()))
     120    {
     121        return false;   // Fail
     122    }
     123   
     124    var pos = cmpPosition.GetPosition2D();
     125    var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
     126    var isOwn = (tileOwner == player);
     127    var isNeutral = (tileOwner == 0);
     128    var isAlly = !isOwn && cmpPlayer.IsAlly(tileOwner);
     129    var isEnemy = !isNeutral && cmpPlayer.IsEnemy(tileOwner);
     130   
     131    if ((isAlly && !this.HasTerritory("ally"))
     132        || (isOwn && !this.HasTerritory("own"))
     133        || (isNeutral && !this.HasTerritory("neutral"))
     134        || (isEnemy && !this.HasTerritory("enemy")))
     135    {
     136        return false;   // Fail
     137    }
     138   
     139    // Check special requirements
     140    if (this.template.Category == "Dock")
     141    {
     142        // Dock must be oriented from land facing into water
     143        var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
     144        if (!cmpFootprint)
     145        {
     146            return false;   // Fail
     147        }
     148       
     149        // Get building's footprint
     150        var shape = cmpFootprint.GetShape();
     151        var halfSize = 0;
     152        if (shape.type == "square")
     153        {
     154            halfSize = shape.depth/2;
     155        }
     156        else if (shape.type == "circle")
     157        {
     158            halfSize = shape.radius;
     159        }
     160
     161        var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     162        var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
     163        if (!cmpTerrain || !cmpWaterManager)
     164        {
     165            return false;   // Fail
     166        }
     167       
     168        var ang = cmpPosition.GetRotation().y;
     169        var sz = halfSize * Math.sin(ang);
     170        var cz = halfSize * Math.cos(ang);
     171        if ((cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz) > cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz)) || // front
     172            (cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz) <= cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz)))   // back
     173        {
     174            return false;   // Fail
     175        }
     176    }
     177   
     178    // Check distance restriction
     179    if (this.template.Distance)
     180    {
     181        var minDist = 65535;
     182        var maxDist = 0;
     183        var ents = Engine.GetEntitiesWithInterface(IID_BuildRestrictions);
     184        for each (var ent in ents)
     185        {
     186            var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
     187            if (cmpBuildRestrictions.GetCategory() == this.template.Distance.FromCategory && IsOwnedByPlayer(player, ent))
     188            {
     189                var cmpEntPosition = Engine.QueryInterface(ent, IID_Position);
     190                if (cmpEntPosition && cmpEntPosition.IsInWorld())
     191                {
     192                    var entPos = cmpEntPosition.GetPosition2D();
     193                    var dist = Math.sqrt((pos.x-entPos.x)*(pos.x-entPos.x) + (pos.y-entPos.y)*(pos.y-entPos.y));
     194                    if (dist < minDist)
     195                    {
     196                        minDist = dist;
     197                    }
     198                    if (dist > maxDist)
     199                    {
     200                        maxDist = dist;
     201                    }
     202                }
     203            }
     204        }
     205       
     206        if (this.template.Distance.MinDistance !== undefined && minDist < this.template.Distance.MinDistance
     207            || this.template.Distance.MaxDistance !== undefined && maxDist > this.template.Distance.MaxDistance)
     208        {
     209            return false;   // Fail
     210        }
     211    }
     212   
     213    // Success
     214    return true;
     215};
     216
    69217BuildRestrictions.prototype.GetCategory = function()
    70218{
     
    72220};
    73221
     222BuildRestrictions.prototype.GetTerritories = function()
     223{
     224    return this.territories;
     225};
     226
     227BuildRestrictions.prototype.HasTerritory = function(territory)
     228{
     229    return (this.territories.indexOf(territory) != -1);
     230};
     231
    74232Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions);
  • ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js

    r9499 r9970  
    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
  • ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js

    r9791 r9970  
    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());
    474 
    475471        // Check whether it's in a visible region
    476472        var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    477         var visible = (cmpRangeManager.GetLosVisibility(ent, player) == "visible");
    478 
    479         var ok = (!colliding && visible);
    480 
     473        var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player) == "visible");
     474        var validPlacement = false;
     475       
     476        if (visible)
     477        {   // Check whether it's obstructed by other entities or invalid terrain
     478            var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
     479            if (!cmpBuildRestrictions)
     480                error("cmpBuildRestrictions not defined");
     481           
     482            validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement(player));
     483        }
     484
     485        var ok = (visible && validPlacement);
     486       
    481487        // Set it to a red shade if this is an invalid location
    482488        var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
     
    493499
    494500    return false;
     501};
     502
     503GuiInterface.prototype.GetFoundationSnapData = function(player, data)
     504{
     505    var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     506    var template = cmpTemplateMgr.GetTemplate(data.template);
     507
     508    if (template.BuildRestrictions.Category == "Dock")
     509    {
     510        var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     511        var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
     512       
     513        // Find direction of most open water, algorithm:
     514        //  1. Pick points in a circle around dock
     515        //  2. If point is in water, add to array
     516        //  3. Scan array looking for consective points
     517        //  4. Find longest sequence of conseuctive points
     518        //  5. Calculate angle using average of sequence
     519        const numPoints = 16;
     520        const dist = 20.0;
     521        var waterPoints = [];
     522        for (var i = 0; i < numPoints; ++i)
     523        {
     524            var angle = (i/numPoints)*2*Math.PI;
     525            var nx = data.x - dist*Math.sin(angle);
     526            var nz = data.z + dist*Math.cos(angle);
     527           
     528            if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
     529            {
     530                waterPoints.push(i);
     531            }
     532        }
     533        var consec = [];
     534        var length = waterPoints.length;
     535        for (var i = 0; i < length; ++i)
     536        {
     537            var count = 0;
     538            for (var j = 0; j < (length-1); ++j)
     539            {
     540                if (((waterPoints[(i + j) % length]+1) % numPoints) == waterPoints[(i + j + 1) % length])
     541                {
     542                    ++count;
     543                }
     544                else
     545                {
     546                    break;
     547                }
     548            }
     549            consec[i] = count;
     550        }
     551        var start = 0;
     552        var count = 0;
     553        for (var c in consec)
     554        {
     555            if (consec[c] > count)
     556            {
     557                start = c;
     558                count = consec[c];
     559            }
     560        }
     561
     562        return { "snapped": true, "x": data.x, "z": data.z, "angle": -(((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI) };
     563    }
     564
     565    return {"snapped": false};
    495566};
    496567
     
    584655    "DisplayRallyPoint": 1,
    585656    "SetBuildingPlacementPreview": 1,
     657    "GetFoundationSnapData": 1,
    586658    "PlaySound": 1,
    587659    "FindIdleUnit": 1,
  • ps/trunk/binaries/data/mods/public/simulation/components/TrainingQueue.js

    r8899 r9970  
    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
  • ps/trunk/binaries/data/mods/public/simulation/data/pathfinder.xml

    r9929 r9970  
    1616      <MinWaterDepth>1</MinWaterDepth>
    1717    </ship>
    18     <!-- Building construction classes: -->
     18    <!-- Building construction classes:
     19    * Land is used for most buildings, which must be away
     20        from water and not on cliffs or mountains.
     21    * Shore is used for docks, which must be near water and
     22        land, yet shallow enough for builders to approach.
     23    -->
    1924    <building-land>
    2025      <MaxWaterDepth>0</MaxWaterDepth>
     26      <MinShoreDistance>2.0</MinShoreDistance>
    2127      <MaxTerrainSlope>1.0</MaxTerrainSlope>
    2228    </building-land>
     29    <building-shore>
     30      <MaxShoreDistance>3.0</MaxShoreDistance>
     31      <MaxTerrainSlope>1.5</MaxTerrainSlope>
     32    </building-shore>
    2333
    2434  </PassabilityClasses>
  • ps/trunk/binaries/data/mods/public/simulation/helpers/Commands.js

    r9776 r9970  
    7575    case "returnresource":
    7676        // Check dropsite is owned by player
    77         if (IsOwnedByPlayer(cmd.target, player))
     77        if (IsOwnedByPlayer(player, cmd.target))
    7878        {
    7979            var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
     
    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
     
    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())
    144         {
    145             // TODO: report error to player (the building site was obstructed)
    146             print("Building site was obstructed\n");
     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(player))
     149        {
     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);
    150 
     155            break;
     156        }
     157       
     158        // Check build limits
     159        var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits);
     160        if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
     161        {
     162            // TODO: The UI should tell the user they can't build this (but we still need this check)
     163           
     164            // Remove the foundation because the construction was aborted
     165            Engine.DestroyEntity(ent);
    151166            break;
    152167        }
     
    168183        }
    169184        */
    170 
    171         var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
    172         var cmpBuildLimits = Engine.QueryInterface(playerEnt, IID_BuildLimits);
    173         if (!cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
    174         {
    175             Engine.DestroyEntity(ent);
    176             break;
    177         }
    178185       
    179186        var cmpCost = Engine.QueryInterface(ent, IID_Cost);
    180187        if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
    181188        {
    182             // TODO: report error to player (they ran out of resources)
    183189            Engine.DestroyEntity(ent);
    184190            break;
     
    566572
    567573/**
    568  * Check if entity is owned by player
    569  */
    570 function IsOwnedByPlayer(entity, player)
    571 {
    572     var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
    573     return (cmpOwnership && cmpOwnership.GetOwner() == player);
    574 }
    575 
    576 /**
    577574 * Check if player can control this entity
    578575 * returns: true if the entity is valid and owned by the player if
     
    581578function CanControlUnit(entity, player, controlAll)
    582579{
    583     return (IsOwnedByPlayer(entity, player) || controlAll);
     580    return (IsOwnedByPlayer(player, entity) || controlAll);
    584581}
    585582
  • ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js

    r9726 r9970  
    232232
    233233/**
     234 * Returns true if the entity 'target' is owned by player
     235 */
     236function IsOwnedByPlayer(player, target)
     237{
     238    var cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership);
     239    return (cmpOwnershipTarget &&  player == cmpOwnershipTarget.GetOwner());
     240}
     241
     242/**
    234243 * Returns true if the entity 'target' is owned by an ally of player
    235244 */
     
    279288Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface);
    280289Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity);
     290Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer);
    281291Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer);
    282292Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer);
  • ps/trunk/binaries/data/mods/public/simulation/templates/special/player.xml

    r9936 r9970  
    44    <LimitMultiplier>1.0</LimitMultiplier>
    55    <Limits>
     6      <CivilCentre/>
     7      <ScoutTower>20</ScoutTower>
     8      <Fortress>5</Fortress>
    69    </Limits>
    710  </BuildLimits>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml

    r9936 r9970  
    1111  </BuildingAI>
    1212  <BuildRestrictions>
    13     <PlacementType>standard</PlacementType>
    14     <Territory>allied</Territory>
     13    <PlacementType>land</PlacementType>
     14    <Territory>own</Territory>
    1515  </BuildRestrictions>
    1616  <Cost>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml

    r9963 r9970  
    77  </Armour>
    88  <BuildRestrictions>
    9     <PlacementType>settlement</PlacementType>
    10     <Territory>all</Territory>
     9    <Territory>own neutral</Territory>
    1110    <Category>CivilCentre</Category>
     11    <Distance>
     12      <FromCategory>CivilCentre</FromCategory>
     13      <MinDistance>150</MinDistance>
     14    </Distance>
    1215  </BuildRestrictions>
    1316  <Cost>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_economic_mill.xml

    r9956 r9970  
    88  <BuildRestrictions>
    99    <Category>Mill</Category>
    10     <Territory>all</Territory>
    1110  </BuildRestrictions>
    1211  <Cost>
  • ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

    r9956 r9970  
    77  </Armour>
    88  <BuildRestrictions>
     9    <Territory>own ally neutral</Territory>
     10    <PlacementType>shore</PlacementType>
    911    <Category>Dock</Category>
    1012  </BuildRestrictions>
  • ps/trunk/source/simulation2/components/CCmpFootprint.cpp

    r8899 r9970  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
     
    2121#include "ICmpFootprint.h"
    2222
    23 #include "ICmpObstruction.h"
    24 #include "ICmpObstructionManager.h"
    25 #include "ICmpPosition.h"
     23#include "simulation2/components/ICmpObstruction.h"
     24#include "simulation2/components/ICmpObstructionManager.h"
     25#include "simulation2/components/ICmpPathfinder.h"
     26#include "simulation2/components/ICmpPosition.h"
     27#include "simulation2/components/ICmpUnitMotion.h"
    2628#include "simulation2/MessageTypes.h"
     29#include "graphics/Terrain.h"   // For CELL_SIZE
    2730#include "maths/FixedVector2D.h"
    2831
     
    126129        // shape.)
    127130
    128         CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
     131        const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
    129132
    130133        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
     
    147150        // else use zero radius
    148151
    149         // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
    150         entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2);
    151 
     152        // Get passability class from UnitMotion
     153        CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), spawned);
     154        if (cmpUnitMotion.null())
     155            return error;
     156
     157        ICmpPathfinder::pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass();
     158        CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
     159        if (cmpPathfinder.null())
     160            return error;
     161       
    152162        CFixedVector2D initialPos = cmpPosition->GetPosition2D();
    153163        entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
    154164
     165        // Max spawning distance in tiles
     166        const size_t maxSpawningDistance = 4;
     167
    155168        if (m_Shape == CIRCLE)
    156169        {
    157             entity_pos_t radius = m_Size0 + clearance;
    158 
    159             // Try equally-spaced points around the circle, starting from the front and expanding outwards in alternating directions
    160             const ssize_t numPoints = 31;
    161             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]
     170            // Expand outwards from foundation
     171            for (size_t dist = 0; dist <= maxSpawningDistance; ++dist)
    162172            {
    163                 entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
    164 
    165                 fixed s, c;
    166                 sincos_approx(angle, s, c);
    167 
    168                 CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
    169 
    170                 SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
    171                 if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, spawnedRadius, NULL))
    172                     return pos; // this position is okay, so return it
     173                // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
     174                entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + CELL_SIZE*dist);
     175                entity_pos_t radius = m_Size0 + clearance;
     176
     177                // Try equally-spaced points around the circle in alternating directions, starting from the front
     178                const ssize_t numPoints = 31 + 2*dist;
     179                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]
     180                {
     181                    entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
     182
     183                    fixed s, c;
     184                    sincos_approx(angle, s, c);
     185
     186                    CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
     187
     188                    SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
     189                    if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass))
     190                        return pos; // this position is okay, so return it
     191                }
    173192            }
    174193        }
     
    178197            sincos_approx(initialAngle, s, c);
    179198
    180             for (size_t edge = 0; edge < 4; ++edge)
     199            // Expand outwards from foundation
     200            for (size_t dist = 0; dist <= maxSpawningDistance; ++dist)
    181201            {
    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)
     202                // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
     203                entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + CELL_SIZE*dist);
     204
     205                for (size_t edge = 0; edge < 4; ++edge)
    189206                {
    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));
    213 
    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);
    217 
    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
     207                    // Try equally-spaced points along the edge in alternating directions, starting from the middle
     208                    const ssize_t numPoints = 9 + 2*dist;
     209
     210                    // Compute the direction and length of the current edge
     211                    CFixedVector2D dir;
     212                    fixed sx, sy;
     213                    switch (edge)
     214                    {
     215                    case 0:
     216                        dir = CFixedVector2D(c, -s);
     217                        sx = m_Size0;
     218                        sy = m_Size1;
     219                        break;
     220                    case 1:
     221                        dir = CFixedVector2D(-s, -c);
     222                        sx = m_Size1;
     223                        sy = m_Size0;
     224                        break;
     225                    case 2:
     226                        dir = CFixedVector2D(s, c);
     227                        sx = m_Size1;
     228                        sy = m_Size0;
     229                        break;
     230                    case 3:
     231                        dir = CFixedVector2D(-c, s);
     232                        sx = m_Size0;
     233                        sy = m_Size1;
     234                        break;
     235                    }
     236                    CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
     237                    dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
     238
     239                    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]
     240                    {
     241                        CFixedVector2D pos (center + dir*i);
     242
     243                        SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
     244                        if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass))
     245                            return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
     246                    }
    221247                }
    222248            }
  • ps/trunk/source/simulation2/components/CCmpObstruction.cpp

    r9510 r9970  
    2121#include "ICmpObstruction.h"
    2222
    23 #include "ICmpObstructionManager.h"
    24 #include "ICmpPosition.h"
     23#include "simulation2/components/ICmpObstructionManager.h"
     24#include "simulation2/components/ICmpPosition.h"
    2525
    2626#include "simulation2/MessageTypes.h"
     
    329329    }
    330330
    331     virtual bool CheckFoundationCollisions()
     331    virtual bool CheckFoundation(std::string className)
    332332    {
    333333        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
     
    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
     345
     346        // Get passability class
     347        ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className);
    345348
    346349        // Ignore collisions with self, or with non-foundation-blocking obstructions
     
    348351
    349352        if (m_Type == STATIC)
    350             return cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL);
    351         else
    352             return cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, NULL);
     353            return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
     354        else
     355            return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Size0, passClass);
    353356    }
    354357
  • ps/trunk/source/simulation2/components/CCmpPathfinder.cpp

    r9666 r9970  
    3030#include "renderer/Scene.h"
    3131#include "simulation2/MessageTypes.h"
     32#include "simulation2/components/ICmpObstruction.h"
    3233#include "simulation2/components/ICmpObstructionManager.h"
    3334#include "simulation2/components/ICmpWaterManager.h"
     
    373374        CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
    374375
     376        // TOOD: these bits should come from ICmpTerrain
    375377        CTerrain& terrain = GetSimContext().GetTerrain();
    376378
     379        // avoid integer overflow in intermediate calculation
     380        const u16 shoreMax = 32767;
     381       
     382        // First pass - find underwater tiles
     383        Grid<bool> waterGrid(m_MapSize, m_MapSize);
    377384        for (u16 j = 0; j < m_MapSize; ++j)
    378385        {
     
    381388                fixed x, z;
    382389                TileCenter(i, j, x, z);
     390               
     391                bool underWater = !cmpWaterMan.null() && (cmpWaterMan->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z));
     392                waterGrid.set(i, j, underWater);
     393            }
     394        }
     395        // Second pass - find shore tiles
     396        Grid<u16> shoreGrid(m_MapSize, m_MapSize);
     397        for (u16 j = 0; j < m_MapSize; ++j)
     398        {
     399            for (u16 i = 0; i < m_MapSize; ++i)
     400            {
     401                // Find a land tile
     402                if (!waterGrid.get(i, j) && (
     403                    (i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1)) ||
     404                    (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1)) ||
     405                    (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1))
     406                    ))
     407                {
     408                    shoreGrid.set(i, j, 0);
     409                }
     410                else
     411                {
     412                    shoreGrid.set(i, j, shoreMax);
     413                }
     414            }
     415        }
     416
     417        // Expand influences to find shore distance
     418        for (size_t y = 0; y < m_MapSize; ++y)
     419        {
     420            u16 min = shoreMax;
     421            for (size_t x = 0; x < m_MapSize; ++x)
     422            {
     423                u16 g = shoreGrid.get(x, y);
     424                if (g > min)
     425                    shoreGrid.set(x, y, min);
     426                else if (g < min)
     427                    min = g;
     428
     429                ++min;
     430            }
     431            for (size_t x = m_MapSize; x > 0; --x)
     432            {
     433                u16 g = shoreGrid.get(x-1, y);
     434                if (g > min)
     435                    shoreGrid.set(x-1, y, min);
     436                else if (g < min)
     437                    min = g;
     438
     439                ++min;
     440            }
     441        }
     442        for (size_t x = 0; x < m_MapSize; ++x)
     443        {
     444            u16 min = shoreMax;
     445            for (size_t y = 0; y < m_MapSize; ++y)
     446            {
     447                u16 g = shoreGrid.get(x, y);
     448                if (g > min)
     449                    shoreGrid.set(x, y, min);
     450                else if (g < min)
     451                    min = g;
     452
     453                ++min;
     454            }
     455            for (size_t y = m_MapSize; y > 0; --y)
     456            {
     457                u16 g = shoreGrid.get(x, y-1);
     458                if (g > min)
     459                    shoreGrid.set(x, y-1, min);
     460                else if (g < min)
     461                    min = g;
     462
     463                ++min;
     464            }
     465        }
     466
     467        // Apply passability classes to terrain
     468        for (u16 j = 0; j < m_MapSize; ++j)
     469        {
     470            for (u16 i = 0; i < m_MapSize; ++i)
     471            {
     472                fixed x, z;
     473                TileCenter(i, j, x, z);
    383474
    384475                TerrainTile t = 0;
     
    386477                u8 obstruct = m_ObstructionGrid->get(i, j);
    387478
    388                 fixed height = terrain.GetVertexGroundLevelFixed(i, j); // TODO: should use tile centre
     479                fixed height = terrain.GetExactGroundLevelFixed(x, z);
    389480
    390481                fixed water;
     
    395486
    396487                fixed slope = terrain.GetSlopeFixed(i, j);
     488
     489                fixed shoredist = fixed::FromInt(shoreGrid.get(i, j));
    397490
    398491                if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
     
    412505                    for (size_t n = 0; n < m_PassClasses.size(); ++n)
    413506                    {
    414                         if (!m_PassClasses[n].IsPassable(depth, slope))
     507                        if (!m_PassClasses[n].IsPassable(depth, slope, shoredist))
    415508                            t |= m_PassClasses[n].m_Mask;
    416509                    }
     
    551644}
    552645
     646bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter,
     647    entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass)
     648{
     649    // Check unit obstruction
     650    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
     651    if (cmpObstructionManager.null())
     652        return false;
     653
     654    if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL))
     655        return false;
     656
     657    // Test against terrain:
     658
     659    UpdateGrid();
     660
     661    u16 i0, j0, i1, j1;
     662    NearestTile(x - r, z - r, i0, j0);
     663    NearestTile(x + r, z + r, i1, j1);
     664    for (u16 j = j0; j <= j1; ++j)
     665    {
     666        for (u16 i = i0; i <= i1; ++i)
     667        {
     668            if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
     669            {
     670                return false;
     671            }
     672        }
     673    }
     674    return true;
     675}
     676
     677bool CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter,
     678    entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w,
     679    entity_pos_t h, entity_id_t id, pass_class_t passClass)
     680{
     681    // Check unit obstruction
     682    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
     683    if (cmpObstructionManager.null())
     684        return false;
     685
     686    if (cmpObstructionManager->TestStaticShape(filter, x, z, a, w, h, NULL))
     687        return false;
     688
     689    // Test against terrain:
     690
     691    UpdateGrid();
     692
     693    CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
     694    if (cmpObstruction.null())
     695        return false;
     696
     697    ICmpObstructionManager::ObstructionSquare square;
     698    if (!cmpObstruction->GetObstructionSquare(square))
     699        return false;
     700
     701    CFixedVector2D halfSize(square.hw, square.hh);
     702    halfSize = halfSize * 1.41421f;
     703    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);
     704
     705    u16 i0, j0, i1, j1;
     706    NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0);
     707    NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1);
     708    for (u16 j = j0; j <= j1; ++j)
     709    {
     710        for (u16 i = i0; i <= i1; ++i)
     711        {
     712            entity_pos_t x, z;
     713            TileCenter(i, j, x, z);
     714            if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize)
     715                && !IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
     716            {
     717                return false;
     718            }
     719        }
     720    }
     721
     722    return true;
     723}
  • ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h

    r9666 r9970  
    9797        else
    9898            m_MaxSlope = std::numeric_limits<fixed>::max();
    99     }
    100 
    101     bool IsPassable(fixed waterdepth, fixed steepness)
    102     {
    103         return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope));
     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
     110    }
     111
     112    bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist)
     113    {
     114        return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope) && (m_MinShore <= shoredist && shoredist <= m_MaxShore));
    104115    }
    105116
     
    109120    fixed m_MaxDepth;
    110121    fixed m_MaxSlope;
     122    fixed m_MinShore;
     123    fixed m_MaxShore;
    111124};
    112125
     
    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, entity_id_t id, pass_class_t passClass);
     266
    250267    virtual void FinishAsyncRequests();
    251268
  • ps/trunk/source/simulation2/components/CCmpTemplateManager.cpp

    r9550 r9970  
    454454    permittedComponentTypes.insert("Obstruction");
    455455    permittedComponentTypes.insert("Decay");
     456    permittedComponentTypes.insert("BuildRestrictions");
    456457
    457458    // Need these for the Actor Viewer:
  • ps/trunk/source/simulation2/components/CCmpTerritoryManager.cpp

    r9960 r9970  
    167167        return *m_Territories;
    168168    }
     169
     170    virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z);
    169171
    170172    // To support lazy updates of territory render data,
     
    684686}
    685687
     688int32_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
     689{
     690    u16 i, j;
     691    CalculateTerritories();
     692    NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     693    return m_Territories->get(i, j);
     694}
    686695
    687696void TerritoryOverlay::StartRender()
  • ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp

    r9713 r9970  
    2121#include "ICmpUnitMotion.h"
    2222
    23 #include "ICmpObstruction.h"
    24 #include "ICmpObstructionManager.h"
    25 #include "ICmpOwnership.h"
    26 #include "ICmpPosition.h"
    27 #include "ICmpPathfinder.h"
    28 #include "ICmpRangeManager.h"
    29 #include "simulation2/MessageTypes.h"
     23#include "simulation2/components/ICmpObstruction.h"
     24#include "simulation2/components/ICmpObstructionManager.h"
     25#include "simulation2/components/ICmpOwnership.h"
     26#include "simulation2/components/ICmpPosition.h"
     27#include "simulation2/components/ICmpPathfinder.h"
     28#include "simulation2/components/ICmpRangeManager.h"
    3029#include "simulation2/helpers/Geometry.h"
    3130#include "simulation2/helpers/Render.h"
     31#include "simulation2/MessageTypes.h"
    3232#include "simulation2/serialization/SerializeTemplates.h"
    3333
     
    123123    fixed m_WalkSpeed; // in metres per second
    124124    fixed m_RunSpeed;
    125     u8 m_PassClass;
    126     u8 m_CostClass;
     125    ICmpPathfinder::pass_class_t m_PassClass;
     126    ICmpPathfinder::cost_class_t m_CostClass;
    127127
    128128    // Dynamic state:
     
    401401    }
    402402
     403    virtual ICmpPathfinder::pass_class_t GetPassabilityClass()
     404    {
     405        return m_PassClass;
     406    }
     407
    403408    virtual void SetSpeed(fixed speed)
    404409    {
  • ps/trunk/source/simulation2/components/ICmpObstruction.cpp

    r9510 r9970  
    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("CheckFoundation", bool, ICmpObstruction, CheckFoundation, std::string)
    2727DEFINE_INTERFACE_METHOD_0("GetConstructionCollisions", std::vector<entity_id_t>, ICmpObstruction, GetConstructionCollisions)
    2828DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)
  • ps/trunk/source/simulation2/components/ICmpObstruction.h

    r9889 r9970  
    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 CheckFoundation(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;
  • ps/trunk/source/simulation2/components/ICmpPathfinder.h

    r9665 r9970  
    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, entity_id_t id, pass_class_t passClass) = 0;
     165
     166    /**
    153167     * Toggle the storage and rendering of debug info.
    154168     */
  • ps/trunk/source/simulation2/components/ICmpTerritoryManager.cpp

    r9889 r9970  
    2323
    2424BEGIN_INTERFACE_WRAPPER(TerritoryManager)
     25DEFINE_INTERFACE_METHOD_2("GetOwner", int32_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t)
    2526END_INTERFACE_WRAPPER(TerritoryManager)
  • ps/trunk/source/simulation2/components/ICmpTerritoryManager.h

    r9889 r9970  
    1919#define INCLUDED_ICMPTERRITORYMANAGER
    2020
     21#include "simulation2/helpers/Grid.h"
    2122#include "simulation2/system/Interface.h"
    22 
    23 #include "simulation2/helpers/Grid.h"
     23#include "simulation2/components/ICmpPosition.h"
    2424
    2525class ICmpTerritoryManager : public IComponent
     
    3030    virtual const Grid<u8>& GetTerritoryGrid() = 0;
    3131
     32    /**
     33     * Get owner of territory at given position
     34     *
     35     * @return player ID of owner; 0 if neutral territory
     36     */
     37    virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z) = 0;
     38
    3239    DECLARE_INTERFACE_TYPE(TerritoryManager)
    3340};
  • ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp

    r9605 r9970  
    8787    }
    8888
     89    virtual ICmpPathfinder::pass_class_t GetPassabilityClass()
     90    {
     91        return m_Script.Call<ICmpPathfinder::pass_class_t>("GetPassabilityClass");
     92    }
     93
    8994    virtual void SetUnitRadius(fixed radius)
    9095    {
  • ps/trunk/source/simulation2/components/ICmpUnitMotion.h

    r9605 r9970  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
     
    2121#include "simulation2/system/Interface.h"
    2222
    23 #include "ICmpPosition.h" // for entity_pos_t
     23#include "simulation2/components/ICmpPathfinder.h" // for pass_class_t
     24#include "simulation2/components/ICmpPosition.h" // for entity_pos_t
    2425
    2526/**
     
    9495
    9596    /**
     97     * Get the unit's passability class.
     98     */
     99    virtual ICmpPathfinder::pass_class_t GetPassabilityClass() = 0;
     100
     101    /**
    96102     * Override the default obstruction radius, used for planning paths and checking for collisions.
    97103     * Bad things may happen if this entity has an active Obstruction component with a larger
Note: See TracChangeset for help on using the changeset viewer.