diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java index 954910297..2ce960604 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java @@ -1,13 +1,18 @@ package com.gpl.rpg.AndorsTrail.controller; +import android.content.Context; +import android.content.Intent; +import android.util.SparseIntArray; import android.view.KeyEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; +import com.gpl.rpg.AndorsTrail.activity.HeroinfoActivity; 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,62 +24,258 @@ 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; + + final private int KEY_UNHANDLED = 0; // Default, for unhandled keycodes + final private int KEY_MOVE_UP = 1; + final private int KEY_MOVE_DOWN = 2; + final private int KEY_MOVE_LEFT = 3; + final private int KEY_MOVE_RIGHT = 4; + final private int KEY_MOVE_UP_LEFT = 5; + final private int KEY_MOVE_UP_RIGHT = 6; + final private int KEY_MOVE_DOWN_LEFT = 7; + final private int KEY_MOVE_DOWN_RIGHT = 8; + final private int KEY_ATTACK = 9; + final private int KEY_FLEE = 10; + final private int KEY_END_TURN = 11; + final private int KEY_HERO_INFO = 12; + final private int KEY_TOOLBOX = 13; + + private SparseIntArray keyMap = new SparseIntArray(); // Android keycode to internal key event mapping. TODO: Configure via preferences + public InputController(ControllerContext controllers, WorldContext world) { this.controllers = controllers; this.world = world; + initializeKeyMap(); + + } + +/* New keyboard handler. Both Key Down and Key Up events handled here 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 without locking kludge? + */ + + // Map key codes to spectic internal actions + // TODO: Move mapping out of code to JSON/XML file, or maybe player prefs + private void initializeKeyMap() { + int key; + + // Keys mapping to UP + key = KEY_MOVE_UP; + keyMap.put(KeyEvent.KEYCODE_DPAD_UP, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_8, key); + keyMap.put(KeyEvent.KEYCODE_8, key); + keyMap.put(KeyEvent.KEYCODE_W, key); + + // Keys mapping to DOWN + key = KEY_MOVE_DOWN; + keyMap.put(KeyEvent.KEYCODE_DPAD_DOWN, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_2, key); + keyMap.put(KeyEvent.KEYCODE_2, key); + keyMap.put(KeyEvent.KEYCODE_S, key); + + // Keys mapping to LEFT + key = KEY_MOVE_LEFT; + keyMap.put(KeyEvent.KEYCODE_DPAD_LEFT, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_4, key); + keyMap.put(KeyEvent.KEYCODE_4, key); + keyMap.put(KeyEvent.KEYCODE_A, key); + + // Keys mapping to RIGHT + key = KEY_MOVE_RIGHT; + keyMap.put(KeyEvent.KEYCODE_DPAD_RIGHT, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_6, key); + keyMap.put(KeyEvent.KEYCODE_6, key); + keyMap.put(KeyEvent.KEYCODE_D, key); + + // Keys mapping to UP_LEFT + key = KEY_MOVE_UP_LEFT; + keyMap.put(KeyEvent.KEYCODE_DPAD_UP_LEFT, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_7, key); + keyMap.put(KeyEvent.KEYCODE_7, key); + keyMap.put(KeyEvent.KEYCODE_MOVE_HOME, key); + + // Keys mapping to UP_RIGHT + key = KEY_MOVE_UP_RIGHT; + keyMap.put(KeyEvent.KEYCODE_DPAD_UP_RIGHT, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_9, key); + keyMap.put(KeyEvent.KEYCODE_9, key); + keyMap.put(KeyEvent.KEYCODE_PAGE_UP, key); + + // Keys mapping to DOWN_LEFT + key = KEY_MOVE_DOWN_LEFT; + keyMap.put(KeyEvent.KEYCODE_DPAD_DOWN_LEFT, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_1, key); + keyMap.put(KeyEvent.KEYCODE_1, key); + keyMap.put(KeyEvent.KEYCODE_MOVE_END, key); + + // Keys mapping to DOWN_RIGHT + key = KEY_MOVE_DOWN_RIGHT; + keyMap.put(KeyEvent.KEYCODE_DPAD_DOWN_RIGHT, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_3, key); + keyMap.put(KeyEvent.KEYCODE_3, key); + keyMap.put(KeyEvent.KEYCODE_PAGE_DOWN, key); + + // Keys mapping to ATTACK + key = KEY_ATTACK; + keyMap.put(KeyEvent.KEYCODE_DPAD_CENTER, key); + keyMap.put(KeyEvent.KEYCODE_BUTTON_A, key); + keyMap.put(KeyEvent.KEYCODE_SPACE, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_5, key); + + // Keys mapping to FLEE + key = KEY_FLEE; + keyMap.put(KeyEvent.KEYCODE_BUTTON_X, key); + keyMap.put(KeyEvent.KEYCODE_F, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_ENTER, key); + keyMap.put(KeyEvent.KEYCODE_ENTER, key); + + // Keys mapping to END_TURN + key = KEY_END_TURN; + keyMap.put(KeyEvent.KEYCODE_BUTTON_Y, key); + keyMap.put(KeyEvent.KEYCODE_E, key); + keyMap.put(KeyEvent.KEYCODE_FORWARD_DEL, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_DOT, key); + + // Keys mapping to HERO_INFO + key = KEY_HERO_INFO; + //keyMap.put(KeyEvent.KEYCODE_BUTTON_SELECT, key); + keyMap.put(KeyEvent.KEYCODE_BUTTON_L1, key); + keyMap.put(KeyEvent.KEYCODE_NUM_LOCK, key); + keyMap.put(KeyEvent.KEYCODE_C, key); + + // Keys mapping to TOOLBOX + key = KEY_TOOLBOX; + keyMap.put(KeyEvent.KEYCODE_BUTTON_R1, key); + keyMap.put(KeyEvent.KEYCODE_NUMPAD_DIVIDE, key); + keyMap.put(KeyEvent.KEYCODE_B, key); } - public boolean onKeyboardAction(int keyCode) { - switch (keyCode) { - case KeyEvent.KEYCODE_NUMPAD_8: - case KeyEvent.KEYCODE_DPAD_UP: - case KeyEvent.KEYCODE_W: - onRelativeMovement(0, -1); - return true; - case KeyEvent.KEYCODE_NUMPAD_2: - case KeyEvent.KEYCODE_DPAD_DOWN: - case KeyEvent.KEYCODE_S: - onRelativeMovement(0, 1); - return true; - case KeyEvent.KEYCODE_NUMPAD_4: - case KeyEvent.KEYCODE_DPAD_LEFT: - case KeyEvent.KEYCODE_A: - onRelativeMovement(-1, 0); - return true; - case KeyEvent.KEYCODE_NUMPAD_6: - case KeyEvent.KEYCODE_DPAD_RIGHT: - case KeyEvent.KEYCODE_D: - onRelativeMovement(1, 0); - return true; - case KeyEvent.KEYCODE_NUMPAD_7: - case KeyEvent.KEYCODE_DPAD_UP_LEFT: - onRelativeMovement(-1, -1); - return true; - case KeyEvent.KEYCODE_NUMPAD_9: - case KeyEvent.KEYCODE_DPAD_UP_RIGHT: - onRelativeMovement(1, -1); - return true; - case KeyEvent.KEYCODE_NUMPAD_1: - case KeyEvent.KEYCODE_DPAD_DOWN_LEFT: - onRelativeMovement(-1, 1); - return true; - case KeyEvent.KEYCODE_NUMPAD_3: - case KeyEvent.KEYCODE_DPAD_DOWN_RIGHT: - onRelativeMovement(1, 1); - return true; - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_SPACE: - onRelativeMovement(0, 0); - return true; - default: - return false; + // Generate game actions based on mapped keys + public boolean onKeyboardAction(Context context, KeyEvent event, boolean acceptInput) { + //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 + boolean keydown = (event.getAction() == KeyEvent.ACTION_DOWN); + boolean inihbit = (keyState_attack || keyState_flee); // used to inhibit movement if an action key is held down + + switch (keyMap.get(event.getKeyCode())) { + + // Ordinal directional keys - only modify one direction register; registers are combined when + // keys used simultaneously to create synthetic diagonals + case KEY_MOVE_UP: + keyState_dy = keydown ? -1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + break; + case KEY_MOVE_DOWN: + keyState_dy = keydown ? 1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + break; + case KEY_MOVE_LEFT: + keyState_dx = keydown ? -1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + break; + case KEY_MOVE_RIGHT: + keyState_dx = keydown ? 1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + 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 KEY_MOVE_UP_LEFT: + keyState_dx = keydown ? -1 : 0; + keyState_dy = keydown ? -1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + break; + case KEY_MOVE_UP_RIGHT: + keyState_dx = keydown ? 1 : 0; + keyState_dy = keydown ? -1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + break; + case KEY_MOVE_DOWN_LEFT: + keyState_dx = keydown ? -1 : 0; + keyState_dy = keydown ? 1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + break; + case KEY_MOVE_DOWN_RIGHT: + keyState_dx = keydown ? 1 : 0; + keyState_dy = keydown ? 1 : 0; + if (acceptInput && !inihbit) onRelativeMovement(keyState_dx, keyState_dy); + break; + + // Special key handling below - some combat/movement stuff done here because it's too + // specific for logic in onRelativeMovement + + // "Attack" shortcut - freeze movement to allow chorded direction when key is released. + // if in combat, executes an attack on key release + case KEY_ATTACK: + if (keydown && !keyState_attack) { // key pressed - pause any movement + if(!world.model.uiSelections.isInCombat) controllers.movementController.stopMovement(); + } else if (!keydown && keyState_attack) { // key released - execute attack / move in direction + if (acceptInput) onRelativeMovement(keyState_dx, keyState_dy); + } + keyState_attack = keydown; + break; + + // "Flee" shortcut. Intitiates flee when pressed. If a direction is held, moves in chosen direction when released + case KEY_FLEE: + if (world.model.uiSelections.isInCombat) { + if (keydown && !keyState_flee) { // button pressed - set flee; movement locked while pressed + if(acceptInput) controllers.combatController.startFlee(); + } else if (!keydown && keyState_flee) { // button released - move flee direction, if held + // We need to do a special call because the movement key may already be down, and if the device + // doesn't generate repeat keystrokes, this handler won't get another event + if ((keyState_dx != 0 || keyState_dy != 0) && allowInputInterval()) { + if(acceptInput) controllers.combatController.executeMoveAttack(keyState_dx, keyState_dy); + } + } + } + keyState_flee = keydown; + break; + + // "End Turn" shortcut. Flag prevents repeated end turn if key is held down. + case KEY_END_TURN: + if (acceptInput && keydown && !keyState_endturn) { + if (world.model.uiSelections.isInCombat) controllers.combatController.endPlayerTurn(); + } + keyState_endturn = keydown; + break; + + // "Hero Info" screen shortcut. New activity takes focus, so we don't need to worry about repeats. + case KEY_HERO_INFO: + if (acceptInput && keydown) context.startActivity(new Intent(context, HeroinfoActivity.class)); + break; + + case KEY_TOOLBOX: +// ??? ToolboxView toolboxview = context.getApplicationContext(). findViewById(R.id.main_toolboxview); + + break; + + case KEY_UNHANDLED: // Unhandled keycode + return false; + + default: // unhandled keymap code entry (should not happen) + L.log("onKeyboardAction(): Unhandled keyMap code constant " + keyMap.get(event.getKeyCode()) + " for keyCode " + event.getKeyCode()); + return false; } + + return true; } public void onRelativeMovement(int dx, int dy) { + //L.log("onRelativeMovement(): dx=" + dx + " dy=" + dy + " combat: " + world.model.uiSelections.isInCombat); if (world.model.uiSelections.isInCombat) { - if (!allowInputInterval()) return; - controllers.combatController.executeMoveAttack(dx, dy); + if (allowInputInterval()) controllers.combatController.executeMoveAttack(dx, dy); + } else if (dx == 0 && dy == 0) { + controllers.movementController.stopMovement(); } else { controllers.movementController.startMovement(dx, dy, null); } diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java index 69cd77d99..89fbcff33 100644 --- a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java +++ b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/view/MainView.java @@ -134,20 +134,17 @@ public final class MainView extends SurfaceView } @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) { + // onKeyboardAction needs context to start new activities. + // canAcceptInput() checks done in handler so it can preserve keystate even if it does not have focus. + return inputController.onKeyboardAction(getContext(), 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) { + // Android provides artificial ACTION_UP events when focus changes; we process them to prevent "stuck key" effect after dialogs close + return inputController.onKeyboardAction(getContext(), keyEvent, canAcceptInput()) || super.onKeyUp(keyCode, keyEvent); } @Override