Adds notification tooltips for building placement, fixes #921.

Tweaks info tooltip borders and padding to improve readability.

This was SVN commit r13191.
This commit is contained in:
historic_bruno 2013-02-24 00:12:41 +00:00
parent 6e6a25dbce
commit ca92e50048
17 changed files with 261 additions and 153 deletions

View File

@ -74,7 +74,17 @@
<image
backcolor="0 0 0 191"
size="0 0 100% 100%"
border="false"
border="true"
bordercolor="white"
/>
</sprite>
<sprite name="BackgroundErrorTooltip">
<image
backcolor="0 0 0 191"
size="0 0 100% 100%"
border="true"
bordercolor="red"
/>
</sprite>

View File

@ -82,16 +82,20 @@ function updateCursorAndTooltip()
if (!tooltipSet)
informationTooltip.hidden = true;
var wallDragTooltip = getGUIObjectByName("wallDragTooltip");
if (placementSupport.wallDragTooltip)
var placementTooltip = getGUIObjectByName("placementTooltip");
if (placementSupport.tooltipMessage)
{
wallDragTooltip.caption = placementSupport.wallDragTooltip;
wallDragTooltip.hidden = false;
if (placementSupport.tooltipError)
placementTooltip.sprite = "BackgroundErrorTooltip";
else
placementTooltip.sprite = "BackgroundInformationTooltip";
placementTooltip.caption = placementSupport.tooltipMessage;
placementTooltip.hidden = false;
}
else
{
wallDragTooltip.caption = "";
wallDragTooltip.hidden = true;
placementTooltip.caption = "";
placementTooltip.hidden = true;
}
}
@ -105,13 +109,18 @@ function updateBuildingPlacementPreview()
{
if (placementSupport.template && placementSupport.position)
{
return Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
var result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
"template": placementSupport.template,
"x": placementSupport.position.x,
"z": placementSupport.position.z,
"angle": placementSupport.angle,
"actorSeed": placementSupport.actorSeed
});
// Show placement info tooltip if invalid position
placementSupport.tooltipError = !result.success;
placementSupport.tooltipMessage = result.success ? "" : result.message;
return result.success;
}
}
else if (placementSupport.mode === "wall")
@ -862,10 +871,10 @@ function handleInputBeforeGui(ev, hoveredObject)
if (result && result.cost)
{
placementSupport.wallDragTooltip = getEntityCostTooltip(result);
placementSupport.tooltipMessage = getEntityCostTooltip(result);
var neededResources = Engine.GuiInterfaceCall("GetNeededResources", result.cost);
if (neededResources)
placementSupport.wallDragTooltip += getNeededResourcesTooltip(neededResources);
placementSupport.tooltipMessage += getNeededResourcesTooltip(neededResources);
}
break;
@ -892,7 +901,7 @@ function handleInputBeforeGui(ev, hoveredObject)
}
else
{
placementSupport.wallDragTooltip = "Cannot build wall here!";
placementSupport.tooltipMessage = "Cannot build wall here!";
}
updateBuildingPlacementPreview();

View File

@ -13,11 +13,12 @@ PlacementSupport.prototype.Reset = function()
this.mode = null;
this.position = null;
this.template = null;
this.tooltipMessage = ""; // tooltip text to show while the user is placing a building
this.tooltipError = false;
this.wallSet = null; // maps types of wall pieces ("tower", "long", "short", ...) to template names
this.wallSnapEntities = null; // list of candidate entities to snap the starting and (!) ending positions to when building walls
this.wallEndPosition = null;
this.wallSnapEntitiesIncludeOffscreen = false; // should the next update of the snap candidate list include offscreen towers?
this.wallDragTooltip = null; // tooltip text while the user is draggin the wall. Used to indicate the current cost to build the wall.
this.SetDefaultAngle();
this.RandomizeActorSeed();

View File

