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.

This was SVN commit r9970.
This commit is contained in:
historic_bruno 2011-08-06 08:11:05 +00:00
parent 76e6b04c71
commit f378e2e651
28 changed files with 755 additions and 198 deletions

View File

@ -80,13 +80,15 @@ function updateBuildingPlacementPreview()
if (placementEntity && placementPosition) if (placementEntity && placementPosition)
{ {
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
"template": placementEntity, "template": placementEntity,
"x": placementPosition.x, "x": placementPosition.x,
"z": placementPosition.z, "z": placementPosition.z,
"angle": placementAngle "angle": placementAngle
}); });
} }
return false;
} }
function resetPlacementEntity() function resetPlacementEntity()
@ -328,13 +330,7 @@ function tryPlaceBuilding(queued)
var selection = g_Selection.toList(); var selection = g_Selection.toList();
// Use the preview to check it's a valid build location // Use the preview to check it's a valid build location
var ok = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { if (!updateBuildingPlacementPreview())
"template": placementEntity,
"x": placementPosition.x,
"z": placementPosition.z,
"angle": placementAngle
});
if (!ok)
{ {
// invalid location - don't build it // invalid location - don't build it
// TODO: play a sound? // TODO: play a sound?
@ -561,13 +557,15 @@ function handleInputBeforeGui(ev, hoveredObject)
placementAngle = defaultPlacementAngle; placementAngle = defaultPlacementAngle;
} }
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
"template": placementEntity, "template": placementEntity,
"x": placementPosition.x, "x": placementPosition.x,
"z": placementPosition.z, "z": placementPosition.z
"angle": placementAngle
}); });
if (snapData.snapped)
placementAngle = snapData.angle;
updateBuildingPlacementPreview();
break; break;
case "mousebuttonup": case "mousebuttonup":
@ -821,12 +819,15 @@ function handleInputAfterGui(ev)
{ {
case "mousemotion": case "mousemotion":
placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y); placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
"template": placementEntity, "template": placementEntity,
"x": placementPosition.x, "x": placementPosition.x,
"z": placementPosition.z, "z": placementPosition.z
"angle": placementAngle
}); });
if (snapData.snapped)
placementAngle = snapData.angle;
updateBuildingPlacementPreview();
return false; // continue processing mouse motion return false; // continue processing mouse motion
@ -855,21 +856,11 @@ function handleInputAfterGui(ev)
{ {
case "session.rotate.cw": case "session.rotate.cw":
placementAngle += rotation_step; placementAngle += rotation_step;
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { updateBuildingPlacementPreview();
"template": placementEntity,
"x": placementPosition.x,
"z": placementPosition.z,
"angle": placementAngle
});
break; break;
case "session.rotate.ccw": case "session.rotate.ccw":
placementAngle -= rotation_step; placementAngle -= rotation_step;
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", { updateBuildingPlacementPreview();
"template": placementEntity,
"x": placementPosition.x,
"z": placementPosition.z,
"angle": placementAngle
});
break; break;
} }

View File

@ -1,63 +1,102 @@
function BuildLimits() {} function BuildLimits() {}
BuildLimits.prototype.Schema = BuildLimits.prototype.Schema =
"<a:help></a:help>" + "<a:help>Specifies per category limits on number of buildings that can be constructed for each player.</a:help>" +
"<a:example>" +
"<Limits>" +
"<CivilCentre/>" +
"<ScoutTower>20</ScoutTower>" +
"<Fortress>5</Fortress>" +
"<Special>" +
"<LimitPerCivCentre>1</LimitPerCivCentre>" +
"</Special>" +
"</Limits>" +
"</a:example>" +
"<element name='LimitMultiplier'>" + "<element name='LimitMultiplier'>" +
"<ref name='positiveDecimal'/>" + "<ref name='positiveDecimal'/>" +
"</element>" + "</element>" +
"<element name='Limits'>" + "<element name='Limits'>" +
"<zeroOrMore>" + "<zeroOrMore>" +
"<element>" + "<element a:help='Specifies a category of building on which to apply this limit. See BuildRestrictions for list of categories.'>" +
"<anyName />" + "<anyName />" +
"<text />" + "<choice>" +
"<text />" +
"<element name='LimitPerCivCentre' a:help='Specifies that this limit is per number of civil centres.'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</choice>" +
"</element>" + "</element>" +
"</zeroOrMore>" + "</zeroOrMore>" +
"</element>"; "</element>";
/*
* TODO: Use an inheriting player_{civ}.xml template for civ-specific limits
*/
BuildLimits.prototype.Init = function() BuildLimits.prototype.Init = function()
{ {
this.limits = []; this.limit = [];
this.unitCount = []; this.count = [];
for (var category in this.template.Limits) for (var category in this.template.Limits)
{ {
this.limits[category] = this.template.Limits[category]; this.limit[category] = this.template.Limits[category];
this.unitCount[category] = 0; this.count[category] = 0;
} }
}; };
BuildLimits.prototype.IncrementCount = function(category) BuildLimits.prototype.IncrementCount = function(category)
{ {
if (this.unitCount[category] !== undefined) if (this.count[category] !== undefined)
this.unitCount[category]++; {
this.count[category]++;
}
}; };
BuildLimits.prototype.DecrementCount = function(category) BuildLimits.prototype.DecrementCount = function(category)
{ {
if (this.unitCount[category] !== undefined) if (this.count[category] !== undefined)
this.unitCount[category]--; {
this.count[category]--;
}
}; };
BuildLimits.prototype.AllowedToBuild = function(category) BuildLimits.prototype.AllowedToBuild = function(category)
{ {
if (this.unitCount[category]) // TODO: The UI should reflect this before the user tries to place the building,
// since the limits are independent of placement location
// Allow unspecified categories and those with no limit
if (this.count[category] === undefined || this.limit[category] === undefined)
{ {
if (this.unitCount[category] >= this.limits[category]) return true;
}
// Rather than complicating the schema unecessarily, just handle special cases here
if (this.limit[category].LimitPerCivCentre !== undefined)
{
if (this.count[category] >= this.count["CivilCentre"] * this.limit[category].LimitPerCivCentre)
{ {
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player); var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Build limit reached for this building"}; var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category].LimitPerCivCentre+" per civil centre reached"};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification); cmpGUIInterface.PushNotification(notification);
return false; return false;
} }
else
return true;
} }
else else if (this.count[category] >= this.limit[category])
{ {
//Check here for terrain var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
return true; var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
var notification = {"player": cmpPlayer.GetPlayerID(), "message": category+" build limit of "+this.limit[category]+ " reached"};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
return false;
} }
return true;
}; };
Engine.RegisterComponentType(IID_BuildLimits, "BuildLimits", BuildLimits); Engine.RegisterComponentType(IID_BuildLimits, "BuildLimits", BuildLimits);

