Package creid.mythos.engine

Source Code of creid.mythos.engine.Mythos

/**************************************************************************************
* Mythos.java
* Runs the game
*
* Primary game "container" is the GameMap.
GameMap contains the layout(Cells), Terrain objects (used by all cells in the map), Actor objects, Field(reactive but not active) objects.
When a new Game is started, the all GameMaps are created as "shell" maps - they are given basic terrain info, exit data and assigned any fixed, custom content, but have no layout, monsters, or items.

The Vault stores static base information for generating Actors, Items, etc. The vault performs random generation and is static (information taken from data files)

GameWorld is the top container that is serialized/unserialized for save/load actions. It is the state model that encapsulates everything.
GameWorld contains:
  -All GameMap objects
  -All global "world" data - Quest states, the player object, etc
  -Exit data. Exits are paired up by a unqiue (for the pair) LinkID number so that when something takes an exit, the GameWorld can take the exit's GameMap and LinkID and determine both the GameMap to load as well as the cell to place them on.
 
  When changing maps, adjacent actors may come with the player, and nearby followers are also coming through. Actors remaining on the level receive full healing/curing and either return to their fixed location or are randomly placed.
  Actors switching maps join an entrance queue. The player is placed immediately, and as many creatures from the entrance queue will be placed as will fit in the adjacent cells to the player. Remaining creatures will be placed as these spaces become available.
 
The game architecture is as follows:
  -Mythos: Starts and updates the GUI, decides what to do with player input
  -GameWorld: the current game state
  -GameWindow: the GUI. Has drawFoo() functions that are called by the main thread to update the screen with the current map, player data, target data, message log, etc. The output side of the GUI is passive, not changing until told to do so. The input side is active, passing player commands to the handler function.
  -RNG, the RNG.
 
  Handler Functions: These methods of the main class determine what to do with the response from the user. There is a state variable that determines which handler is currently in use, and a parent handler which is always called by the GUI that uses this state variable to determine which handler reacts. Handlers include HandleAction (used when it is the Player's turn to act), HandleMenu (when there is a menu or text display open), HandleCursor (for looking, targeting)
 
DISPLAYING TEXT
-Panel to draw on (fullPanel to do the whole window, playPanel to just do the play area window, still showing player stats, logs, etc.
-Title
-Array of paragraphs (ColoredString[])
-first line displayed (int)
-last possible first line (int: array size - number of displayed lines on screen, min 1)

-When player goes up/down move screen up down by one, with pageup/space & pagedown shift first line and redraw the screen

DISPLAYING MENU
-Panel to draw on (fullPanel to do the whole window, playPanel to just do the play area window, still showing player stats, logs, etc.
-Title
-Instructions/general remarks at top of screen
-Array of choices to be shown (MenuItem[])
-whether to show a details panel (boolean)
-current choice (if showing details).

Details is basically a menu itself - it has title, remarks, array of MenuChoices
-on cancel, on choose commands (ints, used to navigate back/forwards, if there is a submenu that is forward command, if a target is needed

GENERAL PLAY
-Use keys to move/melee/switch. Shift+keys does forced action instead of moving (force attack1, close doors, etc)
-Player has ability slots bound to the number keys,-,=,`. These are shown, along with current pools, attack1/defense, etc.
-Speed has five settings, and mostly only applies to moving. Everything else takes one turn.
-Towns/bases are used to train skills / guild promotions.
-Player finds items and
*************************************************************************************/

package creid.mythos.engine;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;

import javax.swing.JFileChooser;

import creid.mythos.ui.GameUI;

public class Mythos implements KeyListener
{
  //Constants/////////////////////////////////////////////////////////////////////////

  //Title
  public static final String VERSION = "0.5.7.6";
  public static final String TITLE = "Mythos " + VERSION;
 
  //Dev or release
  public static final boolean RELEASE = false;
 
  //Debug settings
  public static final boolean DEBUG_MAP = false;
  public static final boolean DEBUG_NO_MOB_GEN = false;
  public static final boolean DEBUG_AUTO_CHARGEN = false;
 
  //Help context
  public static final int HELP_PLAYER_TURN = 0;
 
  //Commands
  public static final int NEW_GAME = KeyEvent.VK_N;
  public static final int LOAD_GAME = KeyEvent.VK_R;
  public static final int EXIT_QUIT = KeyEvent.VK_Q;
  public static final int EXIT_ESCAPE = KeyEvent.VK_ESCAPE;
 
  public static final int ANY_KEY = 0;
  public static final int MAIN_MENU = 1;
  public static final int PLAYER_ACTION = 2;
  public static final int CONFIRM = 3;
  public static final int TEXT_ENTRY = 4;
  public static final int MENU = 5;
  public static final int DIRECTION = 6;
 
  //Move commands
  public static final int MOVE_N = KeyEvent.VK_K;
  public static final int MOVE_N2 = KeyEvent.VK_UP;
  public static final int MOVE_N3 = KeyEvent.VK_NUMPAD8;
  public static final int MOVE_S = KeyEvent.VK_J;
  public static final int MOVE_S2 = KeyEvent.VK_DOWN;
  public static final int MOVE_S3 = KeyEvent.VK_NUMPAD2;
  public static final int MOVE_E = KeyEvent.VK_L;
  public static final int MOVE_E2 = KeyEvent.VK_RIGHT;
  public static final int MOVE_E3 = KeyEvent.VK_NUMPAD6;
  public static final int MOVE_W = KeyEvent.VK_H;
  public static final int MOVE_W2 = KeyEvent.VK_LEFT;
  public static final int MOVE_W3 = KeyEvent.VK_NUMPAD4;
  public static final int MOVE_NW = KeyEvent.VK_Y;
  public static final int MOVE_NW2 = KeyEvent.VK_NUMPAD7;
  public static final int MOVE_NE = KeyEvent.VK_U;
  public static final int MOVE_NE2 = KeyEvent.VK_NUMPAD9;
  public static final int MOVE_SW = KeyEvent.VK_B;
  public static final int MOVE_SW2 = KeyEvent.VK_NUMPAD1;
  public static final int MOVE_SE = KeyEvent.VK_N;
  public static final int MOVE_SE2 = KeyEvent.VK_NUMPAD3;
  public static final int WAIT = KeyEvent.VK_NUMPAD5;
  public static final int WAIT2 = KeyEvent.VK_PERIOD;
  public static final int OPERATE = KeyEvent.VK_O;
  public static final int GROUND_ACT = KeyEvent.VK_G;
  public static final int SWAP_WEAPONS = KeyEvent.VK_S;
  public static final int THROW = KeyEvent.VK_T;
  public static final int FIRE = KeyEvent.VK_F;
  public static final int RELOAD = KeyEvent.VK_R;

  //Misc Action commands
  public static final int EQUIPMENT = KeyEvent.VK_E;
  public static final int INVENTORY = KeyEvent.VK_I;
  public static final int NEXT_TARGET = KeyEvent.VK_TAB;

  //Meta/UI commands
  public static final int LOOK = KeyEvent.VK_X;
  public static final int SET_TARGET = KeyEvent.VK_T;
  public static final int SHOW_HELP = KeyEvent.VK_F1;
  public static final int SHOW_HELP2 = KeyEvent.VK_SLASH; //? when shifted
  public static final int SHOW_OLD_MESSAGES = KeyEvent.VK_P; //? when shifted
 
