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:
parent
76e6b04c71
commit
f378e2e651
@ -80,13 +80,15 @@ function updateBuildingPlacementPreview()
|
||||
|
||||
if (placementEntity && placementPosition)
|
||||
{
|
||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||
return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||
"template": placementEntity,
|
||||
"x": placementPosition.x,
|
||||
"z": placementPosition.z,
|
||||
"angle": placementAngle
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function resetPlacementEntity()
|
||||
@ -328,13 +330,7 @@ function tryPlaceBuilding(queued)
|
||||
var selection = g_Selection.toList();
|
||||
|
||||
// Use the preview to check it's a valid build location
|
||||
var ok = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||
"template": placementEntity,
|
||||
"x": placementPosition.x,
|
||||
"z": placementPosition.z,
|
||||
"angle": placementAngle
|
||||
});
|
||||
if (!ok)
|
||||
if (!updateBuildingPlacementPreview())
|
||||
{
|
||||
// invalid location - don't build it
|
||||
// TODO: play a sound?
|
||||
@ -561,13 +557,15 @@ function handleInputBeforeGui(ev, hoveredObject)
|
||||
placementAngle = defaultPlacementAngle;
|
||||
}
|
||||
|
||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
|
||||
"template": placementEntity,
|
||||
"x": placementPosition.x,
|
||||
"z": placementPosition.z,
|
||||
"angle": placementAngle
|
||||
"z": placementPosition.z
|
||||
});
|
||||
|
||||
if (snapData.snapped)
|
||||
placementAngle = snapData.angle;
|
||||
|
||||
updateBuildingPlacementPreview();
|
||||
break;
|
||||
|
||||
case "mousebuttonup":
|
||||
@ -821,12 +819,15 @@ function handleInputAfterGui(ev)
|
||||
{
|
||||
case "mousemotion":
|
||||
placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
|
||||
"template": placementEntity,
|
||||
"x": placementPosition.x,
|
||||
"z": placementPosition.z,
|
||||
"angle": placementAngle
|
||||
"z": placementPosition.z
|
||||
});
|
||||
if (snapData.snapped)
|
||||
placementAngle = snapData.angle;
|
||||
|
||||
updateBuildingPlacementPreview();
|
||||
|
||||
return false; // continue processing mouse motion
|
||||
|
||||
@ -855,21 +856,11 @@ function handleInputAfterGui(ev)
|
||||
{
|
||||
case "session.rotate.cw":
|
||||
placementAngle += rotation_step;
|
||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||
"template": placementEntity,
|
||||
"x": placementPosition.x,
|
||||
"z": placementPosition.z,
|
||||
"angle": placementAngle
|
||||
});
|
||||
updateBuildingPlacementPreview();
|
||||
break;
|
||||
case "session.rotate.ccw":
|
||||
placementAngle -= rotation_step;
|
||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||
"template": placementEntity,
|
||||
"x": placementPosition.x,
|
||||
"z": placementPosition.z,
|
||||
"angle": placementAngle
|
||||
});
|
||||
updateBuildingPlacementPreview();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,63 +1,102 @@
|
||||
function BuildLimits() {}
|
||||
|
||||
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'>" +
|
||||
"<ref name='positiveDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='Limits'>" +
|
||||
"<zeroOrMore>" +
|
||||
"<element>" +
|
||||
"<element a:help='Specifies a category of building on which to apply this limit. See BuildRestrictions for list of categories.'>" +
|
||||
"<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>" +
|
||||
"</zeroOrMore>" +
|
||||
"</element>";
|
||||
|
||||
/*
|
||||
* TODO: Use an inheriting player_{civ}.xml template for civ-specific limits
|
||||
*/
|
||||
|
||||
BuildLimits.prototype.Init = function()
|
||||
{
|
||||
this.limits = [];
|
||||
this.unitCount = [];
|
||||
this.limit = [];
|
||||
this.count = [];
|
||||
for (var category in this.template.Limits)
|
||||
{
|
||||
this.limits[category] = this.template.Limits[category];
|
||||
this.unitCount[category] = 0;
|
||||
this.limit[category] = this.template.Limits[category];
|
||||
this.count[category] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
BuildLimits.prototype.IncrementCount = function(category)
|
||||
{
|
||||
if (this.unitCount[category] !== undefined)
|
||||
this.unitCount[category]++;
|
||||
if (this.count[category] !== undefined)
|
||||
{
|
||||
this.count[category]++;
|
||||
}
|
||||
};
|
||||
|
||||
BuildLimits.prototype.DecrementCount = function(category)
|
||||
{
|
||||
if (this.unitCount[category] !== undefined)
|
||||
this.unitCount[category]--;
|
||||
if (this.count[category] !== undefined)
|
||||
{
|
||||
this.count[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 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);
|
||||
cmpGUIInterface.PushNotification(notification);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
else
|
||||
else if (this.count[category] >= this.limit[category])
|
||||
{
|
||||
//Check here for terrain
|
||||
return true;
|
||||
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||
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);
|
||||
|
@ -1,19 +1,37 @@
|
||||
function BuildRestrictions() {}
|
||||
|
||||
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>" +
|
||||
"<value>standard</value>" +
|
||||
"<value>settlement</value>" +
|
||||
"</choice>" + // TODO: add special types for fields, docks, walls
|
||||
"</element>" +
|
||||
"<element name='Territory'>" +
|
||||
"<choice>" +
|
||||
"<value>all</value>" +
|
||||
"<value>allied</value>" +
|
||||
"<value>land</value>" +
|
||||
"<value>shore</value>" +
|
||||
"</choice>" +
|
||||
"</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>" +
|
||||
"<value>CivilCentre</value>" +
|
||||
"<value>House</value>" +
|
||||
@ -32,43 +50,183 @@ BuildRestrictions.prototype.Schema =
|
||||
"<value>Resource</value>" +
|
||||
"<value>Special</value>" +
|
||||
"</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: the vague plan for Category is to add some BuildLimitManager which
|
||||
* specifies the limit per category, and which can determine whether you're
|
||||
* 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.Init = function()
|
||||
{
|
||||
this.territories = this.template.Territory.split(/\s+/);
|
||||
};
|
||||
|
||||
BuildRestrictions.prototype.OnOwnershipChanged = function(msg)
|
||||
{
|
||||
// This automatically updates building counts
|
||||
if (this.template.Category)
|
||||
{
|
||||
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
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);
|
||||
|
@ -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
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
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);
|
||||
|
@ -468,16 +468,22 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||
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
|
||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
var visible = (cmpRangeManager.GetLosVisibility(ent, player) == "visible");
|
||||
|
||||
var ok = (!colliding && visible);
|
||||
var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player) == "visible");
|
||||
var validPlacement = false;
|
||||
|
||||
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
|
||||
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
|
||||
if (cmpVisual)
|
||||
@ -494,6 +500,71 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||
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)
|
||||
{
|
||||
// Ignore if no entity was passed
|
||||
@ -583,6 +654,7 @@ var exposedFunctions = {
|
||||
"SetStatusBars": 1,
|
||||
"DisplayRallyPoint": 1,
|
||||
"SetBuildingPlacementPreview": 1,
|
||||
"GetFoundationSnapData": 1,
|
||||
"PlaySound": 1,
|
||||
"FindIdleUnit": 1,
|
||||
|
||||
|
@ -217,7 +217,11 @@ TrainingQueue.prototype.SpawnUnits = function(templateName, count, metadata)
|
||||
// What should we do here?
|
||||
// For now, just move the unit into the middle of the building where it'll probably get stuck
|
||||
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);
|
||||
|
@ -15,11 +15,21 @@
|
||||
<ship>
|
||||
<MinWaterDepth>1</MinWaterDepth>
|
||||
</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>
|
||||
<MaxWaterDepth>0</MaxWaterDepth>
|
||||
<MinShoreDistance>2.0</MinShoreDistance>
|
||||
<MaxTerrainSlope>1.0</MaxTerrainSlope>
|
||||
</building-land>
|
||||
<building-shore>
|
||||
<MaxShoreDistance>3.0</MaxShoreDistance>
|
||||
<MaxTerrainSlope>1.5</MaxTerrainSlope>
|
||||
</building-shore>
|
||||
|
||||
</PassabilityClasses>
|
||||
|
||||
|
@ -74,7 +74,7 @@ function ProcessCommand(player, cmd)
|
||||
|
||||
case "returnresource":
|
||||
// Check dropsite is owned by player
|
||||
if (IsOwnedByPlayer(cmd.target, player))
|
||||
if (IsOwnedByPlayer(player, cmd.target))
|
||||
{
|
||||
var entities = FilterEntityList(cmd.entities, player, controlAllUnits);
|
||||
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)
|
||||
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
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
cmpPosition.JumpTo(cmd.x, cmd.z);
|
||||
cmpPosition.SetYRotation(cmd.angle);
|
||||
|
||||
// Check whether it's obstructed by other entities
|
||||
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
|
||||
if (cmpObstruction && cmpObstruction.CheckFoundationCollisions())
|
||||
// Check whether it's obstructed by other entities or invalid terrain
|
||||
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
|
||||
if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement(player))
|
||||
{
|
||||
// TODO: report error to player (the building site was obstructed)
|
||||
print("Building site was obstructed\n");
|
||||
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
||||
cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was obstructed" });
|
||||
|
||||
// Remove the foundation because the construction was aborted
|
||||
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;
|
||||
}
|
||||
|
||||
@ -167,19 +182,10 @@ function ProcessCommand(player, cmd)
|
||||
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);
|
||||
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
|
||||
{
|
||||
// TODO: report error to player (they ran out of resources)
|
||||
Engine.DestroyEntity(ent);
|
||||
break;
|
||||
}
|
||||
@ -564,15 +570,6 @@ function CanMoveEntsIntoFormation(ents, formationName)
|
||||
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
|
||||
* 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)
|
||||
{
|
||||
return (IsOwnedByPlayer(entity, player) || controlAll);
|
||||
return (IsOwnedByPlayer(player, entity) || controlAll);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,6 +230,15 @@ function IsOwnedByAllyOfEntity(entity, target)
|
||||
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
|
||||
*/
|
||||
@ -278,5 +287,6 @@ Engine.RegisterGlobal("LoadPlayerSettings", LoadPlayerSettings);
|
||||
Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface);
|
||||
Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface);
|
||||
Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity);
|
||||
Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer);
|
||||
Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer);
|
||||
Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer);
|
||||
|
@ -3,6 +3,9 @@
|
||||
<BuildLimits>
|
||||
<LimitMultiplier>1.0</LimitMultiplier>
|
||||
<Limits>
|
||||
<CivilCentre/>
|
||||
<ScoutTower>20</ScoutTower>
|
||||
<Fortress>5</Fortress>
|
||||
</Limits>
|
||||
</BuildLimits>
|
||||
<Player/>
|
||||
|
@ -10,8 +10,8 @@
|
||||
<GarrisonArrowMultiplier>0</GarrisonArrowMultiplier>
|
||||
</BuildingAI>
|
||||
<BuildRestrictions>
|
||||
<PlacementType>standard</PlacementType>
|
||||
<Territory>allied</Territory>
|
||||
<PlacementType>land</PlacementType>
|
||||
<Territory>own</Territory>
|
||||
</BuildRestrictions>
|
||||
<Cost>
|
||||
<Population>0</Population>
|
||||
|
@ -6,9 +6,12 @@
|
||||
<Crush>10.0</Crush>
|
||||
</Armour>
|
||||
<BuildRestrictions>
|
||||
<PlacementType>settlement</PlacementType>
|
||||
<Territory>all</Territory>
|
||||
<Territory>own neutral</Territory>
|
||||
<Category>CivilCentre</Category>
|
||||
<Distance>
|
||||
<FromCategory>CivilCentre</FromCategory>
|
||||
<MinDistance>150</MinDistance>
|
||||
</Distance>
|
||||
</BuildRestrictions>
|
||||
<Cost>
|
||||
<PopulationBonus>20</PopulationBonus>
|
||||
|
@ -7,7 +7,6 @@
|
||||
</Armour>
|
||||
<BuildRestrictions>
|
||||
<Category>Mill</Category>
|
||||
<Territory>all</Territory>
|
||||
</BuildRestrictions>
|
||||
<Cost>
|
||||
<BuildTime>80</BuildTime>
|
||||
|
@ -6,6 +6,8 @@
|
||||
<Crush>20.0</Crush>
|
||||
</Armour>
|
||||
<BuildRestrictions>
|
||||
<Territory>own ally neutral</Territory>
|
||||
<PlacementType>shore</PlacementType>
|
||||
<Category>Dock</Category>
|
||||
</BuildRestrictions>
|
||||
<Cost>
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -20,10 +20,13 @@
|
||||
#include "simulation2/system/Component.h"
|
||||
#include "ICmpFootprint.h"
|
||||
|
||||
#include "ICmpObstruction.h"
|
||||
#include "ICmpObstructionManager.h"
|
||||
#include "ICmpPosition.h"
|
||||
#include "simulation2/components/ICmpObstruction.h"
|
||||
#include "simulation2/components/ICmpObstructionManager.h"
|
||||
#include "simulation2/components/ICmpPathfinder.h"
|
||||
#include "simulation2/components/ICmpPosition.h"
|
||||
#include "simulation2/components/ICmpUnitMotion.h"
|
||||
#include "simulation2/MessageTypes.h"
|
||||
#include "graphics/Terrain.h" // For CELL_SIZE
|
||||
#include "maths/FixedVector2D.h"
|
||||
|
||||
class CCmpFootprint : public ICmpFootprint
|
||||
@ -125,7 +128,7 @@ public:
|
||||
// because the footprint might be inside the obstruction, but it hopefully gives us a nicer
|
||||
// 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());
|
||||
if (cmpPosition.null() || !cmpPosition->IsInWorld())
|
||||
@ -146,30 +149,46 @@ public:
|
||||
}
|
||||
// else use zero radius
|
||||
|
||||
// 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);
|
||||
// Get passability class from UnitMotion
|
||||
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();
|
||||
entity_angle_t initialAngle = cmpPosition->GetRotation().Y;
|
||||
|
||||
// Max spawning distance in tiles
|
||||
const size_t maxSpawningDistance = 4;
|
||||
|
||||
if (m_Shape == CIRCLE)
|
||||
{
|
||||
entity_pos_t radius = m_Size0 + clearance;
|
||||
|
||||
// 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]
|
||||
// Expand outwards from foundation
|
||||
for (size_t dist = 0; dist <= maxSpawningDistance; ++dist)
|
||||
{
|
||||
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;
|
||||
sincos_approx(angle, s, c);
|
||||
// Try equally-spaced points around the circle in alternating directions, starting from the front
|
||||
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
|
||||
if (!cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Z, spawnedRadius, NULL))
|
||||
return pos; // this position is okay, so return it
|
||||
CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius));
|
||||
|
||||
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
|
||||
@ -177,47 +196,54 @@ public:
|
||||
fixed 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
|
||||
const ssize_t numPoints = 9;
|
||||
// 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);
|
||||
|
||||
// Compute the direction and length of the current edge
|
||||
CFixedVector2D dir;
|
||||
fixed sx, sy;
|
||||
switch (edge)
|
||||
for (size_t edge = 0; edge < 4; ++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));
|
||||
// Try equally-spaced points along the edge in alternating directions, starting from the middle
|
||||
const ssize_t numPoints = 9 + 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]
|
||||
{
|
||||
CFixedVector2D pos (center + dir*i);
|
||||
// Compute the direction and length of the current edge
|
||||
CFixedVector2D dir;
|
||||
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
|
||||
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
|
||||
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]
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,8 @@
|
||||
#include "simulation2/system/Component.h"
|
||||
#include "ICmpObstruction.h"
|
||||
|
||||
#include "ICmpObstructionManager.h"
|
||||
#include "ICmpPosition.h"
|
||||
#include "simulation2/components/ICmpObstructionManager.h"
|
||||
#include "simulation2/components/ICmpPosition.h"
|
||||
|
||||
#include "simulation2/MessageTypes.h"
|
||||
|
||||
@ -328,7 +328,7 @@ public:
|
||||
return entity_pos_t::Zero();
|
||||
}
|
||||
|
||||
virtual bool CheckFoundationCollisions()
|
||||
virtual bool CheckFoundation(std::string className)
|
||||
{
|
||||
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
|
||||
if (cmpPosition.null())
|
||||
@ -339,17 +339,20 @@ public:
|
||||
|
||||
CFixedVector2D pos = cmpPosition->GetPosition2D();
|
||||
|
||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
|
||||
if (cmpObstructionManager.null())
|
||||
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
|
||||
if (cmpPathfinder.null())
|
||||
return false; // error
|
||||
|
||||
// Get passability class
|
||||
ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(className);
|
||||
|
||||
// Ignore collisions with self, or with non-foundation-blocking obstructions
|
||||
SkipTagFlagsObstructionFilter filter(m_Tag, ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
|
||||
|
||||
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
|
||||
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()
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "ps/Profile.h"
|
||||
#include "renderer/Scene.h"
|
||||
#include "simulation2/MessageTypes.h"
|
||||
#include "simulation2/components/ICmpObstruction.h"
|
||||
#include "simulation2/components/ICmpObstructionManager.h"
|
||||
#include "simulation2/components/ICmpWaterManager.h"
|
||||
#include "simulation2/serialization/SerializeTemplates.h"
|
||||
@ -372,8 +373,98 @@ void CCmpPathfinder::UpdateGrid()
|
||||
|
||||
CmpPtr<ICmpWaterManager> cmpWaterMan(GetSimContext(), SYSTEM_ENTITY);
|
||||
|
||||
// TOOD: these bits should come from ICmpTerrain
|
||||
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 i = 0; i < m_MapSize; ++i)
|
||||
@ -385,7 +476,7 @@ void CCmpPathfinder::UpdateGrid()
|
||||
|
||||
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;
|
||||
if (!cmpWaterMan.null())
|
||||
@ -395,6 +486,8 @@ void CCmpPathfinder::UpdateGrid()
|
||||
|
||||
fixed slope = terrain.GetSlopeFixed(i, j);
|
||||
|
||||
fixed shoredist = fixed::FromInt(shoreGrid.get(i, j));
|
||||
|
||||
if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
|
||||
t |= 1;
|
||||
|
||||
@ -411,7 +504,7 @@ void CCmpPathfinder::UpdateGrid()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
@ -96,11 +96,22 @@ public:
|
||||
m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed();
|
||||
else
|
||||
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;
|
||||
@ -108,6 +119,8 @@ private:
|
||||
fixed m_MinDepth;
|
||||
fixed m_MaxDepth;
|
||||
fixed m_MaxSlope;
|
||||
fixed m_MinShore;
|
||||
fixed m_MaxShore;
|
||||
};
|
||||
|
||||
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 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();
|
||||
|
||||
void ProcessLongRequests(const std::vector<AsyncLongPathRequest>& longRequests);
|
||||
|
@ -453,6 +453,7 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
|
||||
permittedComponentTypes.insert("Footprint");
|
||||
permittedComponentTypes.insert("Obstruction");
|
||||
permittedComponentTypes.insert("Decay");
|
||||
permittedComponentTypes.insert("BuildRestrictions");
|
||||
|
||||
// Need these for the Actor Viewer:
|
||||
permittedComponentTypes.insert("Attack");
|
||||
|
@ -167,6 +167,8 @@ public:
|
||||
return *m_Territories;
|
||||
}
|
||||
|
||||
virtual int32_t GetOwner(entity_pos_t x, entity_pos_t z);
|
||||
|
||||
// To support lazy updates of territory render data,
|
||||
// we maintain a DirtyID here and increment it whenever territories change;
|
||||
// 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]);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
|
@ -20,15 +20,15 @@
|
||||
#include "simulation2/system/Component.h"
|
||||
#include "ICmpUnitMotion.h"
|
||||
|
||||
#include "ICmpObstruction.h"
|
||||
#include "ICmpObstructionManager.h"
|
||||
#include "ICmpOwnership.h"
|
||||
#include "ICmpPosition.h"
|
||||
#include "ICmpPathfinder.h"
|
||||
#include "ICmpRangeManager.h"
|
||||
#include "simulation2/MessageTypes.h"
|
||||
#include "simulation2/components/ICmpObstruction.h"
|
||||
#include "simulation2/components/ICmpObstructionManager.h"
|
||||
#include "simulation2/components/ICmpOwnership.h"
|
||||
#include "simulation2/components/ICmpPosition.h"
|
||||
#include "simulation2/components/ICmpPathfinder.h"
|
||||
#include "simulation2/components/ICmpRangeManager.h"
|
||||
#include "simulation2/helpers/Geometry.h"
|
||||
#include "simulation2/helpers/Render.h"
|
||||
#include "simulation2/MessageTypes.h"
|
||||
#include "simulation2/serialization/SerializeTemplates.h"
|
||||
|
||||
#include "graphics/Overlay.h"
|
||||
@ -122,8 +122,8 @@ public:
|
||||
bool m_FormationController;
|
||||
fixed m_WalkSpeed; // in metres per second
|
||||
fixed m_RunSpeed;
|
||||
u8 m_PassClass;
|
||||
u8 m_CostClass;
|
||||
ICmpPathfinder::pass_class_t m_PassClass;
|
||||
ICmpPathfinder::cost_class_t m_CostClass;
|
||||
|
||||
// Dynamic state:
|
||||
|
||||
@ -400,6 +400,11 @@ public:
|
||||
return m_RunSpeed;
|
||||
}
|
||||
|
||||
virtual ICmpPathfinder::pass_class_t GetPassabilityClass()
|
||||
{
|
||||
return m_PassClass;
|
||||
}
|
||||
|
||||
virtual void SetSpeed(fixed speed)
|
||||
{
|
||||
m_Speed = speed;
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(Obstruction)
|
||||
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_1("SetActive", void, ICmpObstruction, SetActive, bool)
|
||||
DEFINE_INTERFACE_METHOD_1("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool)
|
||||
|
@ -44,14 +44,14 @@ public:
|
||||
/**
|
||||
* Test whether this entity is colliding with any obstruction that are set to
|
||||
* 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
|
||||
* 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;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -22,4 +22,5 @@
|
||||
#include "simulation2/system/InterfaceScripted.h"
|
||||
|
||||
BEGIN_INTERFACE_WRAPPER(TerritoryManager)
|
||||
DEFINE_INTERFACE_METHOD_2("GetOwner", int32_t, ICmpTerritoryManager, GetOwner, entity_pos_t, entity_pos_t)
|
||||
END_INTERFACE_WRAPPER(TerritoryManager)
|
||||
|
@ -18,9 +18,9 @@
|
||||
#ifndef INCLUDED_ICMPTERRITORYMANAGER
|
||||
#define INCLUDED_ICMPTERRITORYMANAGER
|
||||
|
||||
#include "simulation2/system/Interface.h"
|
||||
|
||||
#include "simulation2/helpers/Grid.h"
|
||||
#include "simulation2/system/Interface.h"
|
||||
#include "simulation2/components/ICmpPosition.h"
|
||||
|
||||
class ICmpTerritoryManager : public IComponent
|
||||
{
|
||||
@ -29,6 +29,13 @@ public:
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
|
@ -86,6 +86,11 @@ public:
|
||||
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)
|
||||
{
|
||||
m_Script.CallVoid("SetUnitRadius", radius);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2010 Wildfire Games.
|
||||
/* Copyright (C) 2011 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -20,7 +20,8 @@
|
||||
|
||||
#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.
|
||||
@ -92,6 +93,11 @@ public:
|
||||
*/
|
||||
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.
|
||||
* Bad things may happen if this entity has an active Obstruction component with a larger
|
||||
|
Loading…
Reference in New Issue
Block a user