View File

@ -1,19 +1,37 @@
function BuildRestrictions() {} function BuildRestrictions() {}
BuildRestrictions.prototype.Schema = BuildRestrictions.prototype.Schema =
"<element name='PlacementType'>" + "<a:help>Specifies building placement restrictions as they relate to terrain, territories, and distance.</a:help>" +
"<a:example>" +
"<BuildRestrictions>" +
"<PlacementType>land</PlacementType>" +
"<Territory>own</Territory>" +
"<Category>Special</Category>" +
"<Distance>" +
"<FromCategory>CivilCentre</FromCategory>" +
"<MaxDistance>40</MaxDistance>" +
"</Distance>" +
"</BuildRestrictions>" +
"</a:example>" +
"<element name='PlacementType' a:help='Specifies the terrain type restriction for this building.'>" +
"<choice>" + "<choice>" +
"<value>standard</value>" + "<value>land</value>" +
"<value>settlement</value>" + "<value>shore</value>" +
"</choice>" + // TODO: add special types for fields, docks, walls
"</element>" +
"<element name='Territory'>" +
"<choice>" +
"<value>all</value>" +
"<value>allied</value>" +
"</choice>" + "</choice>" +
"</element>" + "</element>" +
"<element name='Category'>" + "<element name='Territory' a:help='Specifies territory type restrictions for this building.'>" +
"<list>" +
"<oneOrMore>" +
"<choice>" +
"<value>own</value>" +
"<value>ally</value>" +
"<value>neutral</value>" +
"<value>enemy</value>" +
"</choice>" +
"</oneOrMore>" +
"</list>" +
"</element>" +
"<element name='Category' a:help='Specifies the category of this building, for satisfying special constraints.'>" +
"<choice>" + "<choice>" +
"<value>CivilCentre</value>" + "<value>CivilCentre</value>" +
"<value>House</value>" + "<value>House</value>" +
@ -32,43 +50,183 @@ BuildRestrictions.prototype.Schema =
"<value>Resource</value>" + "<value>Resource</value>" +
"<value>Special</value>" + "<value>Special</value>" +
"</choice>" + "</choice>" +
"</element>"; "</element>" +
"<optional>" +
"<element name='Distance' a:help='Specifies distance restrictions on this building, relative to buildings from the given category.'>" +
"<interleave>" +
"<element name='FromCategory'>" +
"<choice>" +
"<value>CivilCentre</value>" +
"</choice>" +
"</element>" +
"<optional><element name='MinDistance'><data type='positiveInteger'/></element></optional>" +
"<optional><element name='MaxDistance'><data type='positiveInteger'/></element></optional>" +
"</interleave>" +
"</element>" +
"</optional>";
// TODO: add phases, prerequisites, etc // TODO: add phases, prerequisites, etc
/* BuildRestrictions.prototype.Init = function()
* TODO: the vague plan for Category is to add some BuildLimitManager which {
* specifies the limit per category, and which can determine whether you're this.territories = this.template.Territory.split(/\s+/);
* allowed to build more (based on the number in total / per territory / per };
* civ center as appropriate)
*/
/*
* TODO: the vague plan for PlacementType is that it may restrict the locations
* and orientations of new construction work (e.g. civ centers must be on settlements,
* docks must be on shores), which affects the UI and the build permissions
*/
BuildRestrictions.prototype.OnOwnershipChanged = function(msg) BuildRestrictions.prototype.OnOwnershipChanged = function(msg)
{ {
// This automatically updates building counts
if (this.template.Category) if (this.template.Category)
{ {
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (msg.from != -1) if (msg.from != -1)
{ {
var fromPlayerBuildLimits = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(msg.from), IID_BuildLimits); var fromPlayerBuildLimits = QueryPlayerIDInterface(msg.from, IID_BuildLimits);
fromPlayerBuildLimits.DecrementCount(this.template.Category); fromPlayerBuildLimits.DecrementCount(this.template.Category);
} }
if (msg.to != -1) if (msg.to != -1)
{ {
var toPlayerBuildLimits = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(msg.to), IID_BuildLimits); var toPlayerBuildLimits = QueryPlayerIDInterface(msg.to, IID_BuildLimits);
toPlayerBuildLimits.IncrementCount(this.template.Category); toPlayerBuildLimits.IncrementCount(this.template.Category);
} }
} }
}; };
BuildRestrictions.prototype.CheckPlacement = function(player)
{
// TODO: Return error code for invalid placement, which can be handled by the UI
// Check obstructions and terrain passability
var passClassName = "";
switch (this.template.PlacementType)
{
case "shore":
passClassName = "building-shore";
break;
case "land":
default:
passClassName = "building-land";
}
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction || !cmpObstruction.CheckFoundation(passClassName))
{
return false; // Fail
}
// Check territory restrictions
var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!(cmpTerritoryManager && cmpPlayer && cmpPosition && cmpPosition.IsInWorld()))
{
return false; // Fail
}
var pos = cmpPosition.GetPosition2D();
var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
var isOwn = (tileOwner == player);
var isNeutral = (tileOwner == 0);
var isAlly = !isOwn && cmpPlayer.IsAlly(tileOwner);
var isEnemy = !isNeutral && cmpPlayer.IsEnemy(tileOwner);
if ((isAlly && !this.HasTerritory("ally"))
|| (isOwn && !this.HasTerritory("own"))
|| (isNeutral && !this.HasTerritory("neutral"))
|| (isEnemy && !this.HasTerritory("enemy")))
{
return false; // Fail
}
// Check special requirements
if (this.template.Category == "Dock")
{
// Dock must be oriented from land facing into water
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
if (!cmpFootprint)
{
return false; // Fail
}
// Get building's footprint
var shape = cmpFootprint.GetShape();
var halfSize = 0;
if (shape.type == "square")
{
halfSize = shape.depth/2;
}
else if (shape.type == "circle")
{
halfSize = shape.radius;
}
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
if (!cmpTerrain || !cmpWaterManager)
{
return false; // Fail
}
var ang = cmpPosition.GetRotation().y;
var sz = halfSize * Math.sin(ang);
var cz = halfSize * Math.cos(ang);
if ((cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz) > cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz)) || // front
(cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz) <= cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz))) // back
{
return false; // Fail
}
}
// Check distance restriction
if (this.template.Distance)
{
var minDist = 65535;
var maxDist = 0;
var ents = Engine.GetEntitiesWithInterface(IID_BuildRestrictions);
for each (var ent in ents)
{
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (cmpBuildRestrictions.GetCategory() == this.template.Distance.FromCategory && IsOwnedByPlayer(player, ent))
{
var cmpEntPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpEntPosition && cmpEntPosition.IsInWorld())
{
var entPos = cmpEntPosition.GetPosition2D();
var dist = Math.sqrt((pos.x-entPos.x)*(pos.x-entPos.x) + (pos.y-entPos.y)*(pos.y-entPos.y));
if (dist < minDist)
{
minDist = dist;
}
if (dist > maxDist)
{
maxDist = dist;
}
}
}
}
if (this.template.Distance.MinDistance !== undefined && minDist < this.template.Distance.MinDistance
|| this.template.Distance.MaxDistance !== undefined && maxDist > this.template.Distance.MaxDistance)
{
return false; // Fail
}
}
// Success
return true;
};
BuildRestrictions.prototype.GetCategory = function() BuildRestrictions.prototype.GetCategory = function()
{ {
return this.template.Category; return this.template.Category;
}; };
BuildRestrictions.prototype.GetTerritories = function()
{
return this.territories;
};
BuildRestrictions.prototype.HasTerritory = function(territory)
{
return (this.territories.indexOf(territory) != -1);
};
Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions); Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions);