@ -688,9 +688,9 @@
<object name="informationTooltip" type="tooltip" independent="true" style="informationTooltip"/>
<!-- ================================ ================================ -->
<!-- Wall-dragging tooltip. Shows the total cost of building a wall while the player is dragging it. -->
<!-- Building placement info tooltip -->
<!-- ================================ ================================ -->
<object name="wallDragTooltip" type="tooltip" independent="true" style="informationTooltip"/>
<object name="placementTooltip" type="tooltip" independent="true" style="informationTooltip"/>
<!-- ================================ ================================ -->
<!-- START of BOTTOM PANEL -->

View File

@ -206,7 +206,7 @@
<style name="informationTooltip"
anchor="top"
buffer_zone="4"
buffer_zone="6"
font="serif-bold-14"
maxwidth="300"
offset="16 32"

View File

@ -71,11 +71,50 @@ BuildRestrictions.prototype.Init = function()
};
/**
* Returns true iff this entity can be built at its current position.
* Checks whether building placement is valid
* 1. Visibility is not hidden (may be fogged or visible)
* 2. Check foundation
* a. Doesn't obstruct foundation-blocking entities
* b. On valid terrain, based on passability class
* 3. Territory type is allowed
* 4. Dock is on shoreline and facing into water
* 5. Distance constraints satisfied
*
* Returns result object:
* {
* "success": true iff the placement is valid, else false
* "message": message to display in UI for invalid placement, else empty string
* }
*/
BuildRestrictions.prototype.CheckPlacement = function()
{
// TODO: Return error code for invalid placement, which can be handled by the UI
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";
var result = {
"success": false,
"message": name+" cannot be built due to unknown error",
};
// TODO: AI has no visibility info
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (!cmpPlayer.IsAI())
{
// Check whether it's in a visible or fogged region
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
// which would show them as hidden instead of fogged
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpRangeManager || !cmpOwnership)
return result; // Fail
var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner(), true) != "hidden");
if (!explored)
{
result.message = name+" cannot be built in unexplored area";
return result; // Fail
}
}
// Check obstructions and terrain passability
var passClassName = "";
@ -91,20 +130,35 @@ BuildRestrictions.prototype.CheckPlacement = function()
}
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction || !cmpObstruction.CheckFoundation(passClassName))
if (!cmpObstruction)
return result; // Fail
var ret = cmpObstruction.CheckFoundation(passClassName);
if (ret != "success")
{
return false; // Fail
switch (ret)
{
case "fail_error":
case "fail_no_obstruction":
error("CheckPlacement: Error returned from CheckFoundation");
break;
case "fail_obstructs_foundation":
result.message = name+" cannot be built on another building or resource";
break;
case "fail_terrain_class":
// TODO: be more specific and/or list valid terrain?
result.message = name+" cannot be built on invalid terrain";
}
return result; // Fail
}
// Check territory restrictions
var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!(cmpTerritoryManager && cmpPlayer && cmpPosition && cmpPosition.IsInWorld()))
{
return false; // Fail
}
return result; // Fail
var pos = cmpPosition.GetPosition2D();
var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
var isOwn = (tileOwner == cmpPlayer.GetPlayerID());
@ -112,15 +166,26 @@ BuildRestrictions.prototype.CheckPlacement = function()
var isAlly = !isOwn && cmpPlayer.IsAlly(tileOwner);
// We count neutral players as enemies, so you can't build in their territory.
var isEnemy = !isNeutral && (cmpPlayer.IsEnemy(tileOwner) || cmpPlayer.IsNeutral(tileOwner));
if ((isAlly && !this.HasTerritory("ally"))
|| (isOwn && !this.HasTerritory("own"))
|| (isNeutral && !this.HasTerritory("neutral"))
|| (isEnemy && !this.HasTerritory("enemy")))
var territoryFail = true;
var territoryType = "";
if (isAlly && !this.HasTerritory("ally"))
territoryType = "ally";
else if (isOwn && !this.HasTerritory("own"))
territoryType = "own";
else if (isNeutral && !this.HasTerritory("neutral"))
territoryType = "neutral";
else if (isEnemy && !this.HasTerritory("enemy"))
territoryType = "enemy";
else
territoryFail = false
if (territoryFail)
{
return false; // Fail
result.message = name+" cannot be built in "+territoryType+" territory. Valid territories: " + this.GetTerritories().join(", ");
return result; // Fail
}
// Check special requirements
if (this.template.Category == "Dock")
{
@ -130,39 +195,32 @@ BuildRestrictions.prototype.CheckPlacement = function()
// so it's correct even if the criteria changes for these units
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
if (!cmpFootprint)
{
return false; // Fail
}
return result; // 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
}
return result; // Fail
var ang = cmpPosition.GetRotation().y;
var sz = halfSize * Math.sin(ang);
var cz = halfSize * Math.cos(ang);
if ((cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz) - cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz)) < 1.0 // front
|| (cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz) - cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz)) > 2.0) // back
{
return false; // Fail
result.message = name+" must be built on a valid shoreline";
return result; // Fail
}
}
// Check distance restriction
if (this.template.Distance)
{
@ -189,16 +247,23 @@ BuildRestrictions.prototype.CheckPlacement = function()
}
}
}
if ((this.template.Distance.MinDistance && nearest < this.template.Distance.MinDistance)
|| (this.template.Distance.MaxDistance && nearest > this.template.Distance.MaxDistance))
if (this.template.Distance.MinDistance && nearest < +this.template.Distance.MinDistance)
{
return false; // Fail
result.message = name+" too close to a "+this.GetCategory()+", must be at least "+ +this.template.Distance.MinDistance+" units away";
return result; // Fail
}
else if (this.template.Distance.MaxDistance && nearest > +this.template.Distance.MaxDistance)
{
result.message = name+" too far away from a "+this.GetCategory()+", must be within "+ +this.template.Distance.MaxDistance+" units";
return result; // Fail
}
}
// Success
return true;
result.success = true;
result.message = "";
return result;
};
BuildRestrictions.prototype.GetCategory = function()

