1
0
forked from 0ad/0ad

# Improve support for building placement (rotation, collision detection) in new simulation system

This was SVN commit r7362.
This commit is contained in:
Ykkrosh 2010-03-17 23:16:04 +00:00
parent baead3409e
commit 08487ce246
4 changed files with 192 additions and 36 deletions

View File

@ -9,10 +9,15 @@ var INPUT_NORMAL = 0;
var INPUT_SELECTING = 1;
var INPUT_BANDBOXING = 2;
var INPUT_BUILDING_PLACEMENT = 3;
var INPUT_BUILDING_CLICK = 4;
var INPUT_BUILDING_DRAG = 5;
var inputState = INPUT_NORMAL;
var placementEntity = "";
var defaultPlacementAngle = Math.PI;
var placementAngle;
var placementPosition;
var placementEntity;
var mouseX = 0;
var mouseY = 0;
@ -95,6 +100,7 @@ function determineAction(x, y)
// If we don't do anything more specific, just walk
return {"type": "move"};
}
/*
Selection methods: (not all currently implemented)
@ -113,6 +119,40 @@ Selection methods: (not all currently implemented)
// TODO: it'd probably be nice to have a better state-machine system
var dragStart; // used for remembering mouse coordinates at start of drag operations
function tryPlaceBuilding()
{
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)
{
// invalid location - don't build it
return false;
}
// Remove the preview
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
// Start the construction
Engine.PostNetworkCommand({
"type": "construct",
"template": placementEntity,
"x": placementPosition.x,
"z": placementPosition.z,
"angle": placementAngle,
"entities": selection
});
return true;
}
function handleInputBeforeGui(ev)
{
@ -139,8 +179,8 @@ function handleInputBeforeGui(ev)
switch (ev.type)
{
case "mousemotion":
var x0 = selectionDragStart[0];
var y0 = selectionDragStart[1];
var x0 = dragStart[0];
var y0 = dragStart[1];
var x1 = ev.x;
var y1 = ev.y;
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
@ -158,8 +198,8 @@ function handleInputBeforeGui(ev)
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
var x0 = selectionDragStart[0];
var y0 = selectionDragStart[1];
var x0 = dragStart[0];
var y0 = dragStart[1];
var x1 = ev.x;
var y1 = ev.y;
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
@ -191,13 +231,101 @@ function handleInputBeforeGui(ev)
break;
}
break;
case INPUT_BUILDING_CLICK:
switch (ev.type)
{
case "mousemotion":
// If the mouse moved far enough from the original click location,
// then switch to drag-orientatio mode
var dragDeltaX = ev.x - dragStart[0];
var dragDeltaY = ev.y - dragStart[1];
var maxDragDelta = 16;
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
{
inputState = INPUT_BUILDING_DRAG;
return false;
}
break;
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
if (tryPlaceBuilding())
inputState = INPUT_NORMAL;
else
inputState = INPUT_BUILDING_PLACEMENT;
return true;
}
break;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel building
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
case INPUT_BUILDING_DRAG:
switch (ev.type)
{
case "mousemotion":
var dragDeltaX = ev.x - dragStart[0];
var dragDeltaY = ev.y - dragStart[1];
var maxDragDelta = 16;
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
{
// Rotate in the direction of the mouse
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
placementAngle = Math.atan2(target.x - placementPosition.x, target.z - placementPosition.z);
}
else
{
// If the mouse is near the center, snap back to the default orientation
placementAngle = defaultPlacementAngle;
}
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
"template": placementEntity,
"x": placementPosition.x,
"z": placementPosition.z,
"angle": placementAngle
});
break;
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
if (tryPlaceBuilding())
inputState = INPUT_NORMAL;
else
inputState = INPUT_BUILDING_PLACEMENT;
return true;
}
break;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel building
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
}
return false;
}
var selectionDragStart;
function handleInputAfterGui(ev)
{
// State-machine processing:
@ -215,7 +343,7 @@ function handleInputAfterGui(ev)
case "mousebuttondown":
if (ev.button == SDL_BUTTON_LEFT)
{
selectionDragStart = [ ev.x, ev.y ];
dragStart = [ ev.x, ev.y ];
inputState = INPUT_SELECTING;
return true;
}
@ -260,8 +388,8 @@ function handleInputAfterGui(ev)
{
case "mousemotion":
// If the mouse moved further than a limit, switch to bandbox mode
var dragDeltaX = ev.x - selectionDragStart[0];
var dragDeltaY = ev.y - selectionDragStart[1];
var dragDeltaX = ev.x - dragStart[0];
var dragDeltaY = ev.y - dragStart[1];
var maxDragDelta = 4;
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
{
@ -300,12 +428,11 @@ function handleInputAfterGui(ev)
{
case "mousemotion":
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
var angle = Math.PI;
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
"template": placementEntity,
"x": target.x,
"z": target.z,
"angle": angle
"angle": placementAngle
});
return false; // continue processing mouse motion
@ -313,28 +440,15 @@ function handleInputAfterGui(ev)
case "mousebuttondown":
if (ev.button == SDL_BUTTON_LEFT)
{
var selection = g_Selection.toList();
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
var angle = Math.PI;
// Remove the preview
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
// Start the construction
Engine.PostNetworkCommand({
"type": "construct",
"template": placementEntity,
"x": target.x,
"z": target.z,
"angle": angle,
"entities": selection
});
inputState = INPUT_NORMAL;
placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
dragStart = [ ev.x, ev.y ];
inputState = INPUT_BUILDING_CLICK;
return true;
}
else if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel building
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
inputState = INPUT_NORMAL;
return true;
}
@ -348,5 +462,6 @@ function handleInputAfterGui(ev)
function testBuild(ent)
{
placementEntity = ent;
placementAngle = defaultPlacementAngle;
inputState = INPUT_BUILDING_PLACEMENT;
}

View File

@ -16,6 +16,8 @@ Foundation.prototype.InitialiseConstruction = function(owner, template)
// We need to know the owner in OnDestroy, but at that point the entity has already been
// decoupled from its owner, so we need to remember it in here (and assume it won't change)
this.owner = owner;
this.initialised = true;
};
Foundation.prototype.GetBuildPercentage = function()
@ -27,6 +29,9 @@ Foundation.prototype.OnDestroy = function()
{
// Refund a portion of the construction cost, proportional to the amount of build progress remaining
if (!this.initialised) // this happens if the foundation was destroyed because the player had insufficient resources
return;
if (this.buildProgress == 1.0)
return;

View File

@ -147,6 +147,12 @@ GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
}
};
/**
* Display the building placement preview.
* 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).
*/
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
// See if we're changing template
@ -167,16 +173,35 @@ GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
}
}
// Move the preview into the right location
if (this.placementEntity)
{
// Move the preview into the right location
var pos = Engine.QueryInterface(this.placementEntity[1], IID_Position);
if (pos)
{
pos.JumpTo(cmd.x, cmd.z);
pos.SetYRotation(cmd.angle);
}
// Check whether it's obstructed by other entities
var cmpObstruction = Engine.QueryInterface(this.placementEntity[1], IID_Obstruction);
var colliding = (cmpObstruction && cmpObstruction.CheckCollisions());
// Set it to a red shade if this is an obstructed location
var cmpVisual = Engine.QueryInterface(this.placementEntity[1], IID_Visual);
if (cmpVisual)
{
if (colliding)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
if (!colliding)
return true;
}
return false;
};
// List the GuiInterface functions that can be safely called by GUI scripts.

View File

@ -60,10 +60,26 @@ function ProcessCommand(player, cmd)
var playerEnt = cmpPlayerMan.GetPlayerByID(player);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
// Tentatively create the foundation (we don't know yet if the player can really afford it)
// 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)
// Move the foundation to the right place
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
cmpPosition.JumpTo(cmd.x, cmd.z);
cmpPosition.SetYRotation(cmd.angle);
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (cmpObstruction && cmpObstruction.CheckCollisions())
{
// TODO: report error to player (the building site was obstructed)
// Remove the foundation because the construction was aborted
Engine.DestroyEntity(ent);
break;
}
var cmpCost = Engine.QueryInterface(ent, IID_Cost);
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
{
@ -75,11 +91,6 @@ function ProcessCommand(player, cmd)
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);
// Make it owned by the current player
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);