View File

@ -155,7 +155,11 @@ GarrisonHolder.prototype.Eject = function(entity)
// For now, just move the unit into the middle of the building where it'll probably get stuck // For now, just move the unit into the middle of the building where it'll probably get stuck
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
pos = cmpPosition.GetPosition(); pos = cmpPosition.GetPosition();
warn("Can't find free space to ungarrison unit");
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Can't find free space to ungarrison unit"};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
} }
var cmpNewPosition = Engine.QueryInterface(entity, IID_Position); var cmpNewPosition = Engine.QueryInterface(entity, IID_Position);

View File

@ -468,16 +468,22 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
pos.SetYRotation(cmd.angle); pos.SetYRotation(cmd.angle);
} }
// Check whether it's obstructed by other entities
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
var colliding = (cmpObstruction && cmpObstruction.CheckFoundationCollisions());
// Check whether it's in a visible region // Check whether it's in a visible region
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var visible = (cmpRangeManager.GetLosVisibility(ent, player) == "visible"); var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player) == "visible");
var validPlacement = false;
var ok = (!colliding && visible);
if (visible)
{ // Check whether it's obstructed by other entities or invalid terrain
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
error("cmpBuildRestrictions not defined");
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement(player));
}
var ok = (visible && validPlacement);
// Set it to a red shade if this is an invalid location // Set it to a red shade if this is an invalid location
var cmpVisual = Engine.QueryInterface(ent, IID_Visual); var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual) if (cmpVisual)
@ -494,6 +500,71 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
return false; return false;
}; };
GuiInterface.prototype.GetFoundationSnapData = function(player, data)
{
var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateMgr.GetTemplate(data.template);
if (template.BuildRestrictions.Category == "Dock")
{
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
// Find direction of most open water, algorithm:
// 1. Pick points in a circle around dock
// 2. If point is in water, add to array
// 3. Scan array looking for consective points
// 4. Find longest sequence of conseuctive points
// 5. Calculate angle using average of sequence
const numPoints = 16;
const dist = 20.0;
var waterPoints = [];
for (var i = 0; i < numPoints; ++i)
{
var angle = (i/numPoints)*2*Math.PI;
var nx = data.x - dist*Math.sin(angle);
var nz = data.z + dist*Math.cos(angle);
if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
{
waterPoints.push(i);
}
}
var consec = [];
var length = waterPoints.length;
for (var i = 0; i < length; ++i)
{
var count = 0;
for (var j = 0; j < (length-1); ++j)
{
if (((waterPoints[(i + j) % length]+1) % numPoints) == waterPoints[(i + j + 1) % length])
{
++count;
}
else
{
break;
}
}
consec[i] = count;
}
var start = 0;
var count = 0;
for (var c in consec)
{
if (consec[c] > count)
{
start = c;
count = consec[c];
}
}
return { "snapped": true, "x": data.x, "z": data.z, "angle": -(((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI) };
}
return {"snapped": false};
};
GuiInterface.prototype.PlaySound = function(player, data) GuiInterface.prototype.PlaySound = function(player, data)
{ {
// Ignore if no entity was passed // Ignore if no entity was passed
@ -583,6 +654,7 @@ var exposedFunctions = {
"SetStatusBars": 1, "SetStatusBars": 1,
"DisplayRallyPoint": 1, "DisplayRallyPoint": 1,
"SetBuildingPlacementPreview": 1, "SetBuildingPlacementPreview": 1,
"GetFoundationSnapData": 1,
"PlaySound": 1, "PlaySound": 1,
"FindIdleUnit": 1, "FindIdleUnit": 1,

View File

@ -217,7 +217,11 @@ TrainingQueue.prototype.SpawnUnits = function(templateName, count, metadata)
// What should we do here? // What should we do here?
// For now, just move the unit into the middle of the building where it'll probably get stuck // For now, just move the unit into the middle of the building where it'll probably get stuck
pos = cmpPosition.GetPosition(); pos = cmpPosition.GetPosition();
warn("Can't find free space to spawn trained unit");
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var notification = {"player": cmpPlayer.GetPlayerID(), "message": "Can't find free space to spawn trained unit"};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
} }
var cmpNewPosition = Engine.QueryInterface(ent, IID_Position); var cmpNewPosition = Engine.QueryInterface(ent, IID_Position);

View File

@ -15,11 +15,21 @@
<ship> <ship>
<MinWaterDepth>1</MinWaterDepth> <MinWaterDepth>1</MinWaterDepth>
</ship> </ship>
<!-- Building construction classes: --> <!-- Building construction classes:
* Land is used for most buildings, which must be away
from water and not on cliffs or mountains.
* Shore is used for docks, which must be near water and
land, yet shallow enough for builders to approach.
-->
<building-land> <building-land>
<MaxWaterDepth>0</MaxWaterDepth> <MaxWaterDepth>0</MaxWaterDepth>
<MinShoreDistance>2.0</MinShoreDistance>
<MaxTerrainSlope>1.0</MaxTerrainSlope> <MaxTerrainSlope>1.0</MaxTerrainSlope>
</building-land> </building-land>
<building-shore>
<MaxShoreDistance>3.0</MaxShoreDistance>
<MaxTerrainSlope>1.5</MaxTerrainSlope>
</building-shore>
</PassabilityClasses> </PassabilityClasses>

View File

@ -74,7 +74,7 @@ function ProcessCommand(player, cmd)
case "returnresource": case "returnresource":
// Check dropsite is owned by player // Check dropsite is owned by player
if (IsOwnedByPlayer(cmd.target, player)) if (IsOwnedByPlayer(player, cmd.target))
{ {
var entities = FilterEntityList(cmd.entities, player, controlAllUnits); var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) { GetFormationUnitAIs(entities).forEach(function(cmpUnitAI) {
@ -131,23 +131,38 @@ function ProcessCommand(player, cmd)
// Tentatively create the foundation (we might find later that it's a invalid build command) // Tentatively create the foundation (we might find later that it's a invalid build command)
var ent = Engine.AddEntity("foundation|" + cmd.template); var ent = Engine.AddEntity("foundation|" + cmd.template);
// TODO: report errors (e.g. invalid template names) if (ent == INVALID_ENTITY)
{
// Error (e.g. invalid template names)
error("Error creating foundation for '" + cmd.template + "'");
break;
}
// Move the foundation to the right place // Move the foundation to the right place
var cmpPosition = Engine.QueryInterface(ent, IID_Position); var cmpPosition = Engine.QueryInterface(ent, IID_Position);
cmpPosition.JumpTo(cmd.x, cmd.z); cmpPosition.JumpTo(cmd.x, cmd.z);
cmpPosition.SetYRotation(cmd.angle); cmpPosition.SetYRotation(cmd.angle);
// Check whether it's obstructed by other entities // Check whether it's obstructed by other entities or invalid terrain
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (cmpObstruction && cmpObstruction.CheckFoundationCollisions()) if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player))
{ {
// TODO: report error to player (the building site was obstructed) var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
print("Building site was obstructed\n"); cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" });
// Remove the foundation because the construction was aborted // Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent); Engine.DestroyEntity(ent);
break;
}
// Check build limits
var cmpBuildLimits = QueryPlayerIDInterface(player, IID_BuildLimits);
if (!cmpBuildLimits || !cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
{
// TODO: The UI should tell the user they can't build this (but we still need this check)
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
break; break;
} }
@ -167,19 +182,10 @@ function ProcessCommand(player, cmd)
break; break;
} }
*/ */
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
var cmpBuildLimits = Engine.QueryInterface(playerEnt, IID_BuildLimits);
if (!cmpBuildLimits.AllowedToBuild(cmpBuildRestrictions.GetCategory()))
{
Engine.DestroyEntity(ent);
break;
}
var cmpCost = Engine.QueryInterface(ent, IID_Cost); var cmpCost = Engine.QueryInterface(ent, IID_Cost);
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts())) if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
{ {
// TODO: report error to player (they ran out of resources)
Engine.DestroyEntity(ent); Engine.DestroyEntity(ent);
break; break;
} }
@ -564,15 +570,6 @@ function CanMoveEntsIntoFormation(ents, formationName)
return true; return true;
} }
/**
* Check if entity is owned by player
*/
function IsOwnedByPlayer(entity, player)
{
var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
return (cmpOwnership && cmpOwnership.GetOwner() == player);
}
/** /**
* Check if player can control this entity * Check if player can control this entity
* returns: true if the entity is valid and owned by the player if * returns: true if the entity is valid and owned by the player if
@ -580,7 +577,7 @@ function IsOwnedByPlayer(entity, player)
*/ */
function CanControlUnit(entity, player, controlAll) function CanControlUnit(entity, player, controlAll)
{ {
return (IsOwnedByPlayer(entity, player) || controlAll); return (IsOwnedByPlayer(player, entity) || controlAll);
} }
/** /**

View File

@ -230,6 +230,15 @@ function IsOwnedByAllyOfEntity(entity, target)
return false; return false;
} }
/**
* Returns true if the entity 'target' is owned by player
*/
function IsOwnedByPlayer(player, target)
{
var cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership);
return (cmpOwnershipTarget && player == cmpOwnershipTarget.GetOwner());
}
/** /**
* Returns true if the entity 'target' is owned by an ally of player * Returns true if the entity 'target' is owned by an ally of player
*/ */
@ -278,5 +287,6 @@ Engine.RegisterGlobal("LoadPlayerSettings", LoadPlayerSettings);
Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface); Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface);
Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface); Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface);
Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity); Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity);
Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer);
Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer); Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer);
Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer); Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer);

