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;
}
}