View File

@ -798,10 +798,19 @@ GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
*
* Returns true if the placement is okay (everything is valid and the entity is not obstructed by others).
* Returns result object from CheckPlacement:
* {
* "success": true iff the placement is valid, else false
* "message": message to display in UI for invalid placement, else empty string
* }
*/
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
var result = {
"success": false,
"message": "",
}
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
{
@ -832,26 +841,15 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
pos.SetYRotation(cmd.angle);
}
// Check whether it's in a visible or fogged region
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
// which would show them as hidden instead of fogged
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden");
var validPlacement = false;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
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());
}
var ok = (visible && validPlacement);
// Check whether building placement is valid
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
error("cmpBuildRestrictions not defined");
else
result = cmpBuildRestrictions.CheckPlacement();
// Set it to a red shade if this is an invalid location
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
@ -859,17 +857,15 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
if (cmd.actorSeed !== undefined)
cmpVisual.SetActorSeed(cmd.actorSeed);
if (!ok)
if (!result.success)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
return ok;
}
return false;
return result;
};
/**
@ -1317,7 +1313,8 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
continue;
}
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement());
// TODO: Handle results of CheckPlacement
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success);
// If a wall piece has two control groups, it's likely a segment that spans
// between two existing towers. To avoid placing a duplicate wall segment,

View File

@ -133,4 +133,9 @@ Identity.prototype.GetSelectionGroupName = function()
return (this.template.SelectionGroupName || "");
};
Identity.prototype.GetGenericName = function()
{
return this.template.GenericName;
};
Engine.RegisterComponentType(IID_Identity, "Identity", Identity);

View File

@ -601,22 +601,28 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether it's obstructed by other entities or invalid terrain
// Check whether building placement is valid
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions || !cmpBuildRestrictions.CheckPlacement())
if (cmpBuildRestrictions)
{
if (g_DebugCommands)
var ret = cmpBuildRestrictions.CheckPlacement();
if (!ret.success)
{
warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd));
if (g_DebugCommands)
{
warn("Invalid command: build restrictions check failed for player "+player+": "+uneval(cmd));
}
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({ "player": player, "message": ret.message });
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
return false;
}
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);
return false;
}
else
error("cmpBuildRestrictions not defined");
// Check entity limits
var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
@ -647,29 +653,6 @@ function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd)
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
}
// TODO: AI has no visibility info
if (!cmpPlayer.IsAI())
{
// Check whether it's in a visible or fogged region
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
// which would show them as hidden instead of fogged
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden");
if (!visible)
{
if (g_DebugCommands)
{
warn("Invalid command: foundation visibility check failed for player "+player+": "+uneval(cmd));
}
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({ "player": player, "message": "Building site was not visible" });
Engine.DestroyEntity(ent);
return false;
}
}
// We need the cost after tech modifications

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -186,7 +186,7 @@ public:
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))
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return pos; // this position is okay, so return it
}
}
@ -241,7 +241,7 @@ public:
CFixedVector2D pos (center + dir*i);
SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass))
if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS)
return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it
}
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -478,26 +478,26 @@ public:
return m_ControlPersist;
}
virtual bool CheckFoundation(std::string className)
virtual EFoundationCheck CheckFoundation(std::string className)
{
CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
if (!cmpPosition)
return false; // error
return FOUNDATION_CHECK_FAIL_ERROR; // error
if (!cmpPosition->IsInWorld())
return false; // no obstruction
return FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION; // no obstruction
CFixedVector2D pos = cmpPosition->GetPosition2D();
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
if (!cmpPathfinder)
return false; // error
return FOUNDATION_CHECK_FAIL_ERROR; // error
// required precondition to use SkipControlGroupsRequireFlagObstructionFilter
if (m_ControlGroup == INVALID_ENTITY)
{
LOGERROR(L"[CmpObstruction] Cannot test for foundation obstructions; primary control group must be valid");
return false;
return FOUNDATION_CHECK_FAIL_ERROR;
}
// Get passability class
@ -509,11 +509,9 @@ public:
SkipControlGroupsRequireFlagObstructionFilter filter(m_ControlGroup, m_ControlGroup2,
ICmpObstructionManager::FLAG_BLOCK_FOUNDATION);
if (m_Type == STATIC)
return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
else if (m_Type == UNIT)
if (m_Type == UNIT)
return cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, m_Size0, passClass);
else
else
return cmpPathfinder->CheckBuildingPlacement(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, GetEntityId(), passClass);
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -656,16 +656,16 @@ void CCmpPathfinder::ProcessSameTurnMoves()
}
}
bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter,
ICmpObstruction::EFoundationCheck 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)
return false;
return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR;
if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL))
return false;
return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION;
// Test against terrain:
@ -680,36 +680,33 @@ bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter,
{
if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
{
return false;
return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS;
}
}
}
return true;
return ICmpObstruction::FOUNDATION_CHECK_SUCCESS;
}
bool CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter,
ICmpObstruction::EFoundationCheck 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)
return false;
return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR;
if (cmpObstructionManager->TestStaticShape(filter, x, z, a, w, h, NULL))
return false;
return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION;
// Test against terrain:
UpdateGrid();
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
if (!cmpObstruction)
return false;
ICmpObstructionManager::ObstructionSquare square;
if (!cmpObstruction->GetObstructionSquare(square))
return false;
CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), id);
if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(square))
return ICmpObstruction::FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION;
// Expand bounds by 1/sqrt(2) tile (multiply by TERRAIN_TILE_SIZE since we want world coordinates)
entity_pos_t expand = entity_pos_t::FromInt(2).Sqrt().Multiply(entity_pos_t::FromInt(TERRAIN_TILE_SIZE / 2));
@ -728,10 +725,10 @@ bool CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter
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 ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS;
}
}
}
return true;
return ICmpObstruction::FOUNDATION_CHECK_SUCCESS;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -260,9 +260,9 @@ 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 ICmpObstruction::EFoundationCheck 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 ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass);
virtual void FinishAsyncRequests();

View File

@ -501,6 +501,7 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i
// and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
// to this list should be carefully considered
std::set<std::string> permittedComponentTypes;
permittedComponentTypes.insert("Identity");
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -21,9 +21,33 @@
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/system/SimContext.h"
std::string ICmpObstruction::CheckFoundation_wrapper(std::string className)
{
EFoundationCheck check = CheckFoundation(className);
switch (check)
{
case FOUNDATION_CHECK_SUCCESS:
return "success";
case FOUNDATION_CHECK_FAIL_ERROR:
return "fail_error";
case FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION:
return "fail_no_obstruction";
case FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION:
return "fail_obstructs_foundation";
case FOUNDATION_CHECK_FAIL_TERRAIN_CLASS:
return "fail_terrain_class";
default:
debug_warn(L"Unexpected result from CheckFoundation");
return "";
}
}
BEGIN_INTERFACE_WRAPPER(Obstruction)
DEFINE_INTERFACE_METHOD_0("GetUnitRadius", entity_pos_t, ICmpObstruction, GetUnitRadius)
DEFINE_INTERFACE_METHOD_1("CheckFoundation", bool, ICmpObstruction, CheckFoundation, std::string)
DEFINE_INTERFACE_METHOD_1("CheckFoundation", std::string, ICmpObstruction, CheckFoundation_wrapper, std::string)
DEFINE_INTERFACE_METHOD_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation)
DEFINE_INTERFACE_METHOD_2("GetEntityCollisions", std::vector<entity_id_t>, ICmpObstruction, GetEntityCollisions, bool, bool)
DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2012 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -30,6 +30,14 @@ class ICmpObstruction : public IComponent
{
public:
enum EFoundationCheck {
FOUNDATION_CHECK_SUCCESS,
FOUNDATION_CHECK_FAIL_ERROR,
FOUNDATION_CHECK_FAIL_NO_OBSTRUCTION,
FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION,
FOUNDATION_CHECK_FAIL_TERRAIN_CLASS
};
virtual ICmpObstructionManager::tag_t GetObstruction() = 0;
/**
@ -47,9 +55,16 @@ public:
* Test whether this entity is colliding with any obstruction that are set to
* block the creation of foundations.
* @param ignoredEntities List of entities to ignore during the test.
* @return true if foundation is valid (not obstructed)
* @return FOUNDATION_CHECK_SUCCESS if check passes, else an EFoundationCheck
* value describing the type of failure.
*/
virtual bool CheckFoundation(std::string className) = 0;
virtual EFoundationCheck CheckFoundation(std::string className) = 0;
/**
* CheckFoundation wrapper for script calls, to return friendly strings instead of an EFoundationCheck.
* @return "success" if check passes, else a string describing the type of failure.
*/
virtual std::string CheckFoundation_wrapper(std::string className);
/**
* Test whether this entity is colliding with any obstructions that share its

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2013 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -20,6 +20,7 @@
#include "simulation2/system/Interface.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/helpers/Position.h"
#include "maths/FixedVector2D.h"
@ -152,16 +153,18 @@ public:
/**
* Check whether a unit placed here is valid and doesn't hit any obstructions
* or impassable terrain.
* Returns true if the placement is okay.
* @return ICmpObstruction::FOUNDATION_CHECK_SUCCESS if the placement is okay, else
* a value describing the type of failure.
*/
virtual bool CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass) = 0;
virtual ICmpObstruction::EFoundationCheck 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.
* @return ICmpObstruction::FOUNDATION_CHECK_SUCCESS if the placement is okay, else
* a value describing the type of failure.
*/
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;
virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass) = 0;
/**
* Toggle the storage and rendering of debug info.