  public static final int CONFIRM_YES = KeyEvent.VK_Y;
  public static final int CONFIRM_NO = KeyEvent.VK_N;
  public static final int CONFIRM_DEFAULT = KeyEvent.VK_ENTER;
  public static final int CONFIRM_DEFAULT2 = KeyEvent.VK_SPACE;
  public static final int CANCEL = KeyEvent.VK_ESCAPE;
 
  public static final int BACKSPACE = KeyEvent.VK_BACK_SPACE;
  public static final int BACKSPACE2 = KeyEvent.VK_DELETE;
 
  public static final int SAVE_AND_QUIT = KeyEvent.VK_S;
 
  public static final int QUIT_SUICIDE = KeyEvent.VK_Q;

  public static final int ANIMATION_DELAY = 150//Delay in milliseconds between animation steps
 
  //Menu constants
  public static final int MENU_CANCELLED = -1;
  public static final int MENU_DETAIL_MULT = 1000;
  public static final boolean NUMBERED_MENU = true;
  public static final boolean LETTERED_MENU = false;
  public static final String MENU_HEADER_SEPARATOR = "============================================================================";
  public static final String MENU_INSTRUCTIONS = "Press the letter in parentheses to select, or Escape to cancel";
  public static final String MENU_INSTRUCTIONS_NUM = "Press the number in parentheses to select, or Escape to cancel";
  public static final String MENU_INSTRUCTIONS_CONFIRM = "Press Enter to confirm, any other key to cancel";
  public static final int[] SIMPLE_CONFIRM = { CONFIRM_YES, CONFIRM_NO };
 
  public static final String BIRTH_MENU_HEADER = "Choose a background. Your background determines starting attributes and equipment";
  public static final String INVENTORY_HEADER = "Select an item to view, use, or drop it.";
  public static final String EQUIPMENT_HEADER = "Select a slot to wear something or to take off the currently worn item";
  public static final String PICK_EQUIP_HEADER = "Select an item to wield or wear";
  public static final String THROW_HEADER = "Select an item to throw";
 
  public static final int INV_DROP = KeyEvent.VK_D;
  public static final int INV_USE = KeyEvent.VK_U;
 
  public static final String LOOK_INSTRUCTIONS = "Use arrow/WASD/numpad keys to move cursor, 't' to target, ESC to quit";
 
  //Attributes////////////////////////////////////////////////////////////////////////
 
  private static Random randomizer;
  public static MessageBuffer logger;
 
  //Game World
  private GameWorld game;
 
  //static Entity lists built from Data files:
  public static Actor[] Backgrounds;
  public static Cell[] TerrainTypes;
  public static Actor[] CreatureTypes;
  public static Item[] ItemTypes;
  public static MapSpecial[] MapSpecials;
 
  //GUI
  private GameUI gui;
 
  //Input stuff
  private KeyEvent input;
  private boolean shifted;
 
  private JFileChooser chooser;
 
  //Marks the end of a gaming session. Can't just use isDead(), because we can save and quit.
  boolean quitting;
  public static boolean winner;
 
  //Constructors//////////////////////////////////////////////////////////////////////
 
  public Mythos()
  {   
    winner = false;
   
   
    randomizer = new Random();
   
    logger = new MessageBuffer();
   
    //Initialize Display
    gui = new GameUI(TITLE);
    gui.setFocusable(true);
    if(!gui.hasFocus())
      gui.requestFocus();
   
    gui.addKeyListener(this);
   
    new Thread(gui).start();
   
    game = null; //Will be defined later
   
    input = null;
   
    chooser = new JFileChooser(System.getProperty("user.home") + File.separator + "MythosSavedGames");
   
    //Initialize entity type arrays
    TerrainTypes = FileHandler.loadTerrainTypes();
    ItemTypes = FileHandler.loadItems();
    Backgrounds = FileHandler.loadBackgrounds();
    CreatureTypes = FileHandler.loadActors();
    MapSpecials = FileHandler.loadSpecialRooms();
   
    quitting = true;
  }

  //Methods///////////////////////////////////////////////////////////////////////////
 
  public GameWorld getGame()
  {
    return game;
  }
 
  /**
   * Get the title page
   * @return title text
   */
  private String[] getTitleText()
  {
    String[] text = FileHandler.loadTextFromFile(FileHandler.TITLE_PAGE_FILE, false);
   
    //Add in the version number
    for (int i = 0; i < text.length; i++)
      if (text[i].contains("%VERSION%"))
        text[i] = text[i].replace("%VERSION%", VERSION);
   
    return text;
  }
 
  /**
   * get help text from file
   * @param context - what help file to show
   * @return text
   */
  private String[] getHelpText(int context)
  {
    //TODO: Load different help files in different situations
    String[] text = FileHandler.loadTextFromFile(FileHandler.MAIN_HELP_PAGE_FILE, false);
   
    //Add in the version number
    for (int i = 0; i < text.length; i++)
      if (text[i].contains("%VERSION%"))
        text[i] = text[i].replace("%VERSION%", VERSION);
   
    return text;
  }
 
  /**
   * Start a game or exit the program
   * @return game state
   */
  public void mainMenu()
  {
    int choice = 0;
   
    boolean loop = true;
    while (loop)
    {
      logger.clearBuffer();
      gui.setQuittingTime(false);
      gui.hideStatus();
      gui.showTitle(getTitleText());
      //Wait for player input
      choice = getInputCode(MAIN_MENU);
      switch(choice)
      {
      case NEW_GAME:
        newGame();
        break;
      case LOAD_GAME:
        loadGame();
        break;
      case EXIT_ESCAPE:
      case EXIT_QUIT:
        loop = false;
        break;
      }
     
      //Quit all the way if asked to
      if (gui.isQuittingTime())
        loop = false;
    }
  }
 
  static String getSaveDirectoryPath()
  {
    //Warning: This property may not work consistently in older Windows systems
    //TODO: At least add check for consistency in Windows XP. Maybe just use a C:\mythossavedgames folder there?
    return System.getProperty("user.home") + File.separator + "MythosSavedGames";
  }
 
  private String getSaveFileName()
  {
    return getSaveDirectoryPath() + File.separator + game.getPC().getName(Entity.INDEFINITE) + "." + VERSION + ".save";
  }
 
  /**
   * removeSave
   * delete the save file
   */
  private void removeSave()
  {
    File save = new File(getSaveFileName());
   
    if (save.exists())
    {
      if(!save.delete())
      {
        Mythos.logger.errorLog("Error handling save file!");
      }
    }
  }
 
  /**
   * Save the current game state
   */
  public boolean saveGame()
  {
    File saveDirectory = new File(getSaveDirectoryPath());
   
    if (!saveDirectory.exists() && !saveDirectory.mkdir())
    {
      logger.logMessage("Failed to find/create savegame directory! Save aborted!");
      return false;
    }
   
    File save = new File(getSaveFileName());
   
    //Create the file if it doesn't exist
    if (!save.exists())
    {
      try
      {
        save.createNewFile();
      } catch (IOException e)
      {
        logger.logMessage("Failed to create save file! Save aborted!");
        e.printStackTrace();
        return false;
      }
    }
   
    //Open the file for writing
    ObjectOutputStream out;
   
    try
    {
      out = new ObjectOutputStream(new FileOutputStream(save));
      out.writeObject(game);
      out.close();
    } catch (IOException e)
    {
      logger.logMessage("Failure saving game! Save aborted!");
      e.printStackTrace();
      return false;
    }
   
    //If we get here, everything worked!
    return true;
  }
 
