package inf101.v18.rogue101.objects; import inf101.v18.gfx.textmode.Printer; import inf101.v18.grid.GridDirection; import inf101.v18.grid.ILocation; import inf101.v18.rogue101.Main; import inf101.v18.rogue101.game.Game; import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.items.*; import inf101.v18.rogue101.shared.NPC; import inf101.v18.rogue101.states.Attack; import javafx.scene.input.KeyCode; import java.util.List; public class Player implements IPlayer { private int hp = getMaxHealth(); private int attack = 40; private int defence = 20; private final Backpack equipped = new Backpack(); // True if the player wants to pick up something using the digit keys private boolean dropping = false; // True if the player wants to drop something using the digit keys private boolean picking = false; private boolean writing = false; private boolean exploringChest = false; private String text = ""; private String name = "Player"; @Override public boolean keyPressed(IGame game, KeyCode key) { boolean turnConsumed = false; if (writing) { write(game, key); return false; } if (key == KeyCode.LEFT || key == KeyCode.A) { tryToMove(game, GridDirection.WEST); turnConsumed = true; } else if (key == KeyCode.RIGHT || key == KeyCode.D) { tryToMove(game, GridDirection.EAST); turnConsumed = true; } else if (key == KeyCode.UP || key == KeyCode.W) { tryToMove(game, GridDirection.NORTH); turnConsumed = true; } else if (key == KeyCode.DOWN || key == KeyCode.S) { tryToMove(game, GridDirection.SOUTH); turnConsumed = true; } else if (key == KeyCode.E) { turnConsumed = interactInit(game); } else if (key == KeyCode.Q) { turnConsumed = dropInit(game); } else if (key == KeyCode.C) { turnConsumed = useConsumable(game); } else if (key.isDigitKey()) { //Takes care of all digit keys. int keyValue = Integer.parseInt(key.getName()); if (keyValue == 0) { keyValue = 10; } if (dropping) { drop(game, keyValue - 1); dropping = false; turnConsumed = true; } else if (picking) { pickUp(game, keyValue - 1); picking = false; turnConsumed = true; } else if (exploringChest) { loot(game, keyValue - 1); exploringChest = false; turnConsumed = true; } } else if (key == KeyCode.N) { game.displayMessage("Please enter your name: "); writing = true; } else if (key == KeyCode.R) { turnConsumed = nonMeleeAttack(game, getVision(), Attack.RANGED, "ranged"); } else if (key == KeyCode.F) { turnConsumed = nonMeleeAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC, "magic"); } showStatus(game); return turnConsumed; } /** * Attacks if the user is able to use the desired attack. * * @param game An IGame object * @param range The range of the weapon * @param type The type of the attack * @param weapon The required weapon type * @return True if an attack was possible. False otherwise */ private boolean nonMeleeAttack(IGame game, int range, Attack type, String weapon) { boolean turnConsumed = false; IItem item = null; switch (type) { case RANGED: item = getItem(IRangedWeapon.class); break; case MAGIC: item = getItem(IMagicWeapon.class); } if (item == null) { game.displayMessage("You do not have a " + weapon + " weapon."); } else { turnConsumed = rangedAttack(game, range, type); if (!turnConsumed) { game.displayMessage("No enemies within range."); } } return turnConsumed; } /** * Lets the user write his/her name. * * @param game An IGame object * @param key The key pressed */ private void write(IGame game, KeyCode key) { if (key == KeyCode.BACK_SPACE) { if (text.length() > 0) { text = text.substring(0, text.length() - 1); game.displayMessage("Please enter your name: " + text); } } else if (key != KeyCode.ENTER) { text += key.impl_getChar(); //Deprecated, but kind of necessary game.displayMessage("Please enter your name: " + text); } else { name = text.toLowerCase(); text = ""; game.displayMessage("Name set."); writing = false; } } /** * Initializes the interaction with an item, and does what is needed. * * @param game An IGame object * @return True if a turn was consumed. False otherwise */ private boolean interactInit(IGame game) { List items = game.getLocalItems(); if (items.size() < 1) { game.displayMessage("There is nothing to pick up"); } else { if (items.get(0) instanceof IStatic && items.get(0) instanceof IContainer) { //A Static item is always the biggest (and hopefully the only) item on a tile. openChest(game, items); } else if (items.get(0) instanceof IStatic && items.get(0) instanceof StairsUp) { ((Game)game).goUp(); return true; } else if (items.get(0) instanceof IStatic && items.get(0) instanceof StairsDown) { ((Game)game).goDown(); return true; } else { if (items.size() == 1) { pickUp(game, 0); return true; } else { game.displayMessage("Items on this tile:" + niceList(items, 5)); picking = true; } } } return false; } /** * Uses the first consumable from the player's inventory * * @param game An IGame object * @return True if a potion was used. False otherwise */ private boolean useConsumable(IGame game) { IConsumable consumable = (IConsumable) equipped.getFirst(IConsumable.class); if (consumable != null) { hp += consumable.hpIncrease(); if (hp > getMaxHealth()) { hp = getMaxHealth(); } attack += consumable.attackIncrease(); defence += consumable.defenceIncrease(); equipped.remove(consumable); game.displayMessage("Used " + consumable.getName()); return true; } else { game.displayMessage("You don't have any consumables."); return false; } } /** * Lists the contents of a chest, an makes the player ready to loot. * * @param game An IGame object * @param items A list of items on the current tile */ private void openChest(IGame game, List items) { IContainer container = (IContainer) items.get(0); items = container.getContent(); game.displayMessage(container.getInteractMessage() + niceList(items, 10)); exploringChest = true; } /** * Creates a string containing a set number of item names from a list. * * @param list The list of items * @param max The maximum number of items to print * @return A string */ private String niceList(List list, int max) { StringBuilder msg = new StringBuilder(); for (int i = 0; i < Math.min(list.size(), max); i++) { msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(list.get(i).getName())); } return msg.toString(); } /** * Initialized the dropping of an item, and does what is needed. * * @param game An IGame object * @return True if a turn was consumed. False otherwise */ private boolean dropInit(IGame game) { if (equipped.size() == 1) { drop(game, 0); return true; } else if (equipped.size() < 1) { game.displayMessage("You have nothing to drop"); } else { game.displayMessage("Items to drop:" + niceList(equipped.getContent(), equipped.getContent().size())); dropping = true; } return false; } /** * Picks up an item at index i. * * @param game An IGame object * @param i The wanted index */ private void pickUp(IGame game, int i) { if (equipped.hasSpace()) { List items = game.getLocalItems(); if (items.size() >= i) { IItem pickedUp = game.pickUp(items.get(i)); if (pickedUp != null) { equipped.add(pickedUp); game.displayMessage("Picked up " + pickedUp.getName()); } else { game.displayMessage("The item could not be picked up."); } } else { game.displayMessage("That item does not exist."); } } else { game.displayMessage("Your inventory is full."); } } /** * Takes an item from a chest, and gives it to the player. */ private void loot(IGame game, int i) { if (equipped.hasSpace()) { IContainer container = getStaticContainer(game); if (container != null && i < container.getContent().size()) { IItem loot = container.getContent().get(i); equipped.add(loot); container.remove(i); game.displayMessage("Looted " + loot.getName()); } } else { game.displayMessage("Your inventory is full."); } } /** * Gets any static containers on the current tile. * * @param game An IGame object * @return A static container */ private IContainer getStaticContainer(IGame game) { List items = game.getLocalItems(); if (items.size() < 1) { return null; } IItem item = items.get(0); if (item instanceof IStatic && item instanceof IContainer) { return (IContainer) item; } else { return null; } } /** * Drops an item at index i. * * @param game An IGame object * @param i The wanted index */ private void drop(IGame game, int i) { if (!equipped.isEmpty() && equipped.size() > i) { IContainer container = getStaticContainer(game); if (container != null) { if (container.addItem(equipped.get(i))) { game.displayMessage(equipped.get(i).getName() + " stored in " + container.getName()); equipped.remove(i); return; } else { game.displayMessage(container.getName() + " is full."); return; } } if (game.drop(equipped.get(i))) { equipped.remove(i); game.displayMessage("Item dropped."); } else { game.displayMessage("The ground rejected the item."); } } else { game.displayMessage("You have nothing to drop."); } } /** * Updates the status bar with the Player's current stats. * * @param game An IGame object */ private void showStatus(IGame game) { game.formatStatus("HP: %s ATK: %d RATK: %d MATK: %d DEF: %s DMG: %s RDMG: %s MDMG: %s", NPC.hpBar(this), getAttack(Attack.MELEE), getAttack(Attack.RANGED), getAttack(Attack.MAGIC), getDefence(), getDamage(Attack.MELEE), getDamage(Attack.RANGED), getDamage(Attack.MAGIC) ); printInventory(game); } /** * Prints all items in the player's inventory. * * @param game An IGame object */ private void printInventory(IGame game) { Printer printer = game.getPrinter(); List items = equipped.getContent(); printer.clearRegion(Main.COLUMN_RIGHTSIDE_START, 13, 45, 5); printer.printAt(Main.COLUMN_RIGHTSIDE_START, 12, "Inventory:"); for (int i = 0; i < items.size(); i++) { printer.printAt(Main.COLUMN_RIGHTSIDE_START, 13 + i, items.get(i).getName()); } } /** * Changes the first character in a string to uppercase. * * @param input The input string * @return The input string with the first character uppercased */ private String firstCharToUpper(String input) { if (input.length() < 1) { return input; } else { return Character.toUpperCase(input.charAt(0)) + input.substring(1); } } /** * Lets the user move or attack if possible. * * @param game An IGame object * @param dir The direction the player wants to go */ private void tryToMove(IGame game, GridDirection dir) { ILocation loc = game.getLocation(); if (game.canGo(dir)) { game.move(dir); } else if (loc.canGo(dir) && game.getMap().hasActors(loc.go(dir))) { game.attack(dir, game.getMap().getActors(loc.go(dir)).get(0)); } else { game.displayMessage("Umm, it is not possible to move in that direction."); } } @Override public int getAttack() { IBuffItem buff = (IBuffItem)getItem(IBuffItem.class); if (buff != null) { return attack + buff.getBuffDamage(); } else { return attack; } } /** * Gets the attack with added weapon attack * * @param type The attack type corresponding to the weapon type. * @return Attack as int */ private int getAttack(Attack type) { IWeapon weapon = NPC.getWeapon(type, this); if (weapon != null) { return getAttack() + weapon.getWeaponAttack(); } else { return getAttack(); } } @Override public int getDamage() { return 10; } /** * Gets the damage with added weapon damage * * @param type The attack type corresponding to the weapon type. * @return Damage as int */ private int getDamage(Attack type) { IWeapon weapon = NPC.getWeapon(type, this); if (weapon != null) { return getDamage() + weapon.getWeaponDamage(); } else { return getDamage(); } } @Override public IItem getItem(Class type) { for (IItem item : equipped.getContent()) { if (type.isInstance(item)) { return item; } } return null; } @Override public int getCurrentHealth() { return hp; } @Override public int getDefence() { IBuffItem buff = (IBuffItem)getItem(IBuffItem.class); if (buff != null) { return defence + buff.getBuffDamage(); } else { return defence; } } @Override public int getMaxHealth() { return 1000; } @Override public String getName() { return firstCharToUpper(name); } @Override public int getSize() { return 10; } @Override public String getPrintSymbol() { return "\u001b[96m" + "@" + "\u001b[0m"; } @Override public String getSymbol() { return "@"; } @Override public int handleDamage(IGame game, IItem source, int amount) { hp -= amount; showStatus(game); if (hp < 1) { game.displayStatus("Game Over"); } return amount; } @Override public int getVision() { IBuffItem item = (IBuffItem) getItem(IBuffItem.class); if (item != null) { return 3 + item.getBuffVisibility(); } else { return 3; } } /** * Performs a ranged attack on the nearest visible enemy (if any); * * @param game An IGame object * @param range The range of the attack * @param type The attack type * @return True if the player attacked. False otherwise */ private boolean rangedAttack(IGame game, int range, Attack type) { List neighbours = game.getVisible(); for (ILocation neighbour : neighbours) { if (game.getMap().hasActors(neighbour)) { ILocation current = game.getLocation(); List dirs = game.locationDirection(current, neighbour); IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor. if (actor instanceof INonPlayer && current.gridDistanceTo(neighbour) <= range) { GridDirection dir = dirs.get(0); //Only ever has one item game.rangedAttack(dir, actor, type); return true; } } } return false; } }