View File

@ -3,6 +3,9 @@
<BuildLimits> <BuildLimits>
<LimitMultiplier>1.0</LimitMultiplier> <LimitMultiplier>1.0</LimitMultiplier>
<Limits> <Limits>
<CivilCentre/>
<ScoutTower>20</ScoutTower>
<Fortress>5</Fortress>
</Limits> </Limits>
</BuildLimits> </BuildLimits>
<Player/> <Player/>

View File

@ -10,8 +10,8 @@
<GarrisonArrowMultiplier>0</GarrisonArrowMultiplier> <GarrisonArrowMultiplier>0</GarrisonArrowMultiplier>
</BuildingAI> </BuildingAI>
<BuildRestrictions> <BuildRestrictions>
<PlacementType>standard</PlacementType> <PlacementType>land</PlacementType>
<Territory>allied</Territory> <Territory>own</Territory>
</BuildRestrictions> </BuildRestrictions>
<Cost> <Cost>
<Population>0</Population> <Population>0</Population>

View File

@ -6,9 +6,12 @@
<Crush>10.0</Crush> <Crush>10.0</Crush>
</Armour> </Armour>
<BuildRestrictions> <BuildRestrictions>
<PlacementType>settlement</PlacementType> <Territory>own neutral</Territory>
<Territory>all</Territory>
<Category>CivilCentre</Category> <Category>CivilCentre</Category>
<Distance>
<FromCategory>CivilCentre</FromCategory>
<MinDistance>150</MinDistance>
</Distance>
</BuildRestrictions> </BuildRestrictions>
<Cost> <Cost>
<PopulationBonus>20</PopulationBonus> <PopulationBonus>20</PopulationBonus>