  /**
   * load a saved game stat
   */
  public void loadGame()
  {
    int returnVal = chooser.showOpenDialog(null);
    if (returnVal == JFileChooser.APPROVE_OPTION)
    {
      File file = chooser.getSelectedFile();
     
      try
      {
        FileInputStream fin = new FileInputStream(file);
       
        ObjectInputStream oin = new ObjectInputStream(fin);
       
        game = (GameWorld) oin.readObject();
       
        fin.close();
        oin.close();
       
        //Remove the old save file
        removeSave();
       
        quitting = false;
       
        if (game.getPC().hasProperty("WINNER"))
          winner = true;
        else
          winner = false;
       
        startGame();
       
      } catch (IOException | ClassNotFoundException e)
      {
        logger.logMessage("Error: Unable to load save game!");
        e.printStackTrace();
      }
    }
  }
 
  /**
   * quit the current game
   */
  public void quitGame()
  {
    //If we have a game in progress
    if (game != null)
      postmortem();
    else
      saveGame();
  }
 
  /**
   * showInventory
   * @return whether the player used their turn
   */
  private boolean showInventory()
  {
    Item[] inventory = game.getPC().listInventory();
   
    int response = MENU_CANCELLED;
   
    boolean tookTime = false;
   
    do
    {
      response = showMenu(INVENTORY_HEADER, getMenuChoices(inventory), LETTERED_MENU, buildChoiceDetails(inventory), "Press 'u' to use or 'd' to drop", new int[]{ KeyEvent.VK_U, KeyEvent.VK_D});
     
      int action = response / MENU_DETAIL_MULT;
      int which = response % MENU_DETAIL_MULT;
     
      //What did the player decide to do?
      switch (action)
      {
      case INV_DROP:
       
        boolean dropAll = true;
        //Is this a stack of items?
        if (game.getPC().seeItem(which).getAmount() > 1)
        {
          int amount = game.getPC().seeItem(which).getAmount();
         
          amount = Integer.parseInt(getText("Drop how many(" + amount + ")", String.valueOf(amount), true));
         
          //Zero is a bad amount
          if (amount < game.getPC().seeItem(which).getAmount())
          {
            dropAll = false;
          }

          //Drop a few
          if (amount > 0)
          {
            Item drop = new Item(game.getPC().seeItem(which));
            drop.setAmount(amount);
           
            if (game.getPC().getLocation().getMap().placeItem(drop, game.getPC().getLocation().getX(), game.getPC().getLocation().getX(), true))
            {
              tookTime = true;
              //Reduce the amount we still have
              game.getPC().seeItem(which).setAmount(game.getPC().seeItem(which).getAmount() - amount);
             
              //Sanity check, should never happen
              if (game.getPC().seeItem(which).getAmount() < 1)
                game.getPC().takeItem(which, true);
            }
            else
              Mythos.logger.logMessage("There is no room to drop " + game.getPC().seeItem(which).getName(Entity.DEFINITE));
          }
        }
       
        //If we dropped some don't go any further
        if (dropAll)
        {
          if (game.getPC().dropItem(which))
          {
            tookTime = true;
            response = MENU_CANCELLED;
          }
          else //Tell the player no
            Mythos.logger.logMessage("There is no room to drop " + game.getPC().seeItem(which).getName(Entity.DEFINITE));
        }
        break;
       
      case INV_USE:
        Item item = game.getPC().seeItem(which);
       
        if (item.getUse() == null)
        {
          Mythos.logger.logMessage("That item cannot be used");
          break;
        }
       
        //Use the item
        Cell target = null;
        if (game.getCurrentMap().getTarget() != null)
          target = game.getCurrentMap().getTarget().getLocation();
        tookTime = game.getPC().useItem(which, target);
       
        response = MENU_CANCELLED;
       
        break;
      }
    } while (response != MENU_CANCELLED);

    //Hide menu
    gui.hideTitle();
    gui.showMap(game.getCurrentMap());
   
    return tookTime;
  }
 
  /**
   * generate a new player & game from scratch
   */
  public void newGame()
  {
    winner = false;
    quitting = false;
    gui.hideTitle();
   
    //Create the player
    int background;
    String name;
   
    if (DEBUG_AUTO_CHARGEN)
    {
      background = 0;
      name = "Debugger";
    }
    else
    {
      name = getText("Name your character", "Player", false);
      background = showMenu(BIRTH_MENU_HEADER, getMenuChoices(Backgrounds),
          LETTERED_MENU, buildChoiceDetails(Backgrounds), "Are you sure? (y/N)", SIMPLE_CONFIRM);
    }
    //If user closed the window during birth, just quit
    if (gui.isQuittingTime())
      System.exit(0);
   
    Player pc = new Player(name, Backgrounds[background]);
   
    game = new GameWorld(pc);
    game.changeMap(0, 0);
   
    startGame();
  }
 
  private void startGame()
  {
    gui.showStatus();
    gui.showMap(game.getCurrentMap());
    gui.repaint();
   
    try
    {
      playGame();
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
      if (gui.isQuittingTime())
        quitting = true;
    }
  }
 
  private void takeTurn(Actor actor)
  {
    actor.setDoubleMove();
    actor.takeTurn();
   
    //If actor can still act
    if (actor.canDoubleMove())
      actor.takeTurn();
  }
 
  /**
   * Mythos game loop
   */
  public void playGame() throws Exception
  {
    gui.updatePCStatus(game.getPC());
    game.getCurrentMap().updateFOV(game.getPC().getLocation());
   
    Mythos.logger.setGui(gui);
    Mythos.logger.logTransientMessage("Welcome to Mythos!");
   
    Iterator<Actor> actors = null;
    while(!quitting)
    {
      //Process the player and allies
       actors = game.getCurrentMap().getPcs().iterator();
     
      while (!quitting && actors.hasNext())
      {
        Actor pc = actors.next();
       
        if (pc instanceof Player)
        {
          //loop until the player uses their turn
          while(playerCommand(getInputCode(ANY_KEY)));
         
          //Quit if player is dead
          if (pc.isDead())
            quitting = true;
          else
          {
            game.getCurrentMap().updateFOV(game.getPC().getLocation());
            game.getCurrentMap().updateDMap(GameMap.PC_PATH);
          }
         
          processProjections();
        }
        else
        {
          takeTurn(pc);
          processProjections();
        }
       
        gui.updateTarget();
        gui.updatePCStatus(game.getPC());
        gui.repaint();
      }
     
      //Place following creatures if we can
      game.getCurrentMap().placeFollowers();
      gui.repaint();
     
      //Stop if player closed window or died
      if (gui.isQuittingTime() ||game.getPC().isDead())
        break;
     
      game.getCurrentMap().updateDMap(GameMap.MOB_PATH);
      game.getCurrentMap().updateDMap(GameMap.SMART_MOB_PATH);
     
      //Now process the mobs
      actors = game.getCurrentMap().getMobs().iterator();
     
      while(!quitting && actors.hasNext())
      {
        takeTurn(actors.next());
        processProjections();
       
        //Quit if the player died
        if (game.getPC().isDead())
          quitting = true;
       
        gui.updatePCStatus(game.getPC());
        gui.updateTarget();
        gui.repaint();
      }
     
      //Update mob DMap for allies
      game.getCurrentMap().updateDMap(GameMap.ALLY_PATH);
      game.getCurrentMap().updateDMap(GameMap.SMART_ALLY_PATH);
    }
   
    //Let player see the screen before quitting.
    if (game.getPC().isDead())
    {
      postmortem();
    }
  }
 
