Last active
August 29, 2015 14:08
-
-
Save roSievers/32814166e3bbf440c192 to your computer and use it in GitHub Desktop.
Bandboxing - Ripping apart the input code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This module takes care of the so called Bandbox. The component | |
// used to select several units at once by drawing a rectangle. | |
// In particular, it provides an event handling Object "bandboxing" | |
// used by handleInputBeforeGui in input.js. | |
// The Pyrogenesis engine can only import whole javascript modules. | |
// To prevent interference with other code this module is encapsulated | |
// in an object. | |
// First we create a new object, which can be used as an event processor | |
// via: .setHandleInputBeforeGui(bandboxing); on the eventProcessor. | |
var bandboxing = { | |
"channel" : "before", | |
"terminationCallback" : undefined, | |
"dragStart" : undefined, // This remembers, at which position the bandbox was started. | |
"bandbox" : undefined, // This is the Gui element, the visible part of the bandbox. | |
"x0" : undefined, // The top left corner of the bandbox, screen coordinates. | |
"y0" : undefined, | |
"x1" : undefined, // The bottom right corner. | |
"y1" : undefined, | |
}; | |
// Filter by the supplied conditions, until one returns at least on element. | |
// A condition must accept an entity as parameter and return a boolean value. | |
bandboxing.iteratedEntityFilter = function (ents, conditions) | |
{ | |
var preferredEnts = []; | |
for (var i = 0; i < conditions.length; i++) | |
{ | |
preferredEnts = ents.filter(conditions[i]); | |
if (preferredEnts.length > 0) // filter was sucessfull | |
{ | |
return preferredEnts; | |
} | |
} | |
return []; // no condition was matched at all. | |
} | |
// And so on ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This module takes care of the so called Bandbox. The component | |
// used to select several units at once by drawing a rectangle. | |
// In particular, it provides an event handling Object "bandboxing" | |
// used by handleInputBeforeGui in input.js. | |
// The Pyrogenesis engine can only import whole javascript modules. | |
// To prevent interference with other code this module is encapsulated | |
// in a closure. Thus "getBandboxHandler" is the only member visible to | |
// the outside. | |
var getBandboxHandler = function () | |
{ | |
var dragStart; // This remembers, at which position the bandbox was started. | |
var bandbox; // This is the Gui element, the visible part of the bandbox. | |
var x0, y0; // The top left corner of the bandbox, screen coordinates. | |
var x1, y1; // The bottom right corner. | |
// Filter by the supplied conditions, until one returns at least on element. | |
// A condition must accept an entity as parameter and return a boolean value. | |
function iteratedEntityFilter(ents, conditions) | |
{ | |
var preferredEnts = []; | |
for (var i = 0; i < conditions.length; i++) | |
{ | |
preferredEnts = ents.filter(conditions[i]); | |
if (preferredEnts.length > 0) // filter was sucessfull | |
{ | |
return preferredEnts; | |
} | |
} | |
return []; // no condition was matched at all. | |
} | |
// Updates the bandbox object with new positions and visibility. | |
function updateBandboxPosition(event) | |
{ | |
x0 = dragStart[0]; | |
y0 = dragStart[1]; | |
x1 = event.x; | |
y1 = event.y; | |
// normalize the orientation of the rectangle | |
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; } | |
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; } | |
bandbox.size = [x0, y0, x1, y1].join(" "); | |
return false; | |
} | |
// This defines several conditions, by which units may be choosen. | |
function isUnit (entity) | |
{ | |
var entState = GetEntityState(entity); | |
if (!entState) {return false;} | |
return hasClass(entState, "Unit"); | |
} | |
function isDefensive (entity) | |
{ | |
var entState = GetEntityState(entity); | |
if (!entState) {return false;} | |
return hasClass(entState, "Defensive"); | |
} | |
function isNotSupport (entity) | |
{ | |
var entState = GetEntityState(entity); | |
if (!entState) {return false;} | |
return hasClass(entState, "Unit") && !hasClass(entState, "Support"); | |
} | |
function isIdle (entity) | |
{ | |
var entState = GetEntityState(entity); | |
if (!entState) {return false;} | |
return hasClass(entState, "Unit") && entState.unitAI.isIdle; | |
} | |
function isAnything (entity) | |
{ | |
return true; | |
} | |
// returns a list of filters to be used with the iterated entity filter | |
// the list is based of the hotkeys held down. | |
function unitFilterConfiguration() | |
{ | |
var filters = unitFilterConfiguration.filters | |
if (Engine.HotkeyIsPressed("selection.milonly")) | |
return [isNotSupport]; | |
if (Engine.HotkeyIsPressed("selection.idleonly")) | |
return [isIdle]; | |
// By default, first look for units. If none are found, defensive | |
// buildings (e.g. towers) are considered. If there are no defensive | |
// structures either, anything goes. | |
return [isUnit, isDefensive, isAnything]; | |
} | |
// When returnEntities is True, this won't behave as an event | |
// handling function. Instead, the entities selected will be returned. | |
function updateBandboxSelection(event, returnEntities) | |
{ | |
var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID()); | |
var preferredEntities = iteratedEntityFilter(ents, unitFilterConfiguration()); | |
if (returnEntities) | |
{ | |
return preferredEntities | |
} | |
g_Selection.setHighlightList(preferredEntities); | |
// TODO: If this funcion consumes any event which comes with a | |
// change of unitFilterConfiguration, then "." and other Hotkeys | |
// can be overloaded for this. | |
return false; | |
} | |
function updateBandbox(event) | |
{ | |
var result = updateBandboxPosition(event); | |
return result || updateBandboxSelection(event); | |
} | |
function onMouseUp(event) | |
{ | |
if (event.button == SDL_BUTTON_LEFT) | |
{ | |
var ents = updateBandboxSelection(undefined, true); | |
// Update the list of selected units | |
if (Engine.HotkeyIsPressed("selection.add")) | |
{ | |
g_Selection.addList(ents); | |
} | |
else if (Engine.HotkeyIsPressed("selection.remove")) | |
{ | |
g_Selection.removeList(ents); | |
} | |
else | |
{ | |
g_Selection.reset(); | |
g_Selection.addList(ents); | |
} | |
bandboxing.terminate(); | |
return true; // The event was consumed. | |
} | |
else if (event.button == SDL_BUTTON_RIGHT) | |
{ | |
// Cancel selection | |
bandboxing.terminate(); | |
return true; | |
} | |
return false; | |
} | |
// The following makes bandboxing into an eventHandler, as used by input.js | |
// First we create a new object, which can be used as an event processor | |
// preprocessing via: setHandleInputBeforeGui(bandboxing); | |
var bandboxing = {"channel":"before"}; | |
var terminationCallback; | |
bandboxing.initialize = function (callback) | |
{ | |
terminationCallback = callback; | |
bandbox = Engine.GetGUIObjectByName("bandbox"); | |
if (!dragStart) | |
{ | |
error("The bandbox wasn't properly initialized!"); | |
error("The dragStart variable is not set."); | |
terminationCallback(); | |
return; | |
} | |
// This updates the Bandbox. It will have a size of 0, which is wrong. | |
// However, at this stage the bandbox is tiny, thus the bug | |
// most likely won't have any impact. | |
updateBandboxPosition({"x":dragStart[0], "y":dragStart[1]}); | |
bandbox.hidden = false; | |
}; | |
bandboxing.handleEvent = bakeEventHandlingFunction({ | |
"mousemotion" : updateBandbox, | |
"hotkeydown" : updateBandboxSelection, | |
"hotkeyup" : updateBandboxSelection, | |
"mousebuttonup" : onMouseUp | |
}); | |
bandboxing.terminate = function () | |
{ | |
bandbox.hidden = true; | |
// Remove the bandbox hover highlighting | |
g_Selection.setHighlightList([]); | |
// delete the information about dragStart | |
dragStart = undefined; | |
terminationCallback(); | |
} | |
// Any access to the bandboxing module must take it's route | |
// via this wrapper function. | |
function getBandbox(startingPoint) | |
{ | |
if (dragStart) | |
{ | |
warn("The bandbox wasn't properly terminated last time"); | |
} | |
dragStart = startingPoint; | |
return bandboxing; | |
} | |
return getBandbox; | |
} (); // End of the bandboxingClosure. | |
// Rembember that we defined a function and now immediatly call it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This function creates a mutable event handler. It has a state | |
// which is used for the actuall event processing. | |
// A default state is implemented. | |
// This is not a complete state machine, as there are | |
// no transitions. | |
function stateBasedEventProcessor(defaultHandler) | |
{ | |
// This is a closure. | |
var currentEventHandler; | |
var defaultEventHandler = defaultHandler; | |
// This function is passed to each eventHandler upon initialization. | |
// It must be called by the eventHandler when it terminates. | |
// Calling the terminationCallback does not terminate the event, | |
// it only removes it from the EventProcessor. | |
// As an optional argument, a new eventHandler can be supplied | |
// to replace the current one. This is useful, when several | |
// states for a system. (As is done with building placement) | |
function terminationCallback(newHandler) | |
{ | |
currentEventHandler = undefined; // prevent calling terminate | |
if (!newHandler) | |
{ | |
newHandler = defaultEventHandler | |
} | |
setEventHandler(newHandler); | |
} | |
function setEventHandler(handler) | |
{ | |
if (currentEventHandler) | |
{ | |
currentEventHandler.terminate(); | |
} | |
currentEventHandler = handler; | |
handler.initialize(terminationCallback); | |
} | |
function setDefaultHandler(handler) | |
{ | |
defaultEventHandler = handler; | |
} | |
// As an optional argument a channel may be supplied. | |
// This is necessary as a single state machine takes care of | |
// both preprocessing and afterGui processing. | |
function handleEvent(event, channel) | |
{ | |
if (!currentEventHandler) | |
{ | |
return false; | |
} | |
if (!channel) | |
{ | |
return currentEventHandler.handleEvent(event); | |
} | |
if (channel == currentEventHandler.channel) | |
{ | |
return currentEventHandler.handleEvent(event); | |
} | |
return false; // wrong channel | |
} | |
// Generates a new object and returns it. | |
return { | |
"setEventHandler" : setEventHandler, | |
"setDefaultHandler" : setDefaultHandler, | |
"handleEvent" : handleEvent | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment