Changeset View
Standalone View
binaries/data/mods/public/gui/session/input_events.js
- This file was added.
function InputEvents(initState) | |||||
{ | |||||
this.FsmStateNameChanged(initState) | |||||
}; | |||||
InputEvents.prototype.Init = function () | |||||
{ | |||||
this.fsm = new FSM(this.events); | |||||
this.fsm.Init(this, this.GetCurrentState()); | |||||
}; | |||||
InputEvents.prototype.GetCurrentState = function () { return this.fsmStateName; } | |||||
InputEvents.prototype.BaseState = function () { return this.fsmStateName.split(".")[0] } | |||||
InputEvents.prototype.SetNextState = function (state) { return this.fsm.SetNextState(this, state); } | |||||
InputEvents.prototype.SwitchToNextState = function (state) { return this.fsm.SwitchToNextState(this, state); } | |||||
InputEvents.prototype.DeferMessage = function (msg) { return this.fsm.DeferMessage(this, msg); } | |||||
InputEvents.prototype.LookupState = function (currentStateName, stateName) { return this.fsm.LookupState(currentStateName, stateName); } | |||||
elexis: I wonder whether we can find a more expressive name than input events.
Input events are… | |||||
Done Inline ActionsI agree, any suggestions? My naming skills is my weak point :) nani: I agree, any suggestions? My naming skills is my weak point :) | |||||
Not Done Inline ActionsThe name should express the purpose of the prototype. elexis: The name should express the purpose of the prototype. | |||||
InputEvents.prototype.ProcessMessage = function (msg) { return this.fsm.ProcessMessage(this, msg) }; | |||||
InputEvents.prototype.FsmStateNameChanged = function (state) { this.fsmStateName = state; } | |||||
Done Inline Actionsempty line between each prototype member elexis: empty line between each prototype member
each statement on a separate line, that means either… | |||||
InputEvents.prototype.isStateAfterGUI = function () | |||||
Done Inline ActionsWe don't align it that way, because it means adding or renaming one of the lines can lead to having to fix the whitespace of all other lines. elexis: We don't align it that way, because it means adding or renaming one of the lines can lead to… | |||||
{ | |||||
return this.stateAfterGUI.some(state => this.GetCurrentState().startsWith(state)) | |||||
} | |||||
Done Inline ActionsstartsWith sounds fragile, exact matching is preferable where possible elexis: `startsWith` sounds fragile, exact matching is preferable where possible | |||||
Done Inline ActionsstartsWitch is the intended behaviour. nani: `startsWitch` is the intended behaviour.
Ex:
`"A.B.C".startsWith("A.B")` -> state inside of the… | |||||
InputEvents.prototype.hasBaseState = function (baseState) | |||||
{ | |||||
if (typeof baseState == 'string' || baseState instanceof String) | |||||
return this.BaseState() == baseState; | |||||
else // array | |||||
return baseState.indexOf(this.BaseState()) >= 0 | |||||
} | |||||
Done Inline Actions!= -1 (so that the reader knows precisely the one case that is tested for) Actually would IMO be nicer to KISS and support only string arguments, afaics only one line affected elexis: `!= -1` (so that the reader knows precisely the one case that is tested for)
Actually would… | |||||
InputEvents.prototype.stateAfterGUI = [ | |||||
"NORMAL", | |||||
"PRESELECTEDACTION", | |||||
"SELECTING", | |||||
"UNIT_POSITION.START", | |||||
"PLACEMENT" | |||||
]; | |||||
Done Inline ActionsShould start with the constants and then list the code operating on it elexis: Should start with the constants and then list the code operating on it | |||||
InputEvents.prototype.events = { | |||||
"mousemotion": function () { }, | |||||
"mousebuttonup": function () { }, | |||||
"mousebuttondown": function () { }, | |||||
"windowevent": function () { }, | |||||
"hotkeydown": function () { }, | |||||
"hotkeyup": function () { }, | |||||
"keydown": function () { }, | |||||
"keyup": function () { }, | |||||
"(unknown)": function () { }, | |||||
"BANDBOXING": { | |||||
"leave": function () | |||||
{ | |||||
Engine.GetGUIObjectByName("bandbox").hidden = true; | |||||
g_Selection.setHighlightList([]); | |||||
}, | |||||
"mousemotion": function (msg) | |||||
{ | |||||
let bandbox = Engine.GetGUIObjectByName("bandbox"); | |||||
let rect = updateBandbox(bandbox, msg.ev, false); | |||||
let ents = Engine.PickPlayerEntitiesInRect(rect[0], rect[1], rect[2], rect[3], g_ViewedPlayer); | |||||
let preferredEntities = getPreferredEntities(ents); | |||||
g_Selection.setHighlightList(preferredEntities); | |||||
}, | |||||
"mousebuttonup": function (msg) | |||||
{ | |||||
let bandbox = Engine.GetGUIObjectByName("bandbox"); | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
let rect = updateBandbox(bandbox, msg.ev, true); | |||||
// Get list of entities limited to preferred entities | |||||
let ents = getPreferredEntities(Engine.PickPlayerEntitiesInRect(rect[0], rect[1], rect[2], rect[3], g_ViewedPlayer)); | |||||
// Remove the bandbox hover highlighting | |||||
g_Selection.setHighlightList([]); | |||||
// 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); | |||||
} | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
else if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
// Cancel selection | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
} | |||||
}, | |||||
"UNIT_POSITION": { | |||||
"mousemotion": function (msg) | |||||
{ | |||||
return positionUnitsFreehandSelectionMouseMove(msg.ev); | |||||
Done Inline ActionsIn what case is one of the two conditions true and the other false? elexis: In what case is one of the two conditions true and the other false? | |||||
Done Inline Actionsnani: https://stackoverflow.com/questions/17256182/what-is-the-difference-between-string-primitives… | |||||
}, | |||||
"mousebuttonup": function (msg) | |||||
{ | |||||
return positionUnitsFreehandSelectionMouseUp(msg.ev); | |||||
}, | |||||
"START": { | |||||
Done Inline Actions.some if indexOf doesn't cut it elexis: `.some` if `indexOf` doesn't cut it
| |||||
"mousemotion": function (msg) | |||||
{ | |||||
// If the mouse moved further than a limit, switch to unit position mode | |||||
if (g_DragStart.distanceToSquared(msg.ev) >= Math.square(g_MaxDragDelta)) | |||||
Done Inline Actions{} elexis: {} | |||||
Done Inline ActionsOk, but come on, does it really matter ? nani: Ok, but come on, does it really matter ? | |||||
Done Inline ActionsIt's merely a consistency argument. If a reader reads 10x one variant, 5x the second variant, and 15x the other variant, he will notice the difference, wonder why it's the case. Only 5 seconds passed, but he will have the same thought everytime he goes through these. It takes up brain resources, instead the reader should direct all attention towards the original reason he had opened the file. (In general, if one keeps getting the same thought unrelated from the problem that one intended to work on, again and again when reading a file, there is probably something to that thought, fixing it removes the thought, sets one free. I need to test myself for autism some day.) elexis: It's merely a consistency argument. If a reader reads 10x one variant, 5x the second variant… | |||||
{ | |||||
this.SwitchToNextState("UNIT_POSITION"); | |||||
return false; | |||||
} | |||||
}, | |||||
"mousebuttonup": function (msg) | |||||
{ | |||||
this.SwitchToNextState("NORMAL"); | |||||
if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
let action = determineAction(msg.ev.x, msg.ev.y); | |||||
if (action) | |||||
return doAction(action, msg.ev); | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"MASSTRIBUTING": { | |||||
"hotkeyup": function (msg) | |||||
{ | |||||
if (msg.ev.hotkey == "session.masstribute") | |||||
{ | |||||
g_FlushTributing(); | |||||
this.SwitchToNextState("NORMAL"); | |||||
} | |||||
} | |||||
}, | |||||
"BATCHTRAINING": { | |||||
"hotkeyup": function (msg) | |||||
{ | |||||
if (msg.ev.hotkey == "session.batchtrain") | |||||
{ | |||||
flushTrainingBatch(); | |||||
this.SwitchToNextState("NORMAL"); | |||||
} | |||||
} | |||||
}, | |||||
"NORMAL": { | |||||
"mousemotion": function (msg) | |||||
{ | |||||
// Highlight the first hovered entity (if any) | |||||
var ent = Engine.PickEntityAtPoint(msg.ev.x, msg.ev.y); | |||||
if (ent != INVALID_ENTITY) | |||||
g_Selection.setHighlightList([ent]); | |||||
else | |||||
g_Selection.setHighlightList([]); | |||||
return false; | |||||
}, | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
g_DragStart = new Vector2D(msg.ev.x, msg.ev.y); | |||||
this.SwitchToNextState("SELECTING"); | |||||
// If a single click occured, reset the clickedEntity. | |||||
// Also set it if we're double/triple clicking and missed the unit earlier. | |||||
if (msg.ev.clicks == 1 || clickedEntity == INVALID_ENTITY) | |||||
clickedEntity = Engine.PickEntityAtPoint(msg.ev.x, msg.ev.y); | |||||
return true; | |||||
} | |||||
else if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
g_DragStart = new Vector2D(msg.ev.x, msg.ev.y); | |||||
this.SwitchToNextState("UNIT_POSITION.START"); | |||||
} | |||||
}, | |||||
"hotkeydown": function (msg) | |||||
{ | |||||
{ | |||||
if (msg.ev.hotkey.indexOf("selection.group.") == 0) | |||||
{ | |||||
let now = Date.now(); | |||||
if (now - doublePressTimer < doublePressTime && msg.ev.hotkey == prevHotkey) | |||||
{ | |||||
if (msg.ev.hotkey.indexOf("selection.group.select.") == 0) | |||||
{ | |||||
var sptr = msg.ev.hotkey.split("."); | |||||
performGroup("snap", sptr[3]); | |||||
} | |||||
Done Inline ActionsThe other places use a switch instead of if for this case, so could make it more consistent elexis: The other places use a switch instead of if for this case, so could make it more consistent | |||||
} | |||||
else | |||||
{ | |||||
var sptr = msg.ev.hotkey.split("."); | |||||
performGroup(sptr[2], sptr[3]); | |||||
doublePressTimer = now; | |||||
prevHotkey = msg.ev.hotkey; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}, | |||||
"PRESELECTEDACTION": { | |||||
"mousemotion": function (msg) | |||||
{ | |||||
// Highlight the first hovered entity (if any) | |||||
var ent = Engine.PickEntityAtPoint(msg.ev.x, msg.ev.y); | |||||
if (ent != INVALID_ENTITY) | |||||
g_Selection.setHighlightList([ent]); | |||||
Done Inline Actions; same for the other lines of that content elexis: ; same for the other lines of that content | |||||
else | |||||
g_Selection.setHighlightList([]); | |||||
return false; | |||||
}, | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT && preSelectedAction != ACTION_NONE) | |||||
{ | |||||
var action = determineAction(msg.ev.x, msg.ev.y); | |||||
if (!action) | |||||
return; | |||||
if (!Engine.HotkeyIsPressed("session.queue")) | |||||
{ | |||||
preSelectedAction = ACTION_NONE; | |||||
this.SwitchToNextState("NORMAL"); | |||||
} | |||||
return doAction(action, msg.ev); | |||||
} | |||||
else if (msg.ev.button == SDL_BUTTON_RIGHT && preSelectedAction != ACTION_NONE) | |||||
{ | |||||
preSelectedAction = ACTION_NONE; | |||||
Done Inline ActionsA function should either always or never return a value, i.e. return undefined or false here. (Yes return; is the same as return undefined; but the reader shall encounter return; for methods and return x; for functions where the return value is read from). We have a linter rule for that too elexis: A function should either always or never return a value, i.e. `return undefined` or false here. | |||||
this.SwitchToNextState("NORMAL"); | |||||
return; | |||||
} | |||||
}, | |||||
"default": function (msg) | |||||
{ | |||||
Done Inline Actions(!length) elexis: (!length) | |||||
// Slight hack: If selection is empty, reset the input state | |||||
if (g_Selection.toList().length == 0) | |||||
{ | |||||
preSelectedAction = ACTION_NONE; | |||||
this.SwitchToNextState("NORMAL"); | |||||
return; | |||||
} | |||||
} | |||||
}, | |||||
"SELECTING": { | |||||
"mousemotion": function (msg) | |||||
{ | |||||
// If the mouse moved further than a limit, switch to bandbox mode | |||||
if (g_DragStart.distanceTo(msg.ev) >= g_MaxDragDelta) | |||||
{ | |||||
this.SwitchToNextState("BANDBOXING"); | |||||
return false; | |||||
} | |||||
var ent = Engine.PickEntityAtPoint(msg.ev.x, msg.ev.y); | |||||
if (ent != INVALID_ENTITY) | |||||
g_Selection.setHighlightList([ent]); | |||||
else | |||||
g_Selection.setHighlightList([]); | |||||
return false; | |||||
}, | |||||
"mousebuttonup": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
if (clickedEntity == INVALID_ENTITY) | |||||
clickedEntity = Engine.PickEntityAtPoint(msg.ev.x, msg.ev.y); | |||||
// Abort if we didn't click on an entity or if the entity was removed before the mousebuttonup event. | |||||
if (clickedEntity == INVALID_ENTITY || !GetEntityState(clickedEntity)) | |||||
{ | |||||
clickedEntity = INVALID_ENTITY; | |||||
if (!Engine.HotkeyIsPressed("selection.add") && !Engine.HotkeyIsPressed("selection.remove")) | |||||
{ | |||||
g_Selection.reset(); | |||||
resetIdleUnit(); | |||||
} | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
// If camera following and we select different unit, stop | |||||
if (Engine.GetFollowedEntity() != clickedEntity) | |||||
Engine.CameraFollow(0); | |||||
Done Inline Actionsvariable can be inlined elexis: variable can be inlined | |||||
var ents = []; | |||||
if (msg.ev.clicks == 1) | |||||
ents = [clickedEntity]; | |||||
else | |||||
{ | |||||
// Double click or triple click has occurred | |||||
var showOffscreen = Engine.HotkeyIsPressed("selection.offscreen"); | |||||
var matchRank = true; | |||||
var templateToMatch; | |||||
// Check for double click or triple click | |||||
if (msg.ev.clicks == 2) | |||||
{ | |||||
// Select similar units regardless of rank | |||||
templateToMatch = GetEntityState(clickedEntity).identity.selectionGroupName; | |||||
if (templateToMatch) | |||||
Done Inline Actionsthe two variables can be inlined, which not only removes some characters, but also changes the structure of the function to be a sentence of the form subject predicate object, instead of object subject predicate. elexis: the two variables can be inlined, which not only removes some characters, but also changes the… | |||||
Done Inline ActionsShorter lines are better no? Having a s/p/o order doesn't matter is is more difficult to read. nani: Shorter lines are better no? Having a s/p/o order doesn't matter is is more difficult to read. | |||||
Done Inline ActionsWhy would it be more difficult to read? I've heard that one often before, but it's a matter of habitualizing themselves to things without considering the alternative. let rect = updateBandbox("bandbox", msg.ev, false); let ents = Engine.PickPlayerEntitiesInRect(...rect, g_ViewedPlayer); g_Selection.setHighlightList(getPreferredEntities(ents)); g_Selection.setHighlightList(getPreferredEntities( Engine.PickPlayerEntitiesInRect(...updateBandbox("bandbox", msg.ev, false), g_ViewedPlayer))); I would claim the latter is easier to read, It reads:
Instead of:
I've received a lot of hate for pointing this out, but it's the truth. If one can order the statements logically and linearily (1 > 2 > 3 > 4), it's much more straight forward to read than 4, 2, 1, 3, even if the code result is the same. The more information we can transport to the reader, and the quicker the information is transported, the quicker the reader can decide on it, and the more qualified the decision will be, and he won't have to solve puzzles to start understanding and deciding. elexis: Why would it be more difficult to read? I've heard that one often before, but it's a matter of… | |||||
matchRank = false; | |||||
else | |||||
// No selection group name defined, so fall back to exact match | |||||
templateToMatch = GetEntityState(clickedEntity).template; | |||||
} | |||||
else | |||||
// Triple click | |||||
// Select units matching exact template name (same rank) | |||||
templateToMatch = GetEntityState(clickedEntity).template; | |||||
// TODO: Should we handle "control all units" here as well? | |||||
ents = Engine.PickSimilarPlayerEntities(templateToMatch, showOffscreen, matchRank, false); | |||||
} | |||||
// 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); | |||||
} | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
} | |||||
}, | |||||
"PLACEMENT": { | |||||
"enter": function () | |||||
{ | |||||
this.showBuildingPlacementTerrainSnap(mouseX, mouseY); | |||||
}, | |||||
"leave": function () | |||||
{ | |||||
placementSupport.Reset(); | |||||
}, | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
if (placementSupport.mode === "wall") | |||||
this.SwitchToNextState("PLACEMENT.WALL.CLICK"); | |||||
else | |||||
this.SwitchToNextState("PLACEMENT.BUILDING.CLICK"); | |||||
return true; | |||||
} | |||||
else if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
}, | |||||
"mousemotion": function (msg) | |||||
{ | |||||
this.showBuildingPlacementTerrainSnap(msg.ev.x, msg.ev.y); | |||||
}, | |||||
"leave": function () | |||||
{ | |||||
placementSupport.Reset(); | |||||
}, | |||||
"BUILDING": { | |||||
"enter": function () | |||||
{ | |||||
this.showBuildingPlacementTerrainSnap(mouseX, mouseY); | |||||
}, | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
placementSupport.position = Engine.GetTerrainAtScreenPoint(msg.ev.x, msg.ev.y); | |||||
g_DragStart = new Vector2D(msg.ev.x, msg.ev.y); | |||||
this.SwitchToNextState("PLACEMENT.BUILDING.CLICK"); | |||||
return true; | |||||
} | |||||
else if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
}, | |||||
"hotkeydown": function (msg) | |||||
{ | |||||
var rotation_step = Math.PI / 12; // 24 clicks make a full rotation | |||||
switch (msg.ev.hotkey) | |||||
{ | |||||
case "session.rotate.cw": | |||||
placementSupport.angle += rotation_step; | |||||
updateBuildingPlacementPreview(); | |||||
return; | |||||
case "session.rotate.ccw": | |||||
placementSupport.angle -= rotation_step; | |||||
updateBuildingPlacementPreview(); | |||||
return; | |||||
} | |||||
}, | |||||
"CLICK": | |||||
{ | |||||
"mousemotion": function (msg) | |||||
{ | |||||
// If the mouse moved far enough from the original click location, | |||||
// then switch to drag-orientation mode | |||||
let maxDragDelta = 16; | |||||
if (g_DragStart.distanceTo(msg.ev) >= maxDragDelta) | |||||
{ | |||||
this.SwitchToNextState("PLACEMENT.BUILDING.DRAG"); | |||||
return false; | |||||
} | |||||
}, | |||||
"mousebuttonup": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
// If shift is down, let the player continue placing another of the same building | |||||
var queued = Engine.HotkeyIsPressed("session.queue"); | |||||
if (tryPlaceBuilding(queued)) | |||||
{ | |||||
if (queued) | |||||
this.SwitchToNextState("PLACEMENT.BUILDING"); | |||||
else | |||||
this.SwitchToNextState("NORMAL"); | |||||
} | |||||
else | |||||
{ | |||||
this.SwitchToNextState("PLACEMENT.BUILDING"); | |||||
} | |||||
return true; | |||||
} | |||||
}, | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
// Cancel building | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
} | |||||
}, | |||||
"DRAG": { | |||||
"mousemotion": function (msg) | |||||
{ | |||||
let maxDragDelta = 16; | |||||
if (g_DragStart.distanceTo(msg.ev) >= maxDragDelta) | |||||
{ | |||||
// Rotate in the direction of the mouse | |||||
placementSupport.angle = placementSupport.position.horizAngleTo(Engine.GetTerrainAtScreenPoint(msg.ev.x, msg.ev.y)); | |||||
} | |||||
else | |||||
{ | |||||
// If the mouse is near the center, snap back to the default orientation | |||||
placementSupport.SetDefaultAngle(); | |||||
} | |||||
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { | |||||
"template": placementSupport.template, | |||||
"x": placementSupport.position.x, | |||||
"z": placementSupport.position.z | |||||
}); | |||||
if (snapData) | |||||
{ | |||||
placementSupport.angle = snapData.angle; | |||||
placementSupport.position.x = snapData.x; | |||||
Done Inline ActionsGlobals should be defined at the top of the file, so that it's the first thing readers read, and then read the functions that operate on it. elexis: Globals should be defined at the top of the file, so that it's the first thing readers read… | |||||
Done Inline ActionsOk. Will be sent to input.js nani: Ok. Will be sent to input.js | |||||
placementSupport.position.z = snapData.z; | |||||
} | |||||
updateBuildingPlacementPreview(); | |||||
}, | |||||
"mousebuttonup": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
// If shift is down, let the player continue placing another of the same building | |||||
var queued = Engine.HotkeyIsPressed("session.queue"); | |||||
if (tryPlaceBuilding(queued)) | |||||
{ | |||||
if (queued) | |||||
this.SwitchToNextState("PLACEMENT.BUILDING"); | |||||
else | |||||
this.SwitchToNextState("NORMAL"); | |||||
} | |||||
else | |||||
Done Inline Actions-{} elexis: -{} | |||||
{ | |||||
this.SwitchToNextState("PLACEMENT.BUILDING"); | |||||
} | |||||
return true; | |||||
} | |||||
}, | |||||
Done Inline Actions+; elexis: +;
-\n\n | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
// Cancel building | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
} | |||||
}, | |||||
}, | |||||
"WALL": { | |||||
"enter": function () | |||||
{ | |||||
this.showBuildingPlacementTerrainSnap(mouseX, mouseY); | |||||
}, | |||||
"CLICK": { | |||||
// User is mid-click in choosing a starting point for building a wall. The build process can still be cancelled at this point | |||||
// by right-clicking; releasing the left mouse button will 'register' the starting point and commence endpoint choosing mode. | |||||
"mousebuttonup": function (msg) | |||||
{ | |||||
if (msg.ev.button === SDL_BUTTON_LEFT) | |||||
{ | |||||
this.SwitchToNextState("PLACEMENT.WALL.PATHING"); | |||||
return true; | |||||
} | |||||
}, | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
// Cancel building | |||||
updateBuildingPlacementPreview(); | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
Done Inline ActionsI would propose this indentation, since your proposal is 142 characters wide, https://trac.wildfiregames.com/wiki/Coding_Conventions proposed 80 characters, 120 characters is +50% that length, 142 is +78%: if (placementSupport.template && Engine.GuiInterfaceCall("GetNeededResources", { "cost": GetTemplateData(placementSupport.template).cost })) { ..... } elexis: I would propose this indentation, since your proposal is 142 characters wide, https://trac. | |||||
} | |||||
} | |||||
}, | |||||
"PATHING": { | |||||
// User has chosen a starting point for constructing the wall, and is now looking to set the endpoint. | |||||
// Right-clicking cancels wall building mode, left-clicking sets the endpoint and builds the wall and returns to | |||||
// normal input mode. Optionally, shift + left-clicking does not return to normal input, and instead allows the | |||||
// user to continue building walls. | |||||
"mousemotion": function (msg) | |||||
{ | |||||
placementSupport.wallEndPosition = Engine.GetTerrainAtScreenPoint(msg.ev.x, msg.ev.y); | |||||
// Update the building placement preview, and by extension, the list of snapping candidate entities for both (!) | |||||
// the ending point and the starting point to snap to. | |||||
// | |||||
// TODO: Note that here, we need to fetch all similar entities, including any offscreen ones, to support the case | |||||
// where the snap entity for the starting point has moved offscreen, or has been deleted/destroyed, or was a | |||||
// foundation and has been replaced with a completed entity since the user first chose it. Fetching all towers on | |||||
// the entire map instead of only the current screen might get expensive fast since walls all have a ton of towers | |||||
// in them. Might be useful to query only for entities within a certain range around the starting point and ending | |||||
// points. | |||||
placementSupport.wallSnapEntitiesIncludeOffscreen = true; | |||||
var result = updateBuildingPlacementPreview(); // includes an update of the snap entity candidates | |||||
if (result && result.cost) | |||||
{ | |||||
var neededResources = Engine.GuiInterfaceCall("GetNeededResources", { "cost": result.cost }); | |||||
placementSupport.tooltipMessage = [ | |||||
getEntityCostTooltip(result), | |||||
getNeededResourcesTooltip(neededResources) | |||||
].filter(tip => tip).join("\n"); | |||||
} | |||||
}, | |||||
"mousebuttondown": function (msg) | |||||
{ | |||||
if (msg.ev.button == SDL_BUTTON_LEFT) | |||||
{ | |||||
var queued = Engine.HotkeyIsPressed("session.queue"); | |||||
if (tryPlaceWall(queued)) | |||||
{ | |||||
if (queued) | |||||
{ | |||||
// continue building, just set a new starting position where we left off | |||||
placementSupport.position = placementSupport.wallEndPosition; | |||||
placementSupport.wallEndPosition = undefined; | |||||
this.SwitchToNextState("PLACEMENT.WALL.CLICK"); | |||||
} | |||||
else | |||||
{ | |||||
this.SwitchToNextState("NORMAL"); | |||||
} | |||||
} | |||||
else | |||||
placementSupport.tooltipMessage = translate("Cannot build wall here!"); | |||||
updateBuildingPlacementPreview(); | |||||
return true; | |||||
} | |||||
else if (msg.ev.button == SDL_BUTTON_RIGHT) | |||||
{ | |||||
// reset to normal input mode | |||||
updateBuildingPlacementPreview(); | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
InputEvents.prototype.showBuildingPlacementTerrainSnap = function (posX, posY) | |||||
{ | |||||
placementSupport.position = Engine.GetTerrainAtScreenPoint(posX, posY); | |||||
if (placementSupport.mode === "wall") | |||||
{ | |||||
// Including only the on-screen towers in the next snap candidate list is sufficient here, since the user is | |||||
// still selecting a starting point (which must necessarily be on-screen). (The update of the snap entities | |||||
// itself happens in the call to updateBuildingPlacementPreview below). | |||||
placementSupport.wallSnapEntitiesIncludeOffscreen = false; | |||||
} | |||||
else | |||||
{ | |||||
// cancel if not enough resources | |||||
if (placementSupport.template && Engine.GuiInterfaceCall("GetNeededResources", { "cost": GetTemplateData(placementSupport.template).cost })) | |||||
{ | |||||
this.SwitchToNextState("NORMAL"); | |||||
return true; | |||||
} | |||||
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", { | |||||
"template": placementSupport.template, | |||||
"x": placementSupport.position.x, | |||||
"z": placementSupport.position.z, | |||||
}); | |||||
if (snapData) | |||||
{ | |||||
placementSupport.angle = snapData.angle; | |||||
placementSupport.position.x = snapData.x; | |||||
placementSupport.position.z = snapData.z; | |||||
} | |||||
} | |||||
updateBuildingPlacementPreview(); // includes an update of the snap entity candidates | |||||
} | |||||
let g_InputEvents = new InputEvents("NORMAL"); |
I wonder whether we can find a more expressive name than input events.
Input events are keypresses and mousemoves, but the logic in this file relates more to the logic of unit actions, building placement, etc...
The name should imply a clear definition to inform the reader what code can be expected to be found in this file, and prevents authors from adding unrelated stuff (clearly not everything that relates to input events is in this file, prevent spaghetti)