Files
Rogue101/src/inf101/v18/rogue101/objects/Player.java
Kristian Knarvik 781f893226 Improvements
Randomized Girl damage
Better attack range calculation
Fixes some errors
Better checking for which weapon was used
Weapons now increase attack
Manga is now consumable
Adds a missing wall
Stops calling consumables potions
Better player stats
Removes hp numbers
2018-03-21 18:24:13 +01:00

538 lines
17 KiB
Java

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<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) { //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<IItem> 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<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.hasSpace()) {
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.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<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) {
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<IItem> 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<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, type);
return true;
}
}
}
return false;
}
}