View File

@ -7,7 +7,6 @@
</Armour> </Armour>
<BuildRestrictions> <BuildRestrictions>
<Category>Mill</Category> <Category>Mill</Category>
<Territory>all</Territory>
</BuildRestrictions> </BuildRestrictions>
<Cost> <Cost>
<BuildTime>80</BuildTime> <BuildTime>80</BuildTime>

View File

@ -6,6 +6,8 @@
<Crush>20.0</Crush> <Crush>20.0</Crush>
</Armour> </Armour>
<BuildRestrictions> <BuildRestrictions>
<Territory>own ally neutral</Territory>
<PlacementType>shore</PlacementType>
<Category>Dock</Category> <Category>Dock</Category>
</BuildRestrictions> </BuildRestrictions>
<Cost> <Cost>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games. /* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -20,10 +20,13 @@
#include "simulation2/system/Component.h" #include "simulation2/system/Component.h"
#include "ICmpFootprint.h" #include "ICmpFootprint.h"
#include "ICmpObstruction.h" #include "simulation2/components/ICmpObstruction.h"
#include "ICmpObstructionManager.h" #include "simulation2/components/ICmpObstructionManager.h"
#include "ICmpPosition.h" #include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpUnitMotion.h"
#include "simulation2/MessageTypes.h" #include "simulation2/MessageTypes.h"
#include "graphics/Terrain.h" // For CELL_SIZE
#include "maths/FixedVector2D.h" #include "maths/FixedVector2D.h"
class CCmpFootprint : public ICmpFootprint class CCmpFootprint : public ICmpFootprint
@ -125,7 +128,7 @@ public:
// because the footprint might be inside the obstruction, but it hopefully gives us a nicer // because the footprint might be inside the obstruction, but it hopefully gives us a nicer
// shape.) // shape.)
CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1)); const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1));
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null() || !cmpPosition->IsInWorld()) if (cmpPosition.null() || !cmpPosition->IsInWorld())
@ -146,30 +149,46 @@ public:
} }
// else use zero radius // else use zero radius
// The spawn point should be far enough from this footprint to fit the unit, plus a little gap // Get passability class from UnitMotion
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2); CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), spawned);
if (cmpUnitMotion.null())
return error;
ICmpPathfinder::pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass();
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (cmpPathfinder.null())
return error;
CFixedVector2D initialPos = cmpPosition->GetPosition2D(); CFixedVector2D initialPos = cmpPosition->GetPosition2D();
entity_angle_t initialAngle = cmpPosition->GetRotation().Y; entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
// Max spawning distance in tiles
const size_t maxSpawningDistance = 4;
if (m_Shape == CIRCLE) if (m_Shape == CIRCLE)
{ {
entity_pos_t radius = m_Size0 + clearance; // Expand outwards from foundation
for (size_t dist = 0; dist <= maxSpawningDistance; ++dist)
// Try equally-spaced points around the circle, starting from the front and expanding outwards in alternating directions
const ssize_t numPoints = 31;
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]
{ {
entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints); // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + CELL_SIZE*dist);
entity_pos_t radius = m_Size0 + clearance;
fixed s, c; // Try equally-spaced points around the circle in alternating directions, starting from the front
sincos_approx(angle, s, c); const ssize_t numPoints = 31 + 2*dist;
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]
{
entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints);
CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius)); fixed s, c;
sincos_approx(angle, s, c);
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, spawnedRadius, NULL))
return pos; // this position is okay, so return it SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass))
return pos; // this position is okay, so return it
}
} }
} }
else else
@ -177,47 +196,54 @@ public:
fixed s, c; fixed s, c;
sincos_approx(initialAngle, s, c); sincos_approx(initialAngle, s, c);
for (size_t edge = 0; edge < 4; ++edge) // Expand outwards from foundation
for (size_t dist = 0; dist <= maxSpawningDistance; ++dist)
{ {
// Try equally-spaced points along the edge, starting from the middle and expanding outwards in alternating directions // The spawn point should be far enough from this footprint to fit the unit, plus a little gap
const ssize_t numPoints = 9; entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + CELL_SIZE*dist);
// Compute the direction and length of the current edge for (size_t edge = 0; edge < 4; ++edge)
CFixedVector2D dir;
fixed sx, sy;
switch (edge)
{ {
case 0: // Try equally-spaced points along the edge in alternating directions, starting from the middle
dir = CFixedVector2D(c, -s); const ssize_t numPoints = 9 + 2*dist;
sx = m_Size0;
sy = m_Size1;
break;
case 1:
dir = CFixedVector2D(-s, -c);
sx = m_Size1;
sy = m_Size0;
break;
case 2:
dir = CFixedVector2D(s, c);
sx = m_Size1;
sy = m_Size0;
break;
case 3:
dir = CFixedVector2D(-c, s);
sx = m_Size0;
sy = m_Size1;
break;
}
CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
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] // Compute the direction and length of the current edge
{ CFixedVector2D dir;
CFixedVector2D pos (center + dir*i); fixed sx, sy;
switch (edge)
{
case 0:
dir = CFixedVector2D(c, -s);
sx = m_Size0;
sy = m_Size1;
break;
case 1:
dir = CFixedVector2D(-s, -c);
sx = m_Size1;
sy = m_Size0;
break;
case 2:
dir = CFixedVector2D(s, c);
sx = m_Size1;
sy = m_Size0;
break;
case 3:
dir = CFixedVector2D(-c, s);
sx = m_Size0;
sy = m_Size1;
break;
}
CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance);
dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1));
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity 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]
if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, spawnedRadius, NULL)) {
return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it CFixedVector2D pos (center + dir*i);
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass))
return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
}
} }
} }
} }

View File

@ -20,8 +20,8 @@
#include "simulation2/system/Component.h" #include "simulation2/system/Component.h"
#include "ICmpObstruction.h" #include "ICmpObstruction.h"
#include "ICmpObstructionManager.h" #include "simulation2/components/ICmpObstructionManager.h"
#include "ICmpPosition.h" #include "simulation2/components/ICmpPosition.h"
#include "simulation2/MessageTypes.h" #include "simulation2/MessageTypes.h"
@ -328,7 +328,7 @@ public:
return entity_pos_t::Zero(); return entity_pos_t::Zero();
} }
virtual bool CheckFoundationCollisions() virtual bool CheckFoundation(std::string className)
{ {
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
if (cmpPosition.null()) if (cmpPosition.null())
@ -339,17 +339,20 @@ public:
CFixedVector2D pos = cmpPosition->GetPosition2D(); CFixedVector2D pos = cmpPosition->GetPosition2D();
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null()) if (cmpPathfinder.null())
return false; // error return false; // error
// Get passability class
ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className);
// Ignore collisions with self, or with non-foundation-blocking obstructions // Ignore collisions with self, or with non-foundation-blocking obstructions
SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION); SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
if (m_Type == STATIC) if (m_Type == STATIC)
return cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, NULL); return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
else else
return cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, NULL); return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Size0, passClass);
} }
virtual std::vector<entity_id_t> GetConstructionCollisions() virtual std::vector<entity_id_t> GetConstructionCollisions()

View File

@ -29,6 +29,7 @@
#include "ps/Profile.h" #include "ps/Profile.h"
#include "renderer/Scene.h" #include "renderer/Scene.h"
#include "simulation2/MessageTypes.h" #include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpObstructionManager.h"
#include "simulation2/components/ICmpWaterManager.h" #include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/serialization/SerializeTemplates.h" #include "simulation2/serialization/SerializeTemplates.h"
@ -372,8 +373,98 @@ void CCmpPathfinder::UpdateGrid()
CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY); CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
// TOOD: these bits should come from ICmpTerrain
CTerrain& terrain = GetSimContext().GetTerrain(); CTerrain& terrain = GetSimContext().GetTerrain();
// avoid integer overflow in intermediate calculation
const u16 shoreMax = 32767;
// First pass - find underwater tiles
Grid<bool> waterGrid(m_MapSize, m_MapSize);
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
fixed x, z;
TileCenter(i, j, x, z);
bool underWater = !cmpWaterMan.null() && (cmpWaterMan->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z));
waterGrid.set(i, j, underWater);
}
}
// Second pass - find shore tiles
Grid<u16> shoreGrid(m_MapSize, m_MapSize);
for (u16 j = 0; j < m_MapSize; ++j)
{
for (u16 i = 0; i < m_MapSize; ++i)
{
// Find a land tile
if (!waterGrid.get(i, j) && (
(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)) ||
(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)) ||
(j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1))
))
{
shoreGrid.set(i, j, 0);
}
else
{
shoreGrid.set(i, j, shoreMax);
}
}
}
// Expand influences to find shore distance
for (size_t y = 0; y < m_MapSize; ++y)
{
u16 min = shoreMax;
for (size_t x = 0; x < m_MapSize; ++x)
{
u16 g = shoreGrid.get(x, y);
if (g > min)
shoreGrid.set(x, y, min);
else if (g < min)
min = g;
++min;
}
for (size_t x = m_MapSize; x > 0; --x)
{
u16 g = shoreGrid.get(x-1, y);
if (g > min)
shoreGrid.set(x-1, y, min);
else if (g < min)
min = g;
++min;
}
}
for (size_t x = 0; x < m_MapSize; ++x)
{
u16 min = shoreMax;
for (size_t y = 0; y < m_MapSize; ++y)
{
u16 g = shoreGrid.get(x, y);
if (g > min)
shoreGrid.set(x, y, min);
else if (g < min)
min = g;
++min;
}
for (size_t y = m_MapSize; y > 0; --y)
{
u16 g = shoreGrid.get(x, y-1);
if (g > min)
shoreGrid.set(x, y-1, min);
else if (g < min)
min = g;
++min;
}
}
// Apply passability classes to terrain
for (u16 j = 0; j < m_MapSize; ++j) for (u16 j = 0; j < m_MapSize; ++j)
{ {
for (u16 i = 0; i < m_MapSize; ++i) for (u16 i = 0; i < m_MapSize; ++i)
@ -385,7 +476,7 @@ void CCmpPathfinder::UpdateGrid()
u8 obstruct = m_ObstructionGrid->get(i, j); u8 obstruct = m_ObstructionGrid->get(i, j);
fixed height = terrain.GetVertexGroundLevelFixed(i, j); // TODO: should use tile centre fixed height = terrain.GetExactGroundLevelFixed(x, z);
fixed water; fixed water;
if (!cmpWaterMan.null()) if (!cmpWaterMan.null())
@ -395,6 +486,8 @@ void CCmpPathfinder::UpdateGrid()
fixed slope = terrain.GetSlopeFixed(i, j); fixed slope = terrain.GetSlopeFixed(i, j);
fixed shoredist = fixed::FromInt(shoreGrid.get(i, j));
if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING) if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
t |= 1; t |= 1;
@ -411,7 +504,7 @@ void CCmpPathfinder::UpdateGrid()
{ {
for (size_t n = 0; n < m_PassClasses.size(); ++n) for (size_t n = 0; n < m_PassClasses.size(); ++n)
{ {
if (!m_PassClasses[n].IsPassable(depth, slope)) if (!m_PassClasses[n].IsPassable(depth, slope, shoredist))
t |= m_PassClasses[n].m_Mask; t |= m_PassClasses[n].m_Mask;
} }
} }
@ -550,3 +643,81 @@ void CCmpPathfinder::ProcessSameTurnMoves()
} }
} }
bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter,
entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass)
{
// Check unit obstruction
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
return false;
if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL))
return false;
// Test against terrain:
UpdateGrid();
u16 i0, j0, i1, j1;
NearestTile(x - r, z - r, i0, j0);
NearestTile(x + r, z + r, i1, j1);
for (u16 j = j0; j <= j1; ++j)
{
for (u16 i = i0; i <= i1; ++i)
{
if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
{
return false;
}
}
}
return true;
}
bool CCmpPathfinder::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)
{
// Check unit obstruction
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
if (cmpObstructionManager.null())
return false;
if (cmpObstructionManager->TestStaticShape(filter, x, z, a, w, h, NULL))
return false;
// Test against terrain:
UpdateGrid();
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
if (cmpObstruction.null())
return false;
ICmpObstructionManager::ObstructionSquare square;
if (!cmpObstruction->GetObstructionSquare(square))
return false;
CFixedVector2D halfSize(square.hw, square.hh);
halfSize = halfSize * 1.41421f;
CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);
u16 i0, j0, i1, j1;
NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0);
NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1);
for (u16 j = j0; j <= j1; ++j)
{
for (u16 i = i0; i <= i1; ++i)
{
entity_pos_t x, z;
TileCenter(i, j, x, z);
if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize)
&& !IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
{
return false;
}
}
}
return true;
}

View File

@ -96,11 +96,22 @@ public:
m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed(); m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed();
else else
m_MaxSlope = std::numeric_limits<fixed>::max(); m_MaxSlope = std::numeric_limits<fixed>::max();
if (node.GetChild("MinShoreDistance").IsOk())
m_MinShore = node.GetChild("MinShoreDistance").ToFixed();
else
m_MinShore = std::numeric_limits<fixed>::min();
if (node.GetChild("MaxShoreDistance").IsOk())
m_MaxShore = node.GetChild("MaxShoreDistance").ToFixed();
else
m_MaxShore = std::numeric_limits<fixed>::max();
} }
bool IsPassable(fixed waterdepth, fixed steepness) bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist)
{ {
return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope)); return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope) && (m_MinShore <= shoredist && shoredist <= m_MaxShore));
} }
ICmpPathfinder::pass_class_t m_Mask; ICmpPathfinder::pass_class_t m_Mask;
@ -108,6 +119,8 @@ private:
fixed m_MinDepth; fixed m_MinDepth;
fixed m_MaxDepth; fixed m_MaxDepth;
fixed m_MaxSlope; fixed m_MaxSlope;
fixed m_MinShore;
fixed m_MaxShore;
}; };
typedef u16 TerrainTile; typedef u16 TerrainTile;
@ -247,6 +260,10 @@ public:
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); 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);
virtual bool CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass);
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);
virtual void FinishAsyncRequests(); virtual void FinishAsyncRequests();
void ProcessLongRequests(const std::vector<AsyncLongPathRequest>& longRequests); void ProcessLongRequests(const std::vector<AsyncLongPathRequest>& longRequests);

View File

@ -453,6 +453,7 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
permittedComponentTypes.insert("Footprint"); permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("Obstruction"); permittedComponentTypes.insert("Obstruction");
permittedComponentTypes.insert("Decay"); permittedComponentTypes.insert("Decay");
permittedComponentTypes.insert("BuildRestrictions");
// Need these for the Actor Viewer: // Need these for the Actor Viewer:
permittedComponentTypes.insert("Attack"); permittedComponentTypes.insert("Attack");

View File

@ -167,6 +167,8 @@ public:
return *m_Territories; return *m_Territories;
} }
virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z);
// To support lazy updates of territory render data, // To support lazy updates of territory render data,
// we maintain a DirtyID here and increment it whenever territories change; // we maintain a DirtyID here and increment it whenever territories change;
// if a caller has a lower DirtyID then it needs to be updated. // if a caller has a lower DirtyID then it needs to be updated.
@ -683,6 +685,13 @@ void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)
collector.Submit(&m_BoundaryLines[i]); collector.Submit(&m_BoundaryLines[i]);
} }
int32_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
{
u16 i, j;
CalculateTerritories();
NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
return m_Territories->get(i, j);
}
void TerritoryOverlay::StartRender() void TerritoryOverlay::StartRender()
{ {

View File

@ -20,15 +20,15 @@
#include "simulation2/system/Component.h" #include "simulation2/system/Component.h"
#include "ICmpUnitMotion.h" #include "ICmpUnitMotion.h"
#include "ICmpObstruction.h" #include "simulation2/components/ICmpObstruction.h"
#include "ICmpObstructionManager.h" #include "simulation2/components/ICmpObstructionManager.h"
#include "ICmpOwnership.h" #include "simulation2/components/ICmpOwnership.h"
#include "ICmpPosition.h" #include "simulation2/components/ICmpPosition.h"
#include "ICmpPathfinder.h" #include "simulation2/components/ICmpPathfinder.h"
#include "ICmpRangeManager.h" #include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Geometry.h"
#include "simulation2/helpers/Render.h" #include "simulation2/helpers/Render.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/serialization/SerializeTemplates.h" #include "simulation2/serialization/SerializeTemplates.h"
#include "graphics/Overlay.h" #include "graphics/Overlay.h"
@ -122,8 +122,8 @@ public:
bool m_FormationController; bool m_FormationController;
fixed m_WalkSpeed; // in metres per second fixed m_WalkSpeed; // in metres per second
fixed m_RunSpeed; fixed m_RunSpeed;
u8 m_PassClass; ICmpPathfinder::pass_class_t m_PassClass;
u8 m_CostClass; ICmpPathfinder::cost_class_t m_CostClass;
// Dynamic state: // Dynamic state:
@ -400,6 +400,11 @@ public:
return m_RunSpeed; return m_RunSpeed;
} }
virtual ICmpPathfinder::pass_class_t GetPassabilityClass()
{
return m_PassClass;
}
virtual void SetSpeed(fixed speed) virtual void SetSpeed(fixed speed)
{ {
m_Speed = speed; m_Speed = speed;

View File

@ -23,7 +23,7 @@
BEGIN_INTERFACE_WRAPPER(Obstruction) BEGIN_INTERFACE_WRAPPER(Obstruction)
DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius) DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius)
DEFINE_INTERFACE_METHOD_0("CheckFoundationCollisions", bool, ICmpObstruction, CheckFoundationCollisions) DEFINE_INTERFACE_METHOD_1("CheckFoundation", bool, ICmpObstruction, CheckFoundation, std::string)
DEFINE_INTERFACE_METHOD_0("GetConstructionCollisions", std::vector<entity_id_t>, ICmpObstruction, GetConstructionCollisions) DEFINE_INTERFACE_METHOD_0("GetConstructionCollisions", std::vector<entity_id_t>, ICmpObstruction, GetConstructionCollisions)
DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool) DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)
DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool) DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool)

View File

@ -44,14 +44,14 @@ public:
/** /**
* Test whether this entity is colliding with any obstruction that are set to * Test whether this entity is colliding with any obstruction that are set to
* block the creation of foundations. * block the creation of foundations.
* @return true if there is a collision * @return true if foundation is valid (not obstructed)
*/ */
virtual bool CheckFoundationCollisions() = 0; virtual bool CheckFoundation(std::string className) = 0;
/** /**
* Returns a list of entities that are colliding with this entity, and that * Returns a list of entities that are colliding with this entity, and that
* are set to block construction. * are set to block construction.
* @return true if there is a collision * @return vector of blocking entities
*/ */
virtual std::vector<entity_id_t> GetConstructionCollisions() = 0; virtual std::vector<entity_id_t> GetConstructionCollisions() = 0;