  /**
   * postmortem: show the results of our game, finished or not
   */
  public void postmortem()
  {
    if (winner)
      logger.logMessage("You are a WINNER! Press any key to return to the menu");
    else
    {
      logger.logMessage("Game over. Press any key to return to the menu");
    }
    getInputCode(ANY_KEY);
  }
 
  /*
   * processProjections
   * Run any waiting projections
   */
  private void processProjections()
  {
    while(game.getCurrentMap().projectionsToRun())
    {
      Projection p = game.getCurrentMap().getNextProjection();
     
      while (p.canProject())
      {
        p.project();
       
        //Animate if projection is visible
        if (p.isSeen())
        {
          gui.updatePCStatus(game.getPC());
          gui.updateTarget();
          gui.repaint();
         
          //Pause so we can see the projection
          try
          {
            Thread.sleep(ANIMATION_DELAY);
          } catch (InterruptedException e) {}
        }
      }
     
      //Cleanup projection animation
      p.cleanup();
    }
     
  }
 
  /**
   * Player decides to end game
   * @return whether the player went through with it
   */
  private boolean suicide()
  {
    if (winner) //Player retires, not commits suicide
    {
      quitting = true;
      return false;
    }
    else
      //Get confirmation
      if (confirmCommand("Give up and end your game permanently? (Use 'S' to save and quit instead)", false))
        if (confirmCommand("Are you sure?", false)) //Are you really sure?
        {
          quitting = true;
          return false;
        }
   
    return true;
  }
 
  /**
   * display the help text
   */
  private void showHelp()
  {
    //Show the help file until the player presses a key
    gui.showTitle(getHelpText(HELP_PLAYER_TURN));
    getInputCode(ANY_KEY);
    gui.hideTitle();
    gui.showStatus();
    gui.showMap(game.getCurrentMap());
    gui.repaint();
  }
 
  /**
   * display message history
   */
  private void showMessages()
  {
    //Show the help file until the player presses a key
    gui.showTitle(logger.getHistory());
    getInputCode(ANY_KEY);
    gui.hideTitle();
    gui.showStatus();
    gui.showMap(game.getCurrentMap());
    gui.repaint();
  }
 
  /**
   * getMenuChoices
   * Helper for showMenu()
   * Build a list of menu choices from an array of entities
   * @param entities
   * @return
   */
  private String[] getMenuChoices(Entity[] entities)
  {
    String[] choices = new String[entities.length];
   
    for (int i = 0; i < choices.length; i++)
    {
      choices[i] = entities[i].getName(Entity.INDEFINITE);
    }
   
    return choices;
  }
 
  private String[][] buildChoiceDetails(Entity[] entities)
  {
    String[][] details = new String[entities.length][];
   
    for (int i = 0; i < details.length; i++)
    {
      details[i] = entities[i].getDetails();
    }
   
    return details;
  }
 
  /**
   * Helper function for showMenu()
   * @param menuHeader
   * @param menuChoices
   * @param menuStyle
   * @param details
   * @return
   */
  private String[] buildMenuText(String menuHeader, String[] menuChoices, boolean menuStyle, int choice, String[] details, String confirmInstructions)
  {
    //Indices used below
    int startChoices = 2;
    int endChoices = startChoices + menuChoices.length;
    int startDetails = endChoices + 1;
    int endDetails = startDetails;
    if (details != null)
      endDetails += details.length;
   
    //Determine size of string array
    int arraySize = endDetails + 2;
   
    //Prepare the text to show
    String[] show = new String[arraySize];
    show[0] = menuHeader;
    show[1] = MENU_HEADER_SEPARATOR;
   
    //Sanity check: don't have numbered list if > 10 items
    if (menuChoices.length > 10)
      menuStyle = LETTERED_MENU;
   
    //Add in our choices
    for (int i = 0; i < menuChoices.length; i++)
    {
      if (menuStyle == LETTERED_MENU)
        show[startChoices + i] = "(" + Character.valueOf((char)(KeyEvent.VK_A + i)) + ") " + menuChoices[i];
      else
        show[startChoices + i] = "(" + i + ") " + menuChoices[i];
    }
   
    //And finish with some explanatory text
    show[endChoices] = "";
    show[endDetails] = "";
   
    //If we are showing details, show them
    if (details != null)
    {
      show[endChoices+1] = " " + menuChoices[choice];
     
      for (int j = 0; j < details.length; j++)
        show[startDetails + j] = details[j];
     
     
      show[endDetails+1] = confirmInstructions; 
    }
    else if (menuStyle == NUMBERED_MENU)
      show[endDetails+1] = MENU_INSTRUCTIONS_NUM;
    else
      show[endDetails+1] = MENU_INSTRUCTIONS;
   
    return show;
  }
 
  /**
   * showMenu
   * Display a menu to the user and let them pick a choice
   * @param menuHeader: Text displayed at top of menu
   * @param menuChoices: list of lines displaying the menu options.
   * @param menuStyle: preface the choices with letters or numbers.
   * @param choiceDetails: String[] of text to be shown when an item is selected, in addition to a confirmation prompt. If null, no confirmation
   * is offered.
   * @return [users menu choice][users menu command], or MENU_CANCELLED if the user cancels out.
   */
  private int showMenu(String menuHeader, String[] menuChoices, boolean menuStyle, String[][] choiceDetails, String confirmInstructions, int[] detailCommands)
  {
    int choice = MENU_CANCELLED;
   
    //Show the initial choices
    gui.showTitle(buildMenuText(menuHeader, menuChoices, menuStyle, MENU_CANCELLED, null, null));
   
    boolean needsConfirm = true;
    if (choiceDetails == null)
      needsConfirm = false;
   
    //Loop until we get a valid choice or the player quits
    while (true)
    {
      if (gui.isQuittingTime())
        return MENU_CANCELLED;
     
      choice = getInputCode(MENU);
      boolean valid = false;

      //Validate input
      if (choice == CANCEL ||
          (menuStyle == NUMBERED_MENU && choice >= 0 && choice <= 9) ||
          (menuStyle == LETTERED_MENU && choice >= KeyEvent.VK_A && choice <= KeyEvent.VK_Z))
        valid = true;
     
      //Check list length
      if (valid)
      {
        //Convert to number 0-26
        if (menuStyle == LETTERED_MENU && choice != CANCEL)
          choice -= KeyEvent.VK_A;
       
        //Now make sure choice is not out-of-bounds
        if (choice != CANCEL && choice >= menuChoices.length)
          valid = false;
      }
     
      if (choice == CANCEL || gui.isQuittingTime())
        return MENU_CANCELLED;
     
      //We have a valid choice
      if (valid)
      {
        //Do we need to confirm the choice
        if(needsConfirm)
        {
          //Get confirmation
          gui.showTitle(buildMenuText(menuHeader, menuChoices, menuStyle, choice, choiceDetails[choice], confirmInstructions));
         
          int choice2 = MENU_CANCELLED;
          while(choice2 < 0)
          {
            choice2 = getInputCode(ANY_KEY);
           
            //If cancel, go back to the menu
            if (choice2 == CANCEL)
              break;
           
            //Check the choice against our list of allowed command values
            boolean pass = false;
            for (int i = 0; i < detailCommands.length; i++)
              if (choice2 == detailCommands[i])
              {
                pass = true;
                break;
              }
           
            if (pass)
              break;
           
            //Warn the player of invalid command
            logger.logTransientMessage("Invalid command. Enter a letter in parentheses or Escape to back up");
          }
         
          //Now if we cancelled, go back and try again
          if (choice2 == CANCEL || (detailCommands == SIMPLE_CONFIRM && choice2 == KeyEvent.VK_N))
          {
            gui.showTitle(buildMenuText(menuHeader, menuChoices, menuStyle, MENU_CANCELLED, null, null));
            continue;
          }
          else
          {
            choice += MENU_DETAIL_MULT * choice2;  //Add choice 2 to choice 1
            break;
          }
        }
        else //we are done
          break;
      }
     
      //If we get here the user entered a bad command
      logger.logTransientMessage("Invalid command. Choose a menu item or Escape to cancel");
    }
   
    //Clear confirmation for simple conf menus
    if (detailCommands == SIMPLE_CONFIRM)
      choice = choice % MENU_DETAIL_MULT;
     
    return choice;
  }
 
