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:
parent
baead3409e
commit
08487ce246
@ -9,10 +9,15 @@ var INPUT_NORMAL = 0;
|
|||||||
var INPUT_SELECTING = 1;
|
var INPUT_SELECTING = 1;
|
||||||
var INPUT_BANDBOXING = 2;
|
var INPUT_BANDBOXING = 2;
|
||||||
var INPUT_BUILDING_PLACEMENT = 3;
|
var INPUT_BUILDING_PLACEMENT = 3;
|
||||||
|
var INPUT_BUILDING_CLICK = 4;
|
||||||
|
var INPUT_BUILDING_DRAG = 5;
|
||||||
|
|
||||||
var inputState = INPUT_NORMAL;
|
var inputState = INPUT_NORMAL;
|
||||||
|
|
||||||
var placementEntity = "";
|
var defaultPlacementAngle = Math.PI;
|
||||||
|
var placementAngle;
|
||||||
|
var placementPosition;
|
||||||
|
var placementEntity;
|
||||||
|
|
||||||
var mouseX = 0;
|
var mouseX = 0;
|
||||||
var mouseY = 0;
|
var mouseY = 0;
|
||||||
@ -95,6 +100,7 @@ function determineAction(x, y)
|
|||||||
// If we don't do anything more specific, just walk
|
// If we don't do anything more specific, just walk
|
||||||
return {"type": "move"};
|
return {"type": "move"};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Selection methods: (not all currently implemented)
|
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
|
// 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)
|
function handleInputBeforeGui(ev)
|
||||||
{
|
{
|
||||||
@ -139,8 +179,8 @@ function handleInputBeforeGui(ev)
|
|||||||
switch (ev.type)
|
switch (ev.type)
|
||||||
{
|
{
|
||||||
case "mousemotion":
|
case "mousemotion":
|
||||||
var x0 = selectionDragStart[0];
|
var x0 = dragStart[0];
|
||||||
var y0 = selectionDragStart[1];
|
var y0 = dragStart[1];
|
||||||
var x1 = ev.x;
|
var x1 = ev.x;
|
||||||
var y1 = ev.y;
|
var y1 = ev.y;
|
||||||
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
||||||
@ -158,8 +198,8 @@ function handleInputBeforeGui(ev)
|
|||||||
case "mousebuttonup":
|
case "mousebuttonup":
|
||||||
if (ev.button == SDL_BUTTON_LEFT)
|
if (ev.button == SDL_BUTTON_LEFT)
|
||||||
{
|
{
|
||||||
var x0 = selectionDragStart[0];
|
var x0 = dragStart[0];
|
||||||
var y0 = selectionDragStart[1];
|
var y0 = dragStart[1];
|
||||||
var x1 = ev.x;
|
var x1 = ev.x;
|
||||||
var y1 = ev.y;
|
var y1 = ev.y;
|
||||||
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
||||||
@ -191,13 +231,101 @@ function handleInputBeforeGui(ev)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectionDragStart;
|
|
||||||
|
|
||||||
function handleInputAfterGui(ev)
|
function handleInputAfterGui(ev)
|
||||||
{
|
{
|
||||||
// State-machine processing:
|
// State-machine processing:
|
||||||
@ -215,7 +343,7 @@ function handleInputAfterGui(ev)
|
|||||||
case "mousebuttondown":
|
case "mousebuttondown":
|
||||||
if (ev.button == SDL_BUTTON_LEFT)
|
if (ev.button == SDL_BUTTON_LEFT)
|
||||||
{
|
{
|
||||||
selectionDragStart = [ ev.x, ev.y ];
|
dragStart = [ ev.x, ev.y ];
|
||||||
inputState = INPUT_SELECTING;
|
inputState = INPUT_SELECTING;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -260,8 +388,8 @@ function handleInputAfterGui(ev)
|
|||||||
{
|
{
|
||||||
case "mousemotion":
|
case "mousemotion":
|
||||||
// If the mouse moved further than a limit, switch to bandbox mode
|
// If the mouse moved further than a limit, switch to bandbox mode
|
||||||
var dragDeltaX = ev.x - selectionDragStart[0];
|
var dragDeltaX = ev.x - dragStart[0];
|
||||||
var dragDeltaY = ev.y - selectionDragStart[1];
|
var dragDeltaY = ev.y - dragStart[1];
|
||||||
var maxDragDelta = 4;
|
var maxDragDelta = 4;
|
||||||
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
|
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
|
||||||
{
|
{
|
||||||
@ -300,12 +428,11 @@ function handleInputAfterGui(ev)
|
|||||||
{
|
{
|
||||||
case "mousemotion":
|
case "mousemotion":
|
||||||
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
||||||
var angle = Math.PI;
|
|
||||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
||||||
"template": placementEntity,
|
"template": placementEntity,
|
||||||
"x": target.x,
|
"x": target.x,
|
||||||
"z": target.z,
|
"z": target.z,
|
||||||
"angle": angle
|
"angle": placementAngle
|
||||||
});
|
});
|
||||||
|
|
||||||
return false; // continue processing mouse motion
|
return false; // continue processing mouse motion
|
||||||
@ -313,28 +440,15 @@ function handleInputAfterGui(ev)
|
|||||||
case "mousebuttondown":
|
case "mousebuttondown":
|
||||||
if (ev.button == SDL_BUTTON_LEFT)
|
if (ev.button == SDL_BUTTON_LEFT)
|
||||||
{
|
{
|
||||||
var selection = g_Selection.toList();
|
placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
||||||
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
dragStart = [ ev.x, ev.y ];
|
||||||
var angle = Math.PI;
|
inputState = INPUT_BUILDING_CLICK;
|
||||||
// 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;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (ev.button == SDL_BUTTON_RIGHT)
|
else if (ev.button == SDL_BUTTON_RIGHT)
|
||||||
{
|
{
|
||||||
|
// Cancel building
|
||||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
|
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
|
||||||
|
|
||||||
inputState = INPUT_NORMAL;
|
inputState = INPUT_NORMAL;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -348,5 +462,6 @@ function handleInputAfterGui(ev)
|
|||||||
function testBuild(ent)
|
function testBuild(ent)
|
||||||
{
|
{
|
||||||
placementEntity = ent;
|
placementEntity = ent;
|
||||||
|
placementAngle = defaultPlacementAngle;
|
||||||
inputState = INPUT_BUILDING_PLACEMENT;
|
inputState = INPUT_BUILDING_PLACEMENT;
|
||||||
}
|
}
|
||||||
|
@ -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
|
// 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)
|
// decoupled from its owner, so we need to remember it in here (and assume it won't change)
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
|
|
||||||
|
this.initialised = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
Foundation.prototype.GetBuildPercentage = function()
|
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
|
// 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)
|
if (this.buildProgress == 1.0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -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)
|
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
||||||
{
|
{
|
||||||
// See if we're changing template
|
// 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)
|
if (this.placementEntity)
|
||||||
{
|
{
|
||||||
|
// Move the preview into the right location
|
||||||
var pos = Engine.QueryInterface(this.placementEntity[1], IID_Position);
|
var pos = Engine.QueryInterface(this.placementEntity[1], IID_Position);
|
||||||
if (pos)
|
if (pos)
|
||||||
{
|
{
|
||||||
pos.JumpTo(cmd.x, cmd.z);
|
pos.JumpTo(cmd.x, cmd.z);
|
||||||
pos.SetYRotation(cmd.angle);
|
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.
|
// List the GuiInterface functions that can be safely called by GUI scripts.
|
||||||
|
@ -60,10 +60,26 @@ function ProcessCommand(player, cmd)
|
|||||||
var playerEnt = cmpPlayerMan.GetPlayerByID(player);
|
var playerEnt = cmpPlayerMan.GetPlayerByID(player);
|
||||||
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_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);
|
var ent = Engine.AddEntity("foundation|" + cmd.template);
|
||||||
// TODO: report errors (e.g. invalid template names)
|
// 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);
|
var cmpCost = Engine.QueryInterface(ent, IID_Cost);
|
||||||
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
|
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
|
||||||
{
|
{
|
||||||
@ -75,11 +91,6 @@ function ProcessCommand(player, cmd)
|
|||||||
break;
|
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
|
// Make it owned by the current player
|
||||||
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||||
cmpOwnership.SetOwner(player);
|
cmpOwnership.SetOwner(player);
|
||||||
|
Loading…
Reference in New Issue
Block a user