2010-01-09 20:20:14 +01:00
|
|
|
const SDL_BUTTON_LEFT = 1;
|
|
|
|
const SDL_BUTTON_MIDDLE = 2;
|
|
|
|
const SDL_BUTTON_RIGHT = 3;
|
2010-04-19 21:47:23 +02:00
|
|
|
const SDLK_RSHIFT = 303;
|
|
|
|
const SDLK_LSHIFT = 304;
|
2010-06-28 14:30:59 +02:00
|
|
|
|
2010-08-01 18:25:24 +02:00
|
|
|
const MAX_SELECTION_SIZE = 32; // Limits selection size and ensures that there will not be too many selection items in the GUI
|
2010-06-28 14:30:59 +02:00
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
// TODO: these constants should be defined somewhere else instead, in
|
|
|
|
// case any other code wants to use them too
|
|
|
|
|
|
|
|
var INPUT_NORMAL = 0;
|
2010-03-11 21:01:16 +01:00
|
|
|
var INPUT_SELECTING = 1;
|
|
|
|
var INPUT_BANDBOXING = 2;
|
|
|
|
var INPUT_BUILDING_PLACEMENT = 3;
|
2010-03-18 00:16:04 +01:00
|
|
|
var INPUT_BUILDING_CLICK = 4;
|
|
|
|
var INPUT_BUILDING_DRAG = 5;
|
2010-04-19 21:47:23 +02:00
|
|
|
var INPUT_BATCHTRAINING = 6;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
|
|
|
var inputState = INPUT_NORMAL;
|
|
|
|
|
2010-06-07 20:14:18 +02:00
|
|
|
var defaultPlacementAngle = Math.PI*3/4;
|
2010-03-18 00:16:04 +01:00
|
|
|
var placementAngle;
|
|
|
|
var placementPosition;
|
|
|
|
var placementEntity;
|
2010-01-24 18:24:35 +01:00
|
|
|
|
2010-02-05 23:00:39 +01:00
|
|
|
var mouseX = 0;
|
|
|
|
var mouseY = 0;
|
2010-04-19 21:47:23 +02:00
|
|
|
var specialKeyStates = {};
|
|
|
|
specialKeyStates[SDLK_RSHIFT] = 0;
|
|
|
|
specialKeyStates[SDLK_LSHIFT] = 0;
|
|
|
|
// (TODO: maybe we should fix the hotkey system to be usable in this situation,
|
|
|
|
// rather than hardcoding Shift into this code?)
|
2010-02-05 23:00:39 +01:00
|
|
|
|
|
|
|
function updateCursor()
|
|
|
|
{
|
2010-03-12 21:39:52 +01:00
|
|
|
if (inputState == INPUT_NORMAL)
|
2010-02-05 23:00:39 +01:00
|
|
|
{
|
2010-03-12 21:39:52 +01:00
|
|
|
var action = determineAction(mouseX, mouseY);
|
|
|
|
if (action)
|
2010-02-05 23:00:39 +01:00
|
|
|
{
|
2010-03-12 21:39:52 +01:00
|
|
|
if (action.cursor)
|
|
|
|
{
|
|
|
|
Engine.SetCursor(action.cursor);
|
|
|
|
return;
|
|
|
|
}
|
2010-02-05 23:00:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Engine.SetCursor("arrow-default");
|
|
|
|
}
|
|
|
|
|
2010-02-12 23:46:53 +01:00
|
|
|
function findGatherType(gatherer, supply)
|
|
|
|
{
|
|
|
|
if (!gatherer || !supply)
|
2010-07-20 01:01:58 +02:00
|
|
|
return undefined;
|
2010-02-12 23:46:53 +01:00
|
|
|
if (gatherer[supply.type.generic+"."+supply.type.specific])
|
|
|
|
return supply.type.specific;
|
|
|
|
if (gatherer[supply.type.generic])
|
|
|
|
return supply.type.generic;
|
2010-07-20 01:01:58 +02:00
|
|
|
return undefined;
|
2010-02-12 23:46:53 +01:00
|
|
|
}
|
|
|
|
|
2010-02-05 23:00:39 +01:00
|
|
|
/**
|
|
|
|
* Determine the context-sensitive action that should be performed when the mouse is at (x,y)
|
|
|
|
*/
|
|
|
|
function determineAction(x, y)
|
|
|
|
{
|
2010-03-11 21:01:16 +01:00
|
|
|
var selection = g_Selection.toList();
|
2010-02-05 23:00:39 +01:00
|
|
|
|
|
|
|
// No action if there's no selection
|
|
|
|
if (!selection.length)
|
2010-07-20 01:01:58 +02:00
|
|
|
return undefined;
|
2010-02-05 23:00:39 +01:00
|
|
|
|
2010-03-07 21:14:30 +01:00
|
|
|
// If the selection doesn't exist, no action
|
2010-02-05 23:00:39 +01:00
|
|
|
var entState = Engine.GuiInterfaceCall("GetEntityState", selection[0]);
|
2010-03-07 21:14:30 +01:00
|
|
|
if (!entState)
|
2010-07-20 01:01:58 +02:00
|
|
|
return undefined;
|
2010-03-07 21:14:30 +01:00
|
|
|
|
|
|
|
// If the selection isn't friendly units, no action
|
2010-02-05 23:00:39 +01:00
|
|
|
var player = Engine.GetPlayerID();
|
2010-03-07 21:21:17 +01:00
|
|
|
if (entState.player != player && !g_DevSettings.controlAll)
|
2010-07-20 01:01:58 +02:00
|
|
|
return undefined;
|
2010-02-05 23:00:39 +01:00
|
|
|
|
2010-08-05 12:20:47 +02:00
|
|
|
// Work out whether the selection can have rally points
|
|
|
|
var haveRallyPoints = selection.every(function(ent) {
|
|
|
|
var entState = Engine.GuiInterfaceCall("GetEntityState", ent);
|
|
|
|
return entState && entState.rallyPoint;
|
|
|
|
});
|
|
|
|
|
2010-02-05 23:00:39 +01:00
|
|
|
var targets = Engine.PickEntitiesAtPoint(x, y);
|
|
|
|
|
2010-08-05 12:20:47 +02:00
|
|
|
// If there's no target unit
|
2010-02-05 23:00:39 +01:00
|
|
|
if (!targets.length)
|
2010-08-05 12:20:47 +02:00
|
|
|
{
|
|
|
|
// If all selected entities are buildings,
|
|
|
|
// set rally points, else make them walk
|
|
|
|
if (haveRallyPoints)
|
|
|
|
return {"type": "set-rallypoint"};
|
|
|
|
else
|
|
|
|
return {"type": "move"};
|
|
|
|
}
|
2010-02-05 23:00:39 +01:00
|
|
|
|
|
|
|
// Look at the first targeted entity
|
|
|
|
// (TODO: maybe we eventually want to look at more, and be more context-sensitive?
|
|
|
|
// e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse)
|
|
|
|
var targetState = Engine.GuiInterfaceCall("GetEntityState", targets[0]);
|
|
|
|
|
2010-08-05 12:20:47 +02:00
|
|
|
// If we selected buildings with rally points, and then click on one of those selected
|
|
|
|
// buildings, we should remove the rally point
|
|
|
|
if (haveRallyPoints && selection.indexOf(targets[0]) != -1)
|
|
|
|
return {"type": "unset-rallypoint"};
|
|
|
|
|
2010-07-09 00:47:57 +02:00
|
|
|
// Check if the target entity is a resource, foundation, or enemy unit.
|
|
|
|
// Check if any entities in the selection can gather the requested resource, can build the foundation, or can attack the enemy
|
2010-07-09 02:19:36 +02:00
|
|
|
for each (var entityID in selection)
|
2010-07-09 00:47:57 +02:00
|
|
|
{
|
|
|
|
var entState = Engine.GuiInterfaceCall("GetEntityState", entityID);
|
|
|
|
if (!entState)
|
|
|
|
continue;
|
|
|
|
|
2010-07-11 03:25:50 +02:00
|
|
|
var playerOwned = ((targetState.player == entState.player)? true : false);
|
|
|
|
var enemyOwned = ((targetState.player != entState.player)? true : false);
|
|
|
|
var gaiaOwned = ((targetState.player == 0)? true : false);
|
|
|
|
|
|
|
|
// If the target is a resource and we have the right kind of resource gatherers selected, then gather
|
|
|
|
// If the target is a foundation and we have builders selected, then build (or repair)
|
|
|
|
// If the target is an enemy, then attack
|
|
|
|
if (targetState.resourceSupply && (playerOwned || gaiaOwned))
|
|
|
|
{
|
|
|
|
var resource = findGatherType(entState.resourceGatherRates, targetState.resourceSupply);
|
|
|
|
if (resource)
|
|
|
|
return {"type": "gather", "cursor": "action-gather-"+resource, "target": targets[0]};
|
|
|
|
}
|
|
|
|
else if (targetState.foundation && entState.buildEntities && playerOwned)
|
2010-07-09 00:47:57 +02:00
|
|
|
{
|
2010-07-11 03:25:50 +02:00
|
|
|
return {"type": "build", "cursor": "action-build", "target": targets[0]};
|
2010-07-09 00:47:57 +02:00
|
|
|
}
|
2010-07-11 03:25:50 +02:00
|
|
|
else if (entState.attack && enemyOwned)
|
2010-07-09 00:47:57 +02:00
|
|
|
{
|
2010-07-11 03:25:50 +02:00
|
|
|
return {"type": "attack", "cursor": "action-attack", "target": targets[0]};
|
2010-07-09 00:47:57 +02:00
|
|
|
}
|
|
|
|
}
|
2010-07-11 03:25:50 +02:00
|
|
|
|
2010-02-05 23:00:39 +01:00
|
|
|
// If we don't do anything more specific, just walk
|
|
|
|
return {"type": "move"};
|
|
|
|
}
|
2010-03-18 00:16:04 +01:00
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
/*
|
|
|
|
|
|
|
|
Selection methods: (not all currently implemented)
|
|
|
|
|
|
|
|
- Left-click on entity to select (always chooses the 'closest' one if the mouse is over several).
|
|
|
|
Includes non-controllable units (e.g. trees, enemy units).
|
|
|
|
- Double-left-click to select entity plus all of the same type on the screen.
|
|
|
|
- Triple-left-click to select entity plus all of the same type in the world.
|
|
|
|
- Left-click-and-drag to select all in region. Only includes controllable units.
|
|
|
|
- Left-click on empty space to deselect all.
|
|
|
|
- Hotkeys to select various groups.
|
|
|
|
- Shift plus left-click on entity to toggle selection of that unit. Only includes controllable.
|
|
|
|
- Shift plus any other selection method above, to add them to current selection.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2010-03-18 00:16:04 +01:00
|
|
|
var dragStart; // used for remembering mouse coordinates at start of drag operations
|
|
|
|
|
2010-07-21 19:13:04 +02:00
|
|
|
function tryPlaceBuilding(queued)
|
2010-03-18 00:16:04 +01:00
|
|
|
{
|
|
|
|
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,
|
2010-07-21 19:13:04 +02:00
|
|
|
"entities": selection,
|
|
|
|
"queued": queued
|
2010-03-18 00:16:04 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2010-02-05 23:00:39 +01:00
|
|
|
|
2010-08-06 02:29:58 +02:00
|
|
|
// Limits bandboxed selections to certain types of entities based on priority
|
|
|
|
function getPreferredEntities(ents)
|
|
|
|
{
|
|
|
|
var entStateList = [];
|
|
|
|
var preferredEnts = [];
|
|
|
|
|
2010-08-07 02:21:17 +02:00
|
|
|
// Check if there are units in the selection and get a list of entity states
|
|
|
|
for each (var ent in ents)
|
2010-08-06 02:29:58 +02:00
|
|
|
{
|
2010-08-07 02:21:17 +02:00
|
|
|
var entState = Engine.GuiInterfaceCall("GetEntityState", ent);
|
2010-08-06 02:29:58 +02:00
|
|
|
if (!entState)
|
|
|
|
continue;
|
2010-08-07 02:21:17 +02:00
|
|
|
if (isUnit(entState))
|
|
|
|
preferredEnts.push(ent);
|
2010-08-06 02:29:58 +02:00
|
|
|
|
|
|
|
entStateList.push(entState);
|
|
|
|
}
|
|
|
|
|
2010-08-07 02:21:17 +02:00
|
|
|
// If there are no units, check if there are defensive entities in the selection
|
2010-08-06 02:29:58 +02:00
|
|
|
if (!preferredEnts.length)
|
|
|
|
for (var i = 0; i < ents.length; i++)
|
|
|
|
if (isDefensive(entStateList[i]))
|
|
|
|
preferredEnts.push(ents[i]);
|
|
|
|
|
|
|
|
return preferredEnts;
|
|
|
|
}
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
function handleInputBeforeGui(ev)
|
|
|
|
{
|
2010-04-19 21:47:23 +02:00
|
|
|
// Capture mouse position so we can use it for displaying cursors,
|
|
|
|
// and key states
|
2010-02-05 23:00:39 +01:00
|
|
|
switch (ev.type)
|
|
|
|
{
|
|
|
|
case "mousebuttonup":
|
|
|
|
case "mousebuttondown":
|
|
|
|
case "mousemotion":
|
|
|
|
mouseX = ev.x;
|
|
|
|
mouseY = ev.y;
|
|
|
|
break;
|
2010-04-19 21:47:23 +02:00
|
|
|
case "keydown":
|
|
|
|
if (ev.keysym.sym in specialKeyStates)
|
|
|
|
specialKeyStates[ev.keysym.sym] = 1;
|
|
|
|
break;
|
|
|
|
case "keyup":
|
|
|
|
if (ev.keysym.sym in specialKeyStates)
|
|
|
|
specialKeyStates[ev.keysym.sym] = 0;
|
|
|
|
break;
|
2010-02-05 23:00:39 +01:00
|
|
|
}
|
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
// State-machine processing:
|
|
|
|
//
|
|
|
|
// (This is for states which should override the normal GUI processing - events will
|
|
|
|
// be processed here before being passed on, and propagation will stop if this function
|
|
|
|
// returns true)
|
2010-04-19 21:47:23 +02:00
|
|
|
//
|
|
|
|
// TODO: it'd probably be nice to have a better state-machine system, with guaranteed
|
|
|
|
// entry/exit functions, since this is a bit broken now
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
switch (inputState)
|
|
|
|
{
|
|
|
|
case INPUT_BANDBOXING:
|
|
|
|
switch (ev.type)
|
|
|
|
{
|
|
|
|
case "mousemotion":
|
2010-03-18 00:16:04 +01:00
|
|
|
var x0 = dragStart[0];
|
|
|
|
var y0 = dragStart[1];
|
2010-03-12 21:39:52 +01:00
|
|
|
var x1 = ev.x;
|
|
|
|
var y1 = ev.y;
|
|
|
|
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
|
|
|
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
var bandbox = getGUIObjectByName("bandbox");
|
|
|
|
bandbox.size = [x0, y0, x1, y1].join(" ");
|
|
|
|
bandbox.hidden = false;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
|
|
|
|
g_Selection.setHighlightList(ents);
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
return false;
|
2010-01-09 20:20:14 +01:00
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
case "mousebuttonup":
|
|
|
|
if (ev.button == SDL_BUTTON_LEFT)
|
|
|
|
{
|
2010-03-18 00:16:04 +01:00
|
|
|
var x0 = dragStart[0];
|
|
|
|
var y0 = dragStart[1];
|
2010-03-12 21:39:52 +01:00
|
|
|
var x1 = ev.x;
|
|
|
|
var y1 = ev.y;
|
|
|
|
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
|
|
|
|
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
|
|
|
|
|
|
|
|
var bandbox = getGUIObjectByName("bandbox");
|
|
|
|
bandbox.hidden = true;
|
|
|
|
|
2010-08-06 02:29:58 +02:00
|
|
|
// Get list of entities limited to preferred entities
|
2010-03-12 21:39:52 +01:00
|
|
|
var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
|
2010-08-06 02:29:58 +02:00
|
|
|
var preferredEntities = getPreferredEntities(ents)
|
2010-08-01 23:50:03 +02:00
|
|
|
|
2010-08-06 02:29:58 +02:00
|
|
|
if (preferredEntities.length)
|
|
|
|
ents = preferredEntities;
|
2010-06-28 14:30:59 +02:00
|
|
|
|
2010-08-01 23:50:03 +02:00
|
|
|
// Remove entities if selection is too large
|
2010-06-28 14:30:59 +02:00
|
|
|
if (ents.length > MAX_SELECTION_SIZE)
|
|
|
|
ents = ents.slice(0, MAX_SELECTION_SIZE);
|
|
|
|
|
2010-06-26 20:10:03 +02:00
|
|
|
// Set selection list
|
2010-03-12 21:39:52 +01:00
|
|
|
g_Selection.setHighlightList([]);
|
|
|
|
g_Selection.reset();
|
|
|
|
g_Selection.addList(ents);
|
2010-06-28 14:30:59 +02:00
|
|
|
|
2010-03-12 21:39:52 +01:00
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (ev.button == SDL_BUTTON_RIGHT)
|
|
|
|
{
|
|
|
|
// Cancel selection
|
|
|
|
var bandbox = getGUIObjectByName("bandbox");
|
|
|
|
bandbox.hidden = true;
|
|
|
|
|
|
|
|
g_Selection.setHighlightList([]);
|
|
|
|
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2010-03-18 00:16:04 +01:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2010-07-21 19:13:04 +02:00
|
|
|
// If shift is down, let the player continue placing another of the same building
|
|
|
|
var queued = (specialKeyStates[SDLK_RSHIFT] || specialKeyStates[SDLK_LSHIFT]);
|
|
|
|
if (tryPlaceBuilding(queued))
|
2010-05-09 02:25:03 +02:00
|
|
|
{
|
2010-07-21 19:13:04 +02:00
|
|
|
if (queued)
|
2010-05-09 02:25:03 +02:00
|
|
|
inputState = INPUT_BUILDING_PLACEMENT;
|
|
|
|
else
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
}
|
2010-03-18 00:16:04 +01:00
|
|
|
else
|
2010-05-09 02:25:03 +02:00
|
|
|
{
|
2010-03-18 00:16:04 +01:00
|
|
|
inputState = INPUT_BUILDING_PLACEMENT;
|
2010-05-09 02:25:03 +02:00
|
|
|
}
|
2010-03-18 00:16:04 +01:00
|
|
|
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)
|
|
|
|
{
|
2010-07-21 19:13:04 +02:00
|
|
|
// If shift is down, let the player continue placing another of the same building
|
|
|
|
var queued = (specialKeyStates[SDLK_RSHIFT] || specialKeyStates[SDLK_LSHIFT]);
|
|
|
|
if (tryPlaceBuilding(queued))
|
|
|
|
{
|
|
|
|
if (queued)
|
|
|
|
inputState = INPUT_BUILDING_PLACEMENT;
|
|
|
|
else
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
}
|
2010-03-18 00:16:04 +01:00
|
|
|
else
|
2010-07-21 19:13:04 +02:00
|
|
|
{
|
2010-03-18 00:16:04 +01:00
|
|
|
inputState = INPUT_BUILDING_PLACEMENT;
|
2010-07-21 19:13:04 +02:00
|
|
|
}
|
2010-03-18 00:16:04 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "mousebuttondown":
|
|
|
|
if (ev.button == SDL_BUTTON_RIGHT)
|
|
|
|
{
|
|
|
|
// Cancel building
|
|
|
|
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2010-04-19 21:47:23 +02:00
|
|
|
|
|
|
|
case INPUT_BATCHTRAINING:
|
|
|
|
switch (ev.type)
|
|
|
|
{
|
|
|
|
case "keyup":
|
|
|
|
if (ev.keysym.sym == SDLK_RSHIFT || ev.keysym.sym == SDLK_LSHIFT)
|
|
|
|
{
|
|
|
|
flushTrainingQueueBatch();
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2010-03-12 21:39:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2010-03-11 21:01:16 +01:00
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
function handleInputAfterGui(ev)
|
|
|
|
{
|
2010-03-12 21:39:52 +01:00
|
|
|
// State-machine processing:
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
switch (inputState)
|
|
|
|
{
|
|
|
|
case INPUT_NORMAL:
|
|
|
|
switch (ev.type)
|
|
|
|
{
|
2010-03-11 21:01:16 +01:00
|
|
|
case "mousemotion":
|
|
|
|
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
|
|
|
|
g_Selection.setHighlightList(ents);
|
|
|
|
return false;
|
|
|
|
|
2010-01-09 20:20:14 +01:00
|
|
|
case "mousebuttondown":
|
|
|
|
if (ev.button == SDL_BUTTON_LEFT)
|
|
|
|
{
|
2010-03-18 00:16:04 +01:00
|
|
|
dragStart = [ ev.x, ev.y ];
|
2010-03-11 21:01:16 +01:00
|
|
|
inputState = INPUT_SELECTING;
|
2010-01-09 20:20:14 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (ev.button == SDL_BUTTON_RIGHT)
|
|
|
|
{
|
2010-02-05 23:00:39 +01:00
|
|
|
var action = determineAction(ev.x, ev.y);
|
|
|
|
if (!action)
|
|
|
|
break;
|
|
|
|
|
2010-03-11 21:01:16 +01:00
|
|
|
var selection = g_Selection.toList();
|
2010-02-05 23:00:39 +01:00
|
|
|
|
2010-07-21 19:13:04 +02:00
|
|
|
// If shift is down, add the order to the unit's order queue instead
|
|
|
|
// of running it immediately
|
|
|
|
var queued = (specialKeyStates[SDLK_RSHIFT] || specialKeyStates[SDLK_LSHIFT]);
|
|
|
|
|
2010-02-05 23:00:39 +01:00
|
|
|
switch (action.type)
|
2010-01-09 20:20:14 +01:00
|
|
|
{
|
2010-02-05 23:00:39 +01:00
|
|
|
case "move":
|
2010-01-09 20:20:14 +01:00
|
|
|
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
2010-07-21 19:13:04 +02:00
|
|
|
Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
|
2010-02-05 23:00:39 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
case "attack":
|
2010-07-21 19:13:04 +02:00
|
|
|
Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued});
|
2010-01-09 20:20:14 +01:00
|
|
|
return true;
|
2010-02-12 23:46:53 +01:00
|
|
|
|
2010-03-12 22:41:40 +01:00
|
|
|
case "build": // (same command as repair)
|
|
|
|
case "repair":
|
2010-07-21 19:13:04 +02:00
|
|
|
Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "queued": queued});
|
2010-03-12 22:41:40 +01:00
|
|
|
return true;
|
|
|
|
|
2010-02-12 23:46:53 +01:00
|
|
|
case "gather":
|
2010-07-21 19:13:04 +02:00
|
|
|
Engine.PostNetworkCommand({"type": "gather", "entities": selection, "target": action.target, "queued": queued});
|
2010-02-12 23:46:53 +01:00
|
|
|
return true;
|
2010-03-12 22:41:40 +01:00
|
|
|
|
2010-08-05 12:20:47 +02:00
|
|
|
case "set-rallypoint":
|
|
|
|
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
|
|
|
Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z});
|
|
|
|
// Display rally point at the new coordinates, to avoid display lag
|
|
|
|
Engine.GuiInterfaceCall("DisplayRallyPoint", {
|
|
|
|
"entities": selection,
|
|
|
|
"x": target.x,
|
|
|
|
"z": target.z
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case "unset-rallypoint":
|
|
|
|
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
|
|
|
Engine.PostNetworkCommand({"type": "unset-rallypoint", "entities": selection});
|
|
|
|
// Remove displayed rally point
|
|
|
|
Engine.GuiInterfaceCall("DisplayRallyPoint", {
|
|
|
|
"entities": []
|
|
|
|
});
|
|
|
|
return true;
|
|
|
|
|
2010-03-12 22:41:40 +01:00
|
|
|
default:
|
|
|
|
throw new Error("Invalid action.type "+action.type);
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
}
|
2010-03-11 21:01:16 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case INPUT_SELECTING:
|
|
|
|
switch (ev.type)
|
|
|
|
{
|
|
|
|
case "mousemotion":
|
|
|
|
// If the mouse moved further than a limit, switch to bandbox mode
|
2010-03-18 00:16:04 +01:00
|
|
|
var dragDeltaX = ev.x - dragStart[0];
|
|
|
|
var dragDeltaY = ev.y - dragStart[1];
|
2010-03-11 21:01:16 +01:00
|
|
|
var maxDragDelta = 4;
|
|
|
|
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
|
|
|
|
{
|
|
|
|
inputState = INPUT_BANDBOXING;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
|
|
|
|
g_Selection.setHighlightList(ents);
|
|
|
|
return false;
|
|
|
|
|
|
|
|
case "mousebuttonup":
|
|
|
|
if (ev.button == SDL_BUTTON_LEFT)
|
|
|
|
{
|
|
|
|
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
|
|
|
|
if (!ents.length)
|
|
|
|
{
|
|
|
|
g_Selection.reset();
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
g_Selection.reset();
|
|
|
|
g_Selection.addList([ents[0]]);
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2010-01-24 18:24:35 +01:00
|
|
|
case INPUT_BUILDING_PLACEMENT:
|
|
|
|
switch (ev.type)
|
|
|
|
{
|
|
|
|
case "mousemotion":
|
|
|
|
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
2010-03-12 22:41:40 +01:00
|
|
|
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
|
|
|
|
"template": placementEntity,
|
|
|
|
"x": target.x,
|
|
|
|
"z": target.z,
|
2010-03-18 00:16:04 +01:00
|
|
|
"angle": placementAngle
|
2010-03-12 22:41:40 +01:00
|
|
|
});
|
2010-01-24 18:24:35 +01:00
|
|
|
|
|
|
|
return false; // continue processing mouse motion
|
|
|
|
|
|
|
|
case "mousebuttondown":
|
|
|
|
if (ev.button == SDL_BUTTON_LEFT)
|
|
|
|
{
|
2010-03-18 00:16:04 +01:00
|
|
|
placementPosition = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
|
|
|
dragStart = [ ev.x, ev.y ];
|
|
|
|
inputState = INPUT_BUILDING_CLICK;
|
2010-01-24 18:24:35 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (ev.button == SDL_BUTTON_RIGHT)
|
|
|
|
{
|
2010-03-18 00:16:04 +01:00
|
|
|
// Cancel building
|
2010-01-24 18:24:35 +01:00
|
|
|
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
|
|
|
|
inputState = INPUT_NORMAL;
|
|
|
|
return true;
|
|
|
|
}
|
2010-03-11 21:01:16 +01:00
|
|
|
break;
|
2010-01-24 18:24:35 +01:00
|
|
|
}
|
|
|
|
break;
|
2010-01-09 20:20:14 +01:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2010-01-24 18:24:35 +01:00
|
|
|
|
2010-04-19 21:47:23 +02:00
|
|
|
// Called by GUI when user clicks construction button
|
|
|
|
function startBuildingPlacement(buildEntType)
|
2010-01-24 18:24:35 +01:00
|
|
|
{
|
2010-04-19 21:47:23 +02:00
|
|
|
placementEntity = buildEntType;
|
2010-03-18 00:16:04 +01:00
|
|
|
placementAngle = defaultPlacementAngle;
|
2010-01-24 18:24:35 +01:00
|
|
|
inputState = INPUT_BUILDING_PLACEMENT;
|
|
|
|
}
|
2010-04-19 21:47:23 +02:00
|
|
|
|
|
|
|
// Batch training:
|
|
|
|
// When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING
|
|
|
|
// When the user releases shift, or clicks on a different training button, we create the batched units
|
|
|
|
var batchTrainingEntity;
|
|
|
|
var batchTrainingType;
|
|
|
|
var batchTrainingCount;
|
|
|
|
const batchIncrementSize = 5;
|
|
|
|
|
|
|
|
function flushTrainingQueueBatch()
|
|
|
|
{
|
|
|
|
Engine.PostNetworkCommand({"type": "train", "entity": batchTrainingEntity, "template": batchTrainingType, "count": batchTrainingCount});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called by GUI when user clicks training button
|
|
|
|
function addToTrainingQueue(entity, trainEntType)
|
|
|
|
{
|
|
|
|
if (specialKeyStates[SDLK_RSHIFT] || specialKeyStates[SDLK_LSHIFT])
|
|
|
|
{
|
|
|
|
if (inputState == INPUT_BATCHTRAINING)
|
|
|
|
{
|
|
|
|
// If we're already creating a batch of this unit, then just extend it
|
|
|
|
if (batchTrainingEntity == entity && batchTrainingType == trainEntType)
|
|
|
|
{
|
|
|
|
batchTrainingCount += batchIncrementSize;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Otherwise start a new one
|
|
|
|
else
|
|
|
|
{
|
|
|
|
flushTrainingQueueBatch();
|
|
|
|
// fall through to create the new batch
|
|
|
|
}
|
|
|
|
}
|
|
|
|
inputState = INPUT_BATCHTRAINING;
|
|
|
|
batchTrainingEntity = entity;
|
|
|
|
batchTrainingType = trainEntType;
|
|
|
|
batchTrainingCount = batchIncrementSize;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Non-batched - just create a single entity
|
|
|
|
Engine.PostNetworkCommand({"type": "train", "entity": entity, "template": trainEntType, "count": 1});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the number of units that will be present in a batch if the user clicks
|
|
|
|
// the training button with shift down
|
|
|
|
function getTrainingQueueBatchStatus(entity, trainEntType)
|
|
|
|
{
|
|
|
|
if (inputState == INPUT_BATCHTRAINING && batchTrainingEntity == entity && batchTrainingType == trainEntType)
|
|
|
|
return [batchTrainingCount, batchIncrementSize];
|
|
|
|
else
|
|
|
|
return [0, batchIncrementSize];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called by GUI when user clicks production queue item
|
|
|
|
function removeFromTrainingQueue(entity, id)
|
|
|
|
{
|
2010-07-05 18:56:30 +02:00
|
|
|
Engine.PostNetworkCommand({"type": "stop-train", "entity": entity, "id": id});
|
2010-04-19 21:47:23 +02:00
|
|
|
}
|
2010-07-31 00:19:43 +02:00
|
|
|
|
|
|
|
// Called by unit selection buttons
|
|
|
|
function changePrimarySelectionGroup(index)
|
|
|
|
{
|
|
|
|
if (specialKeyStates[SDLK_RSHIFT] || specialKeyStates[SDLK_LSHIFT])
|
|
|
|
g_Selection.makePrimarySelection(index, true);
|
|
|
|
else
|
|
|
|
g_Selection.makePrimarySelection(index, false);
|
|
|
|
}
|
2010-08-01 07:09:30 +02:00
|
|
|
|
2010-08-01 18:25:24 +02:00
|
|
|
// Performs the specified command (delete, town bell, repair, etc.)
|
2010-08-01 07:09:30 +02:00
|
|
|
function performCommand(entity, commandName)
|
|
|
|
{
|
2010-08-01 18:25:24 +02:00
|
|
|
if (entity)
|
2010-08-01 07:09:30 +02:00
|
|
|
{
|
2010-08-01 18:25:24 +02:00
|
|
|
switch (commandName)
|
|
|
|
{
|
|
|
|
case "delete":
|
|
|
|
Engine.PostNetworkCommand({"type": "delete-entity", "entity": entity});
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2010-08-01 07:09:30 +02:00
|
|
|
}
|
2010-08-05 12:20:47 +02:00
|
|
|
}
|