View File

@ -149,6 +149,20 @@ public:
*/ */
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; 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;
/**
* Check whether a unit placed here is valid and doesn't hit any obstructions
* or impassable terrain.
* Returns true if the placement is okay.
*/
virtual bool CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass) = 0;
/**
* Check whether a building placed here is valid and doesn't hit any obstructions
* or impassable terrain.
* Returns true if the placement is okay.
*/
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;
/** /**
* Toggle the storage and rendering of debug info. * Toggle the storage and rendering of debug info.
*/ */

View File

@ -22,4 +22,5 @@
#include "simulation2/system/InterfaceScripted.h" #include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(TerritoryManager) BEGIN_INTERFACE_WRAPPER(TerritoryManager)
DEFINE_INTERFACE_METHOD_2("GetOwner", int32_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t)
END_INTERFACE_WRAPPER(TerritoryManager) END_INTERFACE_WRAPPER(TerritoryManager)

View File

@ -18,9 +18,9 @@
#ifndef INCLUDED_ICMPTERRITORYMANAGER #ifndef INCLUDED_ICMPTERRITORYMANAGER
#define INCLUDED_ICMPTERRITORYMANAGER #define INCLUDED_ICMPTERRITORYMANAGER
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Grid.h" #include "simulation2/helpers/Grid.h"
#include "simulation2/system/Interface.h"
#include "simulation2/components/ICmpPosition.h"
class ICmpTerritoryManager : public IComponent class ICmpTerritoryManager : public IComponent
{ {
@ -29,6 +29,13 @@ public:
virtual const Grid<u8>& GetTerritoryGrid() = 0; virtual const Grid<u8>& GetTerritoryGrid() = 0;
/**
* Get owner of territory at given position
*
* @return player ID of owner; 0 if neutral territory
*/
virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z) = 0;
DECLARE_INTERFACE_TYPE(TerritoryManager) DECLARE_INTERFACE_TYPE(TerritoryManager)
}; };