  /**
   * change to another map
   * @param mapID
   * @param exitID
   */
  private boolean changeMaps()
  {
    if (game.getPC().getLocation() instanceof Exit)
    {
      Exit ex = ((Exit)game.getPC().getLocation());
      int tMap = ex.getTargetMap();
      int tExit = ex.getTargetExit();
     
      game.changeMap(tMap, tExit);
     
      //Update Dmaps
      for (int dm = 0; dm < GameMap.DMAP_ARRAY_SIZE; dm++)
        game.getCurrentMap().updateDMap(dm);
     
      gui.showMap(game.getCurrentMap());
      return true;
    }
    else
    {
      logger.logMessage(game.getPC().getName() + " sees no exit here");
      return false;
    }
  }
 
  private boolean operateTerrain(int dx, int dy)
  {
    Cell targetCell;
   
    //If dx=dy=0, we are wanting "smart" behavior - find a direction for us, or ask if we're not sure
    if (dx == 0 && dy == 0)
    {
      //Get the adjacent cells to choose from
      LinkedList<Cell> aoc = new LinkedList<Cell>();
     
      for (int x = -1; x < 2; x++)
      {
        for (int y = -1; y < 2; y++)
        {
          //Skip start cell
          if (x == 0 && y == 0)
            continue;
         
          Cell c = game.getCurrentMap().getRelativeLocation(game.getPC().getLocation(), x, y);
          if (c.isOperable())
            aoc.add(c);
        }
      }

      //If there are no operable cells, abort
      if (aoc.size() == 0)
      {
        logger.logTransientMessage("You see nothing to operate here");
      }
      else if (aoc.size() == 1) //If there is only one, then choose it
      {
        dx = aoc.getFirst().getX() - game.getPC().getLocation().getX();
        dy = aoc.getFirst().getY() - game.getPC().getLocation().getY();
      }
      else //Ask the player for a direction
      {
        int direction = getDirection(null);
       
        dx = (direction/10) - 1;
        dy = (direction%10) - 1;
      }
    }
   
    //Now Operate the idicated cell
    dx += game.getPC().getLocation().getX();
    dy += game.getPC().getLocation().getY();
    targetCell = game.getCurrentMap().getLocation(dx, dy);
   
    if (targetCell == null)
    {
      logger.errorLog("operateTerrain: Received null from game.getCurrentMap().getLocation(" + dx + "," + dy +")");
      return false;
    }
   
    if (targetCell.getOperation() == Cell.NO_OP)
    {
      Mythos.logger.logTransientMessage("You see nothing to operate there");
      return false;
    }
    else return game.getPC().operateTerrain(targetCell);

    //return targetCell.operate(1);
  }
 
  private boolean reloadCommand()
  {
    //Must have a gun to shoot
    if (game.getPC().getEquipment(Player.MAINDHAND_SLOT) == null || game.getPC().getEquipment(Player.MAINDHAND_SLOT).getType() != Item.I_GUN)
    {
      Mythos.logger.logTransientMessage("You have nothing to shoot with!");
      return false;
    }
   
    Item weapon = game.getPC().getEquipment(Player.MAINDHAND_SLOT);

    //Make sure item has ammo property
    if (!weapon.hasProperty("ammo"))
    {
      Mythos.logger.errorLog("Item " + weapon.getName(Entity.DEFINITE) + " is of type gun but has no ammo property!");
      return false;
    }
   
    //Get example ammo item
    Item check = Mythos.ItemTypes[weapon.getProperty("ammo")];
   
    //Get inventory
    Item[] inventory = game.getPC().listInventory();
   
    //Cycle through the inventory to find the appropriate ammo
    int match = -1;
    for (int i = 0; i < inventory.length; i++)
    {
      if (inventory[i].compareTo(check) == 0)
      {
        match = i;
        break;
      }
    }
   
    //Is there any ammo?
    if (match < 0)
    {
      Mythos.logger.logTransientMessage("No ammo for " + weapon.getName(Entity.DEFINITE) + "!");
      return false;
    }
    else //load the ammo
    {
      int amount = weapon.getProperty("capacity") - weapon.getProperty("loaded");
      if (amount > inventory[match].getAmount())
        amount = inventory[match].getAmount();
     
      if (amount < 1)
      {
        Mythos.logger.logTransientMessage("No ammo for " + weapon.getName(Entity.DEFINITE) + "!");
        return false;
      }
     
      //Take from backpack
      if (amount == game.getPC().seeItem(match).getAmount())
        game.getPC().takeItem(match, true);
      else
        game.getPC().seeItem(match).setAmount(game.getPC().seeItem(match).getAmount() - amount);
     
      String ammoName = check.getPluralName();
      if (amount == 1)
        ammoName = check.getSingularName();
     
      weapon.setProperty("loaded", weapon.getProperty("loaded") + amount);
     
      logger.logMessage(game.getPC().getName(Entity.DEFINITE) + " loads " + amount + " " + ammoName + " into " + weapon.getName(Entity.DEFINITE));
     
      return true;
    }
  }
 
  private Cell getTarget()
  {
    //TODO: If we don't have a target, use a look-targeting command
    if (game.getCurrentMap().getTarget() == null)
    {
      lookAround();
     
      if (game.getCurrentMap().getTarget() == null)
        return null;
    }

    return game.getCurrentMap().getTarget().getLocation();
  }
 
  /**
   * THrow an item
   * @return whether the player took his turn (true) or cancelled
   */
  private boolean throwCommand()
  {
    Item[] inventory = game.getPC().listInventory();
    String[] choices = new String[inventory.length];
    for (int i = 0; i < choices.length; i++)
      choices[i] = inventory[i].getThrowName();
   
    //THROW_HEADER
    int choice = showMenu(THROW_HEADER, choices, LETTERED_MENU, null, null, null);
   
    //Hide menu
    gui.hideTitle();
    gui.showMap(game.getCurrentMap());
       
    //Cancel if asked
    if (choice == MENU_CANCELLED)
      return false;
   
    //Now pick a target if we don't have one
    Cell target = getTarget();
   
    //User cancelled
    if (target == null)
    {
      logger.logTransientMessage("Cancelled");
      return false;
    }
   
    //Throw at our target
    return game.getPC().throwItem(choice, target);
  }
 
