Adds new method for containers for finding first item of a class Adds health potions Allows the user to use potions
448 lines
14 KiB
Java
448 lines
14 KiB
Java
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<IItem> 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<IItem> 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<IItem> 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<IItem> 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<IItem> 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<String> 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<ILocation> neighbours = game.getVisible();
|
|
for (ILocation neighbour : neighbours) {
|
|
if (game.getMap().hasActors(neighbour)) {
|
|
ILocation current = game.getLocation();
|
|
List<GridDirection> 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;
|
|
}
|
|
}
|