package inf101.v18.rogue101.objects; import inf101.v18.grid.GridDirection; import inf101.v18.grid.ILocation; import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.items.*; import inf101.v18.rogue101.shared.NPC; import javafx.scene.input.KeyCode; import java.util.ArrayList; import java.util.List; public class Player implements IPlayer { private int hp = getMaxHealth(); private int attack = 50; private int defence = 30; 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 = pickUpInit(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, but we only use the first 5 for picking up and dropping. int keyValue = Integer.parseInt(key.getName()); if (keyValue <= 9 && keyValue >= 0) { 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) { IItem item = getItem(IRangedWeapon.class); if (item == null) { game.displayMessage("You do not have a ranged weapon."); } else { turnConsumed = rangedAttack(game, 3); if (!turnConsumed) { game.displayMessage("No enemies within range."); } } } else if (key == KeyCode.F) { IItem item = getItem(IMagicWeapon.class); if (item == null) { game.displayMessage("You do not have a magic weapon."); } else { turnConsumed = rangedAttack(game, 2); if (!turnConsumed) { game.displayMessage("No enemies within range."); } } } showStatus(game); 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 picking up of an item, and does what is needed. * * @param game An IGame object * @return True if a turn was consumed. False otherwise */ private boolean pickUpInit(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) { //Static items are always the biggest (and hopefully the only) item on a tile. openChest(game, items); } 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 */ private boolean useConsumable(IGame game) { IConsumable potion = (IConsumable) equipped.getFirst(IConsumable.class); if (potion != null) { hp += potion.hpIncrease(); if (hp > getMaxHealth()) { hp = getMaxHealth(); } attack += potion.attackIncrease(); defence += potion.defenceIncrease(); equipped.remove(potion); game.displayMessage("Used " + potion.getName()); return true; } else { game.displayMessage("You don't have any potions."); } 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; } /** * Prints 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 */ 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.isFull()) { 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.isFull()) { 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) { List items = new ArrayList<>(); for (IItem item : equipped.getContent()) { String name = item.getName(); if (name != null) { items.add(firstCharToUpper(name)); } } //TODO: Add item bonuses to visible stats. game.formatStatus("HP: %d/%d ATK: %d DEF: %s DMG: %s INV: %s", hp, getMaxHealth(), getAttack(), getDefence(), getDamage(), String.join(",", items)); } /** * 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))) { NPC.playSound("audio/Sword Swing-SoundBible.com-639083727.wav"); 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() { return attack; } @Override public int getDamage() { return 20; } @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() { 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() { //TODO: Increase vision based on equipped items return 3; } private boolean rangedAttack(IGame game, int range) { 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); return true; } } } return false; } }