View File

@ -86,6 +86,11 @@ public:
return m_Script.Call<fixed>("GetRunSpeed"); return m_Script.Call<fixed>("GetRunSpeed");
} }
virtual ICmpPathfinder::pass_class_t GetPassabilityClass()
{
return m_Script.Call<ICmpPathfinder::pass_class_t>("GetPassabilityClass");
}
virtual void SetUnitRadius(fixed radius) virtual void SetUnitRadius(fixed radius)
{ {
m_Script.CallVoid("SetUnitRadius", radius); m_Script.CallVoid("SetUnitRadius", radius);

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2010 Wildfire Games. /* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D. * This file is part of 0 A.D.
* *
* 0 A.D. is free software: you can redistribute it and/or modify * 0 A.D. is free software: you can redistribute it and/or modify
@ -20,7 +20,8 @@
#include "simulation2/system/Interface.h" #include "simulation2/system/Interface.h"
#include "ICmpPosition.h" // for entity_pos_t #include "simulation2/components/ICmpPathfinder.h" // for pass_class_t
#include "simulation2/components/ICmpPosition.h" // for entity_pos_t
/** /**
* Motion interface for entities with complex movement capabilities. * Motion interface for entities with complex movement capabilities.
@ -92,6 +93,11 @@ public:
*/ */
virtual fixed GetRunSpeed() = 0; virtual fixed GetRunSpeed() = 0;
/**
* Get the unit's passability class.
*/
virtual ICmpPathfinder::pass_class_t GetPassabilityClass() = 0;
/** /**
* Override the default obstruction radius, used for planning paths and checking for collisions. * Override the default obstruction radius, used for planning paths and checking for collisions.
* Bad things may happen if this entity has an active Obstruction component with a larger * Bad things may happen if this entity has an active Obstruction component with a larger