  /**
   * shoot
   * Player shoots their weapon
   * @return
   */
  private boolean shootCommand()
  {
    //Must have a gun to shoot
    if (game.getPC().getEquipment(Player.MAINDHAND_SLOT) == null || game.getPC().getEquipment(Player.MAINDHAND_SLOT).getType() != Item.I_GUN)
    {
      Mythos.logger.logTransientMessage("You have nothing to shoot with!");
      return false;
    }
   
    Item weapon = game.getPC().getEquipment(Player.MAINDHAND_SLOT);

    //Make sure item has ammo property
    if (!weapon.hasProperty("ammo"))
    {
      Mythos.logger.errorLog("Item " + weapon.getName(Entity.DEFINITE) + " is of type gun but has no ammo property!");
      return false;
    }
   
    //Gun must have ammo to shoot
    if (!weapon.hasProperty("loaded"))
    {
      Mythos.logger.logTransientMessage("You are out of ammo!");
      return false;
    }
   
    //User cancelled
    Cell target = getTarget();
    if (target == null)
    {
      logger.logTransientMessage("Cancelled");
      return false;
    }
   
    //Create shot
    //TODO: Handle higher rates of fire
    Item shot = new Item(ItemTypes[weapon.getProperty("ammo")]);
   
    shot.setAmount(1);
    weapon.setProperty("loaded", weapon.getProperty("loaded") - shot.getAmount());
   
    shot.setProperty("firepower", weapon.getProperty("firepower") + game.getPC().getProperty("firepower"));
   
    return game.getPC().shoot(shot, target);
  }
 
  /**
   * main: Run the game
   */
  public static void main(String[] args)
  {
    Mythos yarg = new Mythos();
    yarg.mainMenu();
    System.exit(0);
  }

  //Input handlers///////////////////////////////////////////////////////////
 
  /**
   * Player command: turn a keypress into an action object for the player
   * @param input
   * @return
   */

  private boolean playerCommand(int command)
  {
    boolean stillPlayerTurn = true;
   
    //Movement commands
    int dx = 0;
    int dy = 0;
    boolean move = false;
       
    switch(command)
    {
    //Movement / Melee / Operate commands
    case MOVE_NW:
    case MOVE_NW2:
      dx--;
    case MOVE_N:
    case MOVE_N2:
    case MOVE_N3:
      dy--;
      move = true;
      break;
     
    case MOVE_SE:
    case MOVE_SE2:
      dx++;
    case MOVE_S:
    case MOVE_S2:
    case MOVE_S3:
      dy++;
      move = true;
      break;
     
    case MOVE_NE:
    case MOVE_NE2:
      dy--;
    case MOVE_E:
    case MOVE_E2:
    case MOVE_E3:
      dx++;
      move = true;
      break;
     
    case MOVE_SW:
    case MOVE_SW2:
      dy++;
    case MOVE_W:
    case MOVE_W2:
    case MOVE_W3:
      dx--;
      move = true;
      break;
     
    case WAIT:
    case WAIT2:
      move = true;
      break;
     
    //Shoot our weapon
    case FIRE:
      stillPlayerTurn = !shootCommand();
      break;
     
    case RELOAD:
      stillPlayerTurn = !reloadCommand();
      break;

    //Throw an item
    case THROW:
      stillPlayerTurn = !throwCommand();
      break;
     
    //Open inventory
    case INVENTORY:
      stillPlayerTurn = !showInventory();
      break;
     
    //List equipment
    case EQUIPMENT:
      stillPlayerTurn = !showEquipment();
      break;
     
    //Operate terrain (open/close doors. Set off traps in your square, etc)
    case OPERATE:
      if (operateTerrain(0,0))
      {
        stillPlayerTurn = false;
      }
      break;
     
    //Interact with ground: pick up item, change level, etc
    case GROUND_ACT:
      //Take stairs
      if (game.getPC().getLocation() instanceof Exit)
        changeMaps();
      //Pick up item
      else
      {
        stillPlayerTurn = !game.getPC().pickupItem();
       
        if (stillPlayerTurn)
        {
          Mythos.logger.logTransientMessage(game.getPC().getName() + " sees nothing to pick up!");
        }
      }
      break;
     
    //look around
    case LOOK:
      lookAround();
      stillPlayerTurn = true;
      break;
     
    //Show help text
    case SHOW_HELP2:
      if (!shifted)
        break;
    case SHOW_HELP:
      showHelp();
      break;
   
    //Previous messages
    case SHOW_OLD_MESSAGES:
      showMessages();
     
    //Select target
    case NEXT_TARGET:
      game.getCurrentMap().nextTarget();
      gui.updateTarget();
      gui.hideStatus();
      gui.showStatus();
      //gui.updatePCStatus(game.getPC());
      //gui.repaint();
      break;
     
    //Save game and quit
    case SAVE_AND_QUIT:
      if (!shifted)
      {
        game.getPC().swapWeapons();
        gui.updatePCStatus(game.getPC());
        break;
      }
    case EXIT_ESCAPE:
      //If player clicks close, save and quit
      if (gui.isQuittingTime() || confirmCommand("Save game and quit", false))
      {
        //Don't quit if the save fails for some reason
        if (saveGame())
        {
          quitting = true;
          stillPlayerTurn = false;
        }
      }
      break;
   
    //Give up and quit without saving.
    case QUIT_SUICIDE:
      if (!shifted)
        break;
        stillPlayerTurn = suicide();
      break;
       
    default:
      logger.logTransientMessage("Unknown command");
      stillPlayerTurn = true;
    }
   
    //Are we moving/meleeing/operating
    if (move)
    {
      //Don't use a turn if player tries to walk into a wall (unlike a monster, though monsters should never try that in the first place)
      stillPlayerTurn = !game.getPC().move(dx, dy);
    }
   
    return stillPlayerTurn;
  }
 
  private boolean showEquipment()
  {
    int slot = MENU_CANCELLED;
   
    boolean tookTime = false;
   
    //showMenu(String menuHeader, String[] menuChoices, boolean menuStyle, String[][] choiceDetails, String confirmInstructions, int[] detailCommands)
    slot = showMenu(EQUIPMENT_HEADER, game.getPC().getEquipmentMenu(), LETTERED_MENU, null, null, null);
   
    if (slot != MENU_CANCELLED)
    {
      //Did the player want to un-equip something or equip something?
      if (game.getPC().getEquipment(slot) == null)
      {
        int index = showMenu(PICK_EQUIP_HEADER, getMenuChoices(game.getPC().listInventory()), LETTERED_MENU, null, null, null);
       
        //ON cancelling, return to equipment list
        if (slot == MENU_CANCELLED)
          return showEquipment();
        else //equip the item
        {
          Item i = game.getPC().seeItem(index);
         
          if (game.getPC().canEquip(i, slot))
          {
           
            i = game.getPC().takeItem(index, false);
           
            tookTime = game.getPC().equipItem(i, slot);
           
            if (tookTime)
            {
              Mythos.logger.logMessage(game.getPC().getName(Entity.INDEFINITE) + " " + Player.EQUIP_VERB[slot] + " " + i.getName(Entity.DEFINITE));
            }
            else
              Mythos.logger.logMessage("That item cannot be worn there");
          }
          else
            Mythos.logger.logMessage("That item cannot be worn there");
        }
      }
      else //Remove selected equipment
      {
        Item removed = game.getPC().removeEquipment(slot);
        game.getPC().stowItem(removed); //put back in inventory
        Mythos.logger.logMessage(game.getPC().getName(Entity.INDEFINITE) + " " + Player.UNEQUIP_VERB[slot] + " " + removed.getName(Entity.DEFINITE));
        tookTime = true;
      }
    }
   
    //Hide menu
    gui.hideTitle();
    gui.showMap(game.getCurrentMap());
   
    return tookTime;
  }
 
 
 
