diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java index 16419f1fa..b39060712 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java @@ -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) { diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java index b7e52ee4e..e9e1ca700 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java @@ -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 view; - + public ScrollAnimationHandler(MainView view) { this.view = new WeakReference(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 view; private boolean stop = true; - + public SpriteMoveAnimationHandler(MainView view) { this.view = new WeakReference(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);