Redo keyboard input handling to allow diagonal movement using either keypad or two-key combos, and add keyboard/dpad shortcuts for Flee, Attack, and End Turn.

This commit is contained in:
guru_meditation_no42
2021-04-11 18:54:37 -07:00
parent 80f55d6d14
commit edd147e970
2 changed files with 215 additions and 97 deletions

View File

@@ -8,6 +8,7 @@ import android.view.View.OnLongClickListener;
import com.gpl.rpg.AndorsTrail.context.ControllerContext;
import com.gpl.rpg.AndorsTrail.context.WorldContext;
import com.gpl.rpg.AndorsTrail.util.Coord;
import com.gpl.rpg.AndorsTrail.util.L;
public final class InputController implements OnClickListener, OnLongClickListener{
private final ControllerContext controllers;
@@ -19,36 +20,160 @@ public final class InputController implements OnClickListener, OnLongClickListen
private long lastTouchEventTime = 0;
private boolean isDpadActive = false;
private int keyState_dx = 0;
private int keyState_dy = 0;
private boolean keyState_attack = false;
private boolean keyState_flee = false;
private boolean keyState_endturn = false;
public InputController(ControllerContext controllers, WorldContext world) {
this.controllers = controllers;
this.world = world;
}
public boolean onKeyboardAction(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_W:
onRelativeMovement(0, -1);
return true;
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_S:
onRelativeMovement(0, 1);
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_A:
onRelativeMovement(-1, 0);
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_D:
onRelativeMovement(1, 0);
return true;
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_SPACE:
onRelativeMovement(0, 0);
return true;
default:
return false;
/* New keyboard handler. Both Key Down and Key Up events handled to allow conditional behaviours.
On 4-way dpad controllers, cursor keys, and WASD, diagonals are generated by chording two keys.
Single-key diagonals are supported on numeric keypad and 8-way dpads (not seen/tested in the wild).
Because two-key combos initially generate a ordinal movement (one key comes in first), which can
be dangerous in tight spaces, modifiers are provided to "lock" the input until both keys are down.
TODO: Use delay timer to enable chorded diagonals on first move?
*/
public boolean onKeyboardAction(KeyEvent event, boolean canAcceptInput) {
// L.log("onKeyboardAction(): Processing action " + event.getAction() + " for keyCode " + event.getKeyCode());
if (event.getAction() != KeyEvent.ACTION_DOWN && event.getAction() != KeyEvent.ACTION_UP) return false; // don't handle other actions
// Android provides artificial ACTION_UP events when focus changes; we need them to prevent "stuck key" effect after dialogs close
if (!canAcceptInput && event.getAction() == KeyEvent.ACTION_DOWN) return false;
boolean keydown = (event.getAction() == KeyEvent.ACTION_DOWN);
boolean cancel = false; // used cancel final direction handling if already handled in switch()
switch (event.getKeyCode()) {
// Ordinal directional keys - only modify one direction register, can be combined when
// used simultaneously to create synthetic diagonals
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_NUMPAD_8:
case KeyEvent.KEYCODE_8:
case KeyEvent.KEYCODE_W:
keyState_dy = keydown ? -1 : 0;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_NUMPAD_2:
case KeyEvent.KEYCODE_2:
case KeyEvent.KEYCODE_S:
keyState_dy = keydown ? 1 : 0;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_NUMPAD_4:
case KeyEvent.KEYCODE_4:
case KeyEvent.KEYCODE_A:
keyState_dx = keydown ? -1 : 0;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_NUMPAD_6:
case KeyEvent.KEYCODE_6:
case KeyEvent.KEYCODE_D:
keyState_dx = keydown ? 1 : 0;
break;
// Diagonal directional keys. Modify both direction registers, can't be combined
// TODO: store individual key position to allow combinations. May not be worth the trouble.
case KeyEvent.KEYCODE_DPAD_UP_LEFT:
case KeyEvent.KEYCODE_NUMPAD_7:
case KeyEvent.KEYCODE_7:
case KeyEvent.KEYCODE_MOVE_HOME:
keyState_dx = keydown ? -1 : 0;
keyState_dy = keydown ? -1 : 0;
break;
case KeyEvent.KEYCODE_DPAD_UP_RIGHT:
case KeyEvent.KEYCODE_NUMPAD_9:
case KeyEvent.KEYCODE_9:
case KeyEvent.KEYCODE_PAGE_UP:
keyState_dx = keydown ? 1 : 0;
keyState_dy = keydown ? -1 : 0;
break;
case KeyEvent.KEYCODE_DPAD_DOWN_LEFT:
case KeyEvent.KEYCODE_NUMPAD_1:
case KeyEvent.KEYCODE_1:
case KeyEvent.KEYCODE_MOVE_END:
keyState_dx = keydown ? -1 : 0;
keyState_dy = keydown ? 1 : 0;
break;
case KeyEvent.KEYCODE_DPAD_DOWN_RIGHT:
case KeyEvent.KEYCODE_NUMPAD_3:
case KeyEvent.KEYCODE_3:
case KeyEvent.KEYCODE_PAGE_DOWN:
keyState_dx = keydown ? 1 : 0;
keyState_dy = keydown ? 1 : 0;
break;
// "Attack" shortcut - freeze movement to allow chorded direction when key is released.
// if in combat, executes an attack on key release
case KeyEvent.KEYCODE_DPAD_CENTER: // Not sure if this is needed
case KeyEvent.KEYCODE_BUTTON_A: // lock movement until released for precise directional move/attack
case KeyEvent.KEYCODE_SPACE:
case KeyEvent.KEYCODE_NUMPAD_5:
if (!keydown && keyState_attack) { // key released - execute attack
if(!world.model.uiSelections.isInCombat) {
controllers.movementController.stopMovement();
} else if (allowInputInterval()) {
controllers.combatController.executeMoveAttack(keyState_dx, keyState_dy);
}
}
keyState_attack = keydown; // prevents movement event below if pressed
cancel = false; //don't cancel, allow pending movement immediately on release via code below
break;
// "Flee" shortcut. Intitiates flee when pressed. If a direction is held, moves in chosen direction when released
case KeyEvent.KEYCODE_BUTTON_X:
case KeyEvent.KEYCODE_F:
case KeyEvent.KEYCODE_NUMPAD_ENTER:
case KeyEvent.KEYCODE_ENTER:
if (world.model.uiSelections.isInCombat) {
if (keydown && !keyState_flee) { // button pressed
controllers.combatController.startFlee();
} else if (!keydown && keyState_flee) { // button released - move flee direction, if chosen
// if no movement, executeMoveAttack() will just attack again, so we have to check and do it here
if ((keyState_dx != 0 || keyState_dy != 0) && allowInputInterval()) {
controllers.combatController.executeMoveAttack(keyState_dx, keyState_dy);
}
cancel = true;
}
}
keyState_flee = keydown;
break;
// "End Turn" shortcut. Prevents repeated end turn if key is held down.
case KeyEvent.KEYCODE_BUTTON_Y:
case KeyEvent.KEYCODE_E:
case KeyEvent.KEYCODE_FORWARD_DEL:
case KeyEvent.KEYCODE_NUMPAD_DOT:
if (keydown && !keyState_endturn) {
if (world.model.uiSelections.isInCombat) controllers.combatController.endPlayerTurn();
}
keyState_endturn = keydown;
cancel = true;
break;
default: // unhandled key
return false;
}
/* process movement if not already handled above and action modifier button is not down.
Modifiers allow input from 4-way controller or keyboard to settle (e.g., combined keys
for diagonals) before action is taken.
*/
if (!cancel && !keyState_attack && !keyState_endturn && !keyState_flee) {
if (world.model.uiSelections.isInCombat) {
if (keydown && allowInputInterval()) controllers.combatController.executeMoveAttack(keyState_dx, keyState_dy);
} else if (keyState_dx == 0 && keyState_dy == 0) {
controllers.movementController.stopMovement();
} else {
controllers.movementController.startMovement(keyState_dx, keyState_dy, null);
}
}
return true;
}
public void onRelativeMovement(int dx, int dy) {

View File

@@ -45,7 +45,7 @@ import android.view.SurfaceHolder;
import android.view.SurfaceView;
public final class MainView extends SurfaceView
implements SurfaceHolder.Callback,
implements SurfaceHolder.Callback,
PlayerMovementListener,
CombatSelectionListener,
MonsterSpawnListener,
@@ -63,7 +63,7 @@ public final class MainView extends SurfaceView
private final Coord mapTopLeft = new Coord(); // Map coords of visible map
private CoordRect mapViewArea; // Area in mapcoordinates containing the visible map. topleft == this.topleft
private Rect redrawClip = new Rect(); //Area in screen coordinates containing the visible map.
private final ModelContainer model;
private final WorldContext world;
private final ControllerContext controllers;
@@ -74,7 +74,7 @@ public final class MainView extends SurfaceView
private final Paint mPaint = new Paint();
private final CoordRect p1x1 = new CoordRect(new Coord(), new Size(1,1));
private boolean hasSurface = false;
//DEBUG
// private Coord touchedTile = null;
// private static Paint touchHighlight = new Paint();
@@ -88,15 +88,15 @@ public final class MainView extends SurfaceView
// redrawHighlight.setStrokeWidth(0f);
// redrawHighlight.setStyle(Style.STROKE);
// }
private PredefinedMap currentMap;
private LayeredTileMap currentTileMap;
private TileCollection tiles;
private final Coord playerPosition = new Coord();
private Size surfaceSize;
private boolean redrawNextTick = false;
private boolean scrolling = false;
private Coord scrollVector;
private long scrollStartTime;
@@ -120,39 +120,32 @@ public final class MainView extends SurfaceView
this.preferences = app.getPreferences();
alternateColorFilterPaint.setStyle(Style.FILL);
holder.addCallback(this);
setFocusable(true);
requestFocus();
setOnClickListener(this.inputController);
setOnLongClickListener(this.inputController);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent msg) {
if (!canAcceptInput()) return true;
if (inputController.onKeyboardAction(keyCode)) return true;
else return super.onKeyDown(keyCode, msg);
public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
return inputController.onKeyboardAction(keyEvent, canAcceptInput()) || super.onKeyDown(keyCode, keyEvent);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent msg) {
if (!canAcceptInput()) return true;
inputController.onKeyboardCancel();
return super.onKeyUp(keyCode, msg);
public boolean onKeyUp(int keyCode, KeyEvent keyEvent) {
return inputController.onKeyboardAction(keyEvent, canAcceptInput()) || super.onKeyUp(keyCode, keyEvent);
}
@Override
public void surfaceChanged(SurfaceHolder sh, int format, int w, int h) {
if (w <= 0 || h <= 0) return;
this.scale = world.tileManager.scale;
this.mPaint.setFilterBitmap(scale != 1);
this.scaledTileSize = world.tileManager.viewTileSize;
@@ -161,12 +154,12 @@ public final class MainView extends SurfaceView
this.screenSizeTileCount = new Size(
(int) Math.floor(getWidth() / scaledTileSize)
,(int) Math.floor(getHeight() / scaledTileSize)
);
);
if (sh.getSurfaceFrame().right != surfaceSize.width || sh.getSurfaceFrame().bottom != surfaceSize.height) {
sh.setFixedSize(surfaceSize.width, surfaceSize.height);
}
if (model.currentMaps.map != null) {
onPlayerEnteredNewMap(model.currentMaps.map, model.player.position);
} else {
@@ -190,22 +183,22 @@ public final class MainView extends SurfaceView
if (!canAcceptInput()) return true;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
final int tile_x = (int) Math.floor(((int)event.getX() - screenOffset.x * scale) / scaledTileSize) + mapTopLeft.x;
final int tile_y = (int) Math.floor(((int)event.getY() - screenOffset.y * scale) / scaledTileSize) + mapTopLeft.y;
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
final int tile_x = (int) Math.floor(((int)event.getX() - screenOffset.x * scale) / scaledTileSize) + mapTopLeft.x;
final int tile_y = (int) Math.floor(((int)event.getY() - screenOffset.y * scale) / scaledTileSize) + mapTopLeft.y;
// touchedTile = new Coord(tile_x, tile_y);
if (inputController.onTouchedTile(tile_x, tile_y)) return true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
inputController.onTouchCancel();
break;
if (inputController.onTouchedTile(tile_x, tile_y)) return true;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
inputController.onTouchCancel();
break;
}
return super.onTouchEvent(event);
}
private boolean canAcceptInput() {
if (!model.uiSelections.isMainActivityVisible) return false;
if (!hasSurface) return false;
@@ -241,14 +234,14 @@ public final class MainView extends SurfaceView
private void redrawArea_(CoordRect area, final VisualEffectAnimation effect, int tileID, int textYOffset) {
if (!hasSurface) return;
if (!currentMap.intersects(area)) return;
if (!mapViewArea.intersects(area)) return;
if (shouldRedrawEverything()) {
area = mapViewArea;
}
calculateRedrawRect(area);
redrawRect.intersect(redrawClip);
Canvas c = null;
@@ -264,7 +257,7 @@ public final class MainView extends SurfaceView
if (area == mapViewArea) {
area = adaptAreaToScrolling(area);
}
synchronized (holder) { synchronized (tiles) {
int xScroll = 0;
int yScroll = 0;
@@ -302,7 +295,7 @@ public final class MainView extends SurfaceView
if (c != null) holder.unlockCanvasAndPost(c);
}
}
private boolean isRedrawRectWholeScreen(Rect redrawRect) {
// if (redrawRect.width() < mapViewArea.size.width * scaledTileSize) return false;
// if (redrawRect.height() < mapViewArea.size.height * scaledTileSize) return false;
@@ -337,7 +330,7 @@ public final class MainView extends SurfaceView
}
private CoordRect adaptAreaToScrolling(final CoordRect area) {
if (!scrolling || scrollVector == null) return area;
int x, y, w, h;
@@ -355,10 +348,10 @@ public final class MainView extends SurfaceView
y = area.topLeft.y;
h = area.size.height - scrollVector.y;
}
CoordRect result = new CoordRect(new Coord(x, y), new Size(w, h));
CoordRect result = new CoordRect(new Coord(x, y), new Size(w, h));
return result;
}
private void calculateRedrawRect(final CoordRect area) {
worldCoordsToScreenCords(area, redrawRect);
}
@@ -368,13 +361,13 @@ public final class MainView extends SurfaceView
// destScreenRect.top = screenOffset.y + (worldArea.topLeft.y - mapViewArea.topLeft.y) * scaledTileSize;
// destScreenRect.right = destScreenRect.left + worldArea.size.width * scaledTileSize;
// destScreenRect.bottom = destScreenRect.top + worldArea.size.height * scaledTileSize;
destScreenRect.left = screenOffset.x + (worldArea.topLeft.x - mapViewArea.topLeft.x) * tileSize;
destScreenRect.top = screenOffset.y + (worldArea.topLeft.y - mapViewArea.topLeft.y) * tileSize;
destScreenRect.right = destScreenRect.left + worldArea.size.width * tileSize;
destScreenRect.bottom = destScreenRect.top + worldArea.size.height * tileSize;
}
// private void worldCoordsToBitmapCoords(final CoordRect worldArea, Rect dstBitmapArea) {
// dstBitmapArea.left = worldArea.topLeft.x * tileSize;
// dstBitmapArea.top = worldArea.topLeft.y * tileSize;
@@ -391,17 +384,17 @@ public final class MainView extends SurfaceView
applyAlternateFilter(canvas, area);
}
}
private void doDrawRect_Ground(Canvas canvas, CoordRect area) {
drawMapLayer(canvas, area, currentTileMap.currentLayout.layerGround);
tryDrawMapLayer(canvas, area, currentTileMap.currentLayout.layerObjects);
}
private void doDrawRect_Objects(Canvas canvas, CoordRect area) {
// if (!tryDrawMapBitmap(canvas, area, objectsBitmap)) {
// tryDrawMapLayer(canvas, area, currentTileMap.currentLayout.layerObjects);
// }
for (BloodSplatter splatter : currentMap.splatters) {
drawFromMapPosition(canvas, area, splatter.position, splatter.iconID);
}
@@ -435,11 +428,11 @@ public final class MainView extends SurfaceView
}
}
}
private void doDrawRect_Above(Canvas canvas, CoordRect area) {
tryDrawMapLayer(canvas, area, currentTileMap.currentLayout.layerAbove);
tryDrawMapLayer(canvas, area, currentTileMap.currentLayout.layerTop);
if (model.uiSelections.selectedPosition != null) {
if (model.uiSelections.selectedMonster != null) {
drawFromMapPosition(canvas, area, model.uiSelections.selectedPosition, TileManager.iconID_attackselect);
@@ -471,13 +464,13 @@ public final class MainView extends SurfaceView
}
}
}
private void applyAlternateFilter(Canvas canvas, CoordRect area) {
canvas.drawRect(canvas.getClipBounds(), alternateColorFilterPaint);
}
private void drawFromMapPosition(Canvas canvas, final CoordRect area, final Coord p, final int tile) {
if (!area.contains(p)) return;
_drawFromMapPosition(canvas, area, p.x, p.y, tile);
@@ -491,7 +484,7 @@ public final class MainView extends SurfaceView
y -= mapViewArea.topLeft.y;
// if ( (x >= 0 && x < mapViewArea.size.width)
// && (y >= 0 && y < mapViewArea.size.height)) {
tiles.drawTile(canvas, tile, x * tileSize, y * tileSize, mPaint);
tiles.drawTile(canvas, tile, x * tileSize, y * tileSize, mPaint);
// }
}
@@ -512,7 +505,7 @@ public final class MainView extends SurfaceView
Size visibleNumberOfTiles = new Size(
Math.min(screenSizeTileCount.width, currentMap.size.width)
,Math.min(screenSizeTileCount.height, currentMap.size.height)
);
);
mapViewArea = new CoordRect(mapTopLeft, visibleNumberOfTiles);
updateClip();
@@ -520,18 +513,18 @@ public final class MainView extends SurfaceView
// (surfaceSize.width - scaledTileSize * visibleNumberOfTiles.width) / 2
// ,(surfaceSize.height - scaledTileSize * visibleNumberOfTiles.height) / 2
// );
screenOffset.set(
(surfaceSize.width - tileSize * visibleNumberOfTiles.width) / 2
,(surfaceSize.height - tileSize * visibleNumberOfTiles.height) / 2
);
);
useAlternateColorFilterPaint = currentTileMap.setColorFilter(this.mPaint, this.alternateColorFilterPaint, preferences.highQualityFilters);
}
// touchedTile = null;
clearCanvas();
recalculateMapTopLeft(model.player.position, false);
@@ -564,22 +557,22 @@ public final class MainView extends SurfaceView
}
}
}
private void updateClip() {
worldCoordsToScreenCords(mapViewArea, redrawClip);
}
public static final class ScrollAnimationHandler extends Handler implements Runnable {
private static final int FRAME_DURATION = 40;
private final WeakReference<MainView> view;
public ScrollAnimationHandler(MainView view) {
this.view = new WeakReference<MainView>(view);
}
@Override
public void run() {
MainView v = view.get();
@@ -613,18 +606,18 @@ public final class MainView extends SurfaceView
postDelayed(this, 0);
}
}
public static final class SpriteMoveAnimationHandler extends Handler implements Runnable {
private static final int FRAME_DURATION = 40;
private final WeakReference<MainView> view;
private boolean stop = true;
public SpriteMoveAnimationHandler(MainView view) {
this.view = new WeakReference<MainView>(view);
}
@Override
public void run() {
if (!stop) postDelayed(this, FRAME_DURATION);
@@ -656,14 +649,14 @@ public final class MainView extends SurfaceView
if (v.controllers.preferences.enableUiAnimations) postDelayed(this, 0);
}
}
public void stop() {
stop = true;
}
}
@Override
public void onPlayerMoved(PredefinedMap map, Coord newPosition, Coord previousPosition) {
if (map != currentMap) return;
@@ -794,19 +787,19 @@ public final class MainView extends SurfaceView
movingSpritesRedrawTick.start();
}
}
@Override
public void onNewSpriteMoveFrame(SpriteMoveAnimation animation) {
//redrawMoveArea_(CoordRect.getBoundingRect(animation.origin, animation.destination, animation.actor.tileSize), animation);
}
@Override
public void onSpriteMoveCompleted(SpriteMoveAnimation animation) {
if (animation.map != currentMap) return;
movingSprites--;
redrawArea(CoordRect.getBoundingRect(animation.origin, animation.destination, animation.actor.tileSize), RedrawAreaDebugReason.EffectCompleted);
}
@Override
public void onAsyncAreaUpdate(CoordRect area) {
redrawArea(area, RedrawAreaDebugReason.AsyncRequest);