  /**
   * lookAround
   * Look command
   */
  private void lookAround()
  {
    Mythos.logger.logMessage(LOOK_INSTRUCTIONS);
   
    int keypress = EXIT_ESCAPE;
    game.getCurrentMap().setHighlight(game.getPC().getLocation().getX(), game.getPC().getLocation().getY());
   
    Mythos.logger.logMessage(game.getCurrentMap().getHighlightedCellName());
   
    do
    {
      keypress = getInputCode(ANY_KEY);
      boolean describe = true;
     
      if (keypress != EXIT_ESCAPE)
      {
        int dx = 0;
        int dy = 0;
        //Highlight chosen cell
        switch(keypress)
        {
        case MOVE_NW:
        case MOVE_NW2:
          dx--;
        case MOVE_N:
        case MOVE_N2:
        case MOVE_N3:
          dy--;
          break;
         
        case MOVE_SE:
        case MOVE_SE2:
          dx++;
        case MOVE_S:
        case MOVE_S2:
        case MOVE_S3:
          dy++;
          break;
         
        case MOVE_NE:
        case MOVE_NE2:
          dy--;
        case MOVE_E:
        case MOVE_E2:
        case MOVE_E3:
          dx++;
          break;
         
        case MOVE_SW:
        case MOVE_SW2:
          dy++;
        case MOVE_W:
        case MOVE_W2:
        case MOVE_W3:
          dx--;
          break;
         
        case WAIT:
        case WAIT2:
          break;
         
        case SET_TARGET:
          Cell targetCell = game.getCurrentMap().getLocation(game.getCurrentMap().getHighlightX(),
              game.getCurrentMap().getHighlightY());
         
          if (targetCell.isOccupied())
          {
            Actor t = targetCell.getOccupant();
           
            boolean found = false;
            for (Actor m: game.getCurrentMap().getMobs())
              if (m == t) //actor is a mob, not an ally
              {
                found = true;
                game.getCurrentMap().setTarget(t);
                gui.updateTarget();
                gui.hideStatus();
                gui.showStatus();
                keypress = EXIT_ESCAPE; //quit the look interface
                gui.overwriteLastMessage("Target set");
                describe = false;
                break;
              }
           
            if (!found)
            {
              gui.overwriteLastMessage("You cannot target an ally!");
              describe = false;
            }
          }
          else
          {
            gui.overwriteLastMessage("There is nothing to target there");
            describe = false;
          }
          break;
        }
       
        game.getCurrentMap().changeHighlightX(dx);
        game.getCurrentMap().changeHighlightY(dy);
       
        //Describe what we see
        if (describe)
          gui.overwriteLastMessage(game.getCurrentMap().getHighlightedCellName());
      }
    } while(keypress != EXIT_ESCAPE);
   
    //Remove the highlighting
    game.getCurrentMap().removeHighlight();
    gui.repaint();
  }

  /**
   * getText: Interactive text prompt
   * @param prompt: Instructions for player
   * @param defaultText: if no text is entered when player hits "Enter", use this
   * @param numbersOnly: only allow numbers to be typed.
   * @return
   */
  private String getText(String prompt, String defaultText, boolean numbersOnly)
  {
    prompt += ": ";
    gui.showTransientMessage(prompt + defaultText);
    StringBuilder text = new StringBuilder();
   
    while(true)
    {
      int code = getInputCode(TEXT_ENTRY);
     
      if (code == CONFIRM_DEFAULT)
      {
        //If blank, use the default
        if (text.length() == 0)
          return defaultText;
        else
          return text.toString();
      }
      else if (code == CANCEL)
      {
        return defaultText;
      }
      else if (code == BACKSPACE || code == BACKSPACE2)
      {
        //Do nothing if no text
        if (text.length() == 0)
          continue;
       
        text.deleteCharAt(text.length() - 1);
        gui.showTransientMessage(prompt + text.toString());
      }
      else if (code >= KeyEvent.VK_A && code <= KeyEvent.VK_Z)
      {
        boolean alpha = false;
        switch(code)
        {
          case KeyEvent.VK_0:
            text.append('0');
            break;
          case KeyEvent.VK_1:
            text.append('1');
            break;
          case KeyEvent.VK_2:
            text.append('2');
            break;
          case KeyEvent.VK_3:
            text.append('3');
            break;
          case KeyEvent.VK_4:
            text.append('4');
            break;
          case KeyEvent.VK_5:
            text.append('5');
            break;
          case KeyEvent.VK_6:
            text.append('6');
            break;
          case KeyEvent.VK_7:
            text.append('7');
            break;
          case KeyEvent.VK_8:
            text.append('8');
            break;
          case KeyEvent.VK_9:
            text.append('9');
            break;
          default:
            alpha = true;
        }
       
        if (alpha && !numbersOnly)
        {
          switch(code)
          {
          case KeyEvent.VK_A:
            text.append(shifted ? 'A' : 'a');
            break;
          case KeyEvent.VK_B:
            text.append(shifted ? 'B' : 'b');
            break;
          case KeyEvent.VK_C:
            text.append(shifted ? 'C' : 'c');
            break;
          case KeyEvent.VK_D:
            text.append(shifted ? 'D' : 'd');
            break;
          case KeyEvent.VK_E:
            text.append(shifted ? 'E' : 'e');
            break;
          case KeyEvent.VK_F:
            text.append(shifted ? 'F' : 'f');
            break;
          case KeyEvent.VK_G:
            text.append(shifted ? 'G' : 'g');
            break;
          case KeyEvent.VK_H:
            text.append(shifted ? 'H' : 'h');
            break;
          case KeyEvent.VK_I:
            text.append(shifted ? 'I' : 'i');
            break;
          case KeyEvent.VK_J:
            text.append(shifted ? 'J' : 'j');
            break;
          case KeyEvent.VK_K:
            text.append(shifted ? 'K' : 'k');
            break;
          case KeyEvent.VK_L:
            text.append(shifted ? 'L' : 'l');
            break;
          case KeyEvent.VK_M:
            text.append(shifted ? 'M' : 'm');
            break;
          case KeyEvent.VK_N:
            text.append(shifted ? 'N' : 'n');
            break;
          case KeyEvent.VK_O:
            text.append(shifted ? 'O' : 'o');
            break;
          case KeyEvent.VK_P:
            text.append(shifted ? 'P' : 'p');
            break;
          case KeyEvent.VK_Q:
            text.append(shifted ? 'Q' : 'q');
            break;
          case KeyEvent.VK_R:
            text.append(shifted ? 'R' : 'r');
            break;
          case KeyEvent.VK_S:
            text.append(shifted ? 'S' : 's');
            break;
          case KeyEvent.VK_T:
            text.append(shifted ? 'T' : 't');
            break;
          case KeyEvent.VK_U:
            text.append(shifted ? 'U' : 'u');
            break;
          case KeyEvent.VK_V:
            text.append(shifted ? 'V' : 'v');
            break;
          case KeyEvent.VK_W:
            text.append(shifted ? 'W' : 'w');
            break;
          case KeyEvent.VK_X:
            text.append(shifted ? 'X' : 'x');
            break;
          case KeyEvent.VK_Y:
            text.append(shifted ? 'Y' : 'y');
            break;
          case KeyEvent.VK_Z:
            text.append(shifted ? 'Z' : 'z');
            break;
          }
        }
        gui.showTransientMessage(prompt + text.toString());
      }
      else
      {
        text.append(Character.toChars(code));
        gui.showTransientMessage(prompt + text.toString());
      }
    }
  }
 
  /**
   * getDirection: get a direction from the player
   * @param prompt
   * @return
   */
  private int getDirection(String prompt)
  {
    //Default prompt
    if (prompt == null)
      prompt = "Which direction?";
   
    gui.showTransientMessage(prompt);
   
    int dx = 1;
    int dy = 1;
   
    switch(getInputCode(DIRECTION))
    {
    case MOVE_NW:
    case MOVE_NW2:
      dx--;
    case MOVE_N:
    case MOVE_N2:
    case MOVE_N3:
      dy--;
      break;
     
    case MOVE_SE:
    case MOVE_SE2:
      dx++;
    case MOVE_S:
    case MOVE_S2:
    case MOVE_S3:
      dy++;
      break;
     
    case MOVE_NE:
    case MOVE_NE2:
      dy--;
    case MOVE_E:
    case MOVE_E2:
    case MOVE_E3:
      dx++;
      break;
     
    case MOVE_SW:
    case MOVE_SW2:
      dy++;
    case MOVE_W:
    case MOVE_W2:
    case MOVE_W3:
      dx--;
      break;

    case CANCEL:
      return -1;
    }
   
    return dx * 10 + dy;
  }
 
  private boolean confirmCommand(String message, boolean confirmByDefault)
  {
    boolean confirmation = confirmByDefault;
   
    if (confirmation)
      message += " (Y/n)?";
    else
      message += "(y/N)?";
   
    gui.showTransientMessage(message);
   
    switch(getInputCode(CONFIRM))
    {
    case CONFIRM_YES:
      confirmation = true;
      break;
    case CONFIRM_NO:
    case CANCEL:
      confirmation = false;
      break;
    default:
      //Leave with default confirmation
    }
   
    gui.showTransientMessage("");
    return confirmation;
  }
 
  private boolean isDirectionCommand(int command)
  {
    //TODO: Add cancel/back
    switch(command)
    {
    case MOVE_N:
    case MOVE_N2:
    case MOVE_N3:
    case MOVE_S:
    case MOVE_S2:
    case MOVE_S3:
    case MOVE_E:
    case MOVE_E2:
    case MOVE_E3:
    case MOVE_W:
    case MOVE_W2:
    case MOVE_W3:
    case MOVE_NW:
    case MOVE_NW2:
    case MOVE_NE:
    case MOVE_NE2:
    case MOVE_SW:
    case MOVE_SW2:
    case MOVE_SE:
    case MOVE_SE2:
    case WAIT:
    case WAIT2:
      return true;
    }
   
    return false;
  }
 
  //Get command
  private int getInputCode(int type)
  {
    int code = -99;
   
    //Wait for input
    while(input == null)
    {
      try
      {
        Thread.sleep(50);
      } catch (InterruptedException e) {}
     
      if (input != null)
        switch(type)
        {
        case CONFIRM:
          if (input.getKeyCode() != CONFIRM_YES && input.getKeyCode() != CONFIRM_NO
              && input.getKeyCode() != CONFIRM_DEFAULT && input.getKeyCode() != CONFIRM_DEFAULT2)
            input = null;
          break;
         
        case MENU:
          //Accept only escape or alphanumeric
          if (input.getKeyCode() != CANCEL && (input.getKeyCode() < KeyEvent.VK_0 || input.getKeyCode() > KeyEvent.VK_9 ) &&
              (input.getKeyCode() < KeyEvent.VK_A || input.getKeyCode() > KeyEvent.VK_Z))
            input = null;
          break;
         
        case DIRECTION:
          if (!isDirectionCommand(input.getKeyCode()))
            input = null;
          break;
         
        case ANY_KEY:
          break;
        }
     
      //If player closed the window, exit
      if (gui.isQuittingTime())
      {
        //game.getPC().quitting = true;
        return EXIT_ESCAPE;
      }
    }
   
    code = input.getKeyCode();
    input = null;
   
    return code;
  }
 
  @Override
  public void keyPressed(KeyEvent arg0)
  {
    if (arg0.getKeyCode() == KeyEvent.VK_SHIFT)
    {
      if (!shifted)
        shifted = true;
    }
    else
      input = arg0;
  }

  @Override
  public void keyReleased(KeyEvent arg0)
  {
    if (arg0.getKeyCode() == KeyEvent.VK_SHIFT)
      if (shifted)
        shifted = false;
  }

  @Override
  public void keyTyped(KeyEvent arg0)
  {
    ;
  }
 
  public static int callRNG(int min, int max)
  {
    if (min < 0)
    {
      min = 0;
    }
   
    if (min > max)
    {
      System.out.println("Invalid callRNG! Min > Max; reducing Min");
      min = max;
    }
   
    if (min == max)
      return max;
   
    //Finally, we can call the RNG
    return min + randomizer.nextInt(1 + max - min)//3-6 min=3 max = 6. 6-3 = 3. nextInt(3+1) gives 0-3, +3 = 3-6
  }
 
  public static int callRNG(int max)
  {
    return callRNG(0, max);
  }
 
  static Actor randomMob(int level)
  {
    LinkedList<Actor> bestiary = new LinkedList<Actor>();
   
    for (Actor a: CreatureTypes)
    {
      if (a.getLevel() <= level)
        for (int j = 0; j < a.getRarity(); j++)
          bestiary.add(a);
    }
   
    Actor pick = new Actor(bestiary.get(Mythos.callRNG(bestiary.size() - 1)));
   
    return pick;
  }
 
  static Item genItem(int id)
  {
    Item pick = new Item(ItemTypes[id]);
   
    //Some items come in stacks
    if (pick.hasProperty("stack") || pick.hasProperty("levelstack"))
    {
      pick.setAmount(Mythos.callRNG(1, pick.getProperty("stack")));
    }
   
    return pick;
  }
 
  static Item randomItem(int level)
  {
    LinkedList<Item> catalog = new LinkedList<Item>();
   
    for (Item i: ItemTypes)
    {
      if (i.getLevel() <= level)
      {
        for (int j = 0; j < i.getRarity(); j++)
          catalog.add(i);
      }
    }
   
    Item pick = genItem(catalog.get(Mythos.callRNG(catalog.size() - 1)).level);
   
    //Some items come in stacks
    if (pick.hasProperty("levelstack"))
    {
      int dice = level;
      int sides = pick.getProperty("levelstack");
      int stackSize = 0;
     
      while (dice > 0)
      {
        stackSize += Mythos.callRNG(1, sides);
        dice--;
      }
     
      pick.setAmount(stackSize);
    }
   
    return pick;
  }
}
TOP

Related Classes of creid.mythos.engine.Mythos

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.