Small fixes and improvements

A boss will drop all its items
A girl will choose a name from a list (not finalized)
Carrots will no longer spawn randomly
Lets a message overflow to the next line
Added a new method for dropping an item at any location (for Boss.java)
Improves backpacks
Improves symbols for existing items
Added an interact-message to all containers, and removed it from IStatic
Makes it possible to get any of the 1-10 items in a chest
Makes an item dropped on the same tile as a static container enter the container
Code improvements
Game end screen
This commit is contained in:
Kristian Knarvik 2018-03-19 19:11:09 +01:00
parent bec5f90efd
commit 27e4259f00
17 changed files with 406 additions and 179 deletions

View File

@ -1,17 +1,26 @@
package inf101.v18.rogue101.enemies;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.Backpack;
import inf101.v18.rogue101.items.Sword;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.shared.NPC;
public class Boss implements INonPlayer {
private int hp = getMaxHealth();
Backpack backpack = new Backpack();
private Backpack backpack = new Backpack();
private ILocation loc;
public Boss() {
backpack.add(new Sword());
}
@Override
public void doTurn(IGame game) {
loc = game.getLocation();
NPC.tryAttack(game, 1);
}
@Override
@ -61,7 +70,7 @@ public class Boss implements INonPlayer {
@Override
public String getPrintSymbol() {
return "\uD83D\uDE08";
return "\u001b[91m" + "\uD83D\uDE08" + "\u001b[0m";
}
@Override
@ -69,10 +78,25 @@ public class Boss implements INonPlayer {
return "B";
}
@Override
public int getVision() {
return 2;
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
//TODO: Drop item on death.
hp -= amount;
if (hp < 0 && backpack.size() > 0) {
boolean dropped = false;
for (IItem item : backpack.getContent()) {
if (game.dropAt(loc, item)) {
dropped = true;
}
}
if (dropped) {
game.displayMessage(getName() + " dropped something");
}
}
return amount;
}
}

View File

@ -29,6 +29,7 @@ public class Girl implements INonPlayer {
private Backpack backpack = new Backpack();
private static final Random random = new Random();
private List<Class<?>> validItems;
private static final String[] namelist = {"Milk", "Salad"};
public Girl() {
setStats();
@ -100,8 +101,7 @@ public class Girl implements INonPlayer {
}
private String randomName() {
//TODO: Choose from a list of names, or generate name.
return "Girl";
return namelist[random.nextInt(namelist.length)] + "-chan";
}
@Override
@ -141,16 +141,16 @@ public class Girl implements INonPlayer {
private boolean attack(IGame game) {
switch (occupation) {
case KNIGHT:
if (NPC.tryAttack(game)) {
if (NPC.tryAttack(game, 1)) {
return true;
}
break;
case MAGE:
if (NPC.tryAttackRanged(game, 2)) {
if (NPC.tryAttack(game, 2)) {
return true;
}
case BOWSMAN:
if (NPC.tryAttackRanged(game, 4)) {
if (NPC.tryAttack(game, 4)) {
return true;
}
}
@ -183,15 +183,6 @@ public class Girl implements INonPlayer {
return attack;
}
/**
* Retrieves a girl's occupation.
*
* @return
*/
public Occupation getOccupation() {
return occupation;
}
@Override
public int getAttack() {
return attack;

View File

@ -30,7 +30,7 @@ public class Rabbit implements INonPlayer {
return;
}
if (NPC.tryAttack(game)) {
if (NPC.tryAttack(game, 1)) {
return;
}

View File

@ -91,7 +91,7 @@ public class Game implements IGame {
}
// Prints some helpful information.
String[] info = {"Controls:", "WASD or arrow keys for movement", "E to pick up an item", "Q to drop an item", "1-5 to choose an item", "N to change name", "ENTER to confirm", "R to use a ranged attack", "F to use a magic attack"};
String[] info = {"Controls:", "WASD or arrow keys for movement", "E to pick up an item", "Q to drop an item", "1-0 to choose an item (10=0)", "N to change name", "ENTER to confirm", "R to use a ranged attack", "F to use a magic attack"};
for (int i = 0; i < info.length; i++) {
this.printer.printAt(map.getWidth() + 2, 1 + i, info[i]);
}
@ -181,6 +181,7 @@ public class Game implements IGame {
if (!map.has(loc, target)) {
throw new IllegalMoveException("Target isn't there!");
}
//TODO: Detect the weapon used
IWeapon weapon = (IWeapon) currentActor.getItem(IWeapon.class);
if (weapon != null) {
NPC.playSound(weapon.getSound());
@ -189,7 +190,9 @@ public class Game implements IGame {
}
if (getAttack() >= getDefence(target)) {
int actualDamage = target.handleDamage(this, target, getDamage(target));
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
if (currentActor != null) {
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
}
} else {
formatMessage("%s tried to hit %s, but missed", currentActor.getName(), target.getName());
}
@ -243,18 +246,21 @@ public class Game implements IGame {
beginTurn();
}
if (random.nextInt(100) < 20) {
/*if (random.nextInt(100) < 20) {
ILocation loc = map.getLocation(random.nextInt(map.getWidth()), random.nextInt(map.getHeight()));
if (!map.hasActors(loc) && !map.hasItems(loc) && !map.hasWall(loc)) {
map.add(loc, new Carrot());
}
}
}*/ //We don't want this in the actual game.
// process actors one by one; for the IPlayer, we return and wait for keypresses
// Possible for INonPlayer, we could also return early (returning
// *false*), and then insert a little timer delay between each non-player move
// (the timer
// is already set up in Main)
if (numPlayers == 0) {
kill();
}
while (!actors.isEmpty()) {
// get the next player or non-player in the queue
currentActor = actors.remove(0);
@ -275,11 +281,11 @@ public class Game implements IGame {
} else if (currentActor instanceof IPlayer) {
if (currentActor.isDestroyed()) {
// a dead human player gets removed from the game
// TODO: you might want to be more clever here
displayMessage("YOU DIE!!!");
//This never actually triggers, because of map.clean();
/*displayMessage("YOU DIE!!!");
map.remove(currentLocation, currentActor);
currentActor = null;
currentLocation = null;
currentLocation = null;*/
} else {
// For the human player, we need to wait for input, so we just return.
// Further keypresses will cause keyPressed() to be called, and once the human
@ -300,6 +306,37 @@ public class Game implements IGame {
return true;
}
/**
* Player is dead. It needs to be properly killed off.
*/
public void kill() {
map.remove(currentLocation, currentActor);
currentActor = null;
currentLocation = null;
actors = new ArrayList<>();
loadMap("gameover.txt");
}
/**
* Loads a map with the desired name
*
* @param mapName Name of map, including extension.
*/
private void loadMap(String mapName) {
IGrid<String> inputGrid = MapReader.readFile("maps/" + mapName);
if (inputGrid == null) {
System.err.println("Map not found falling back to builtin map");
inputGrid = MapReader.readString(Main.BUILTIN_MAP);
}
this.map = new GameMap(inputGrid.getArea());
for (ILocation loc : inputGrid.locations()) {
IItem item = createItem(inputGrid.get(loc));
if (item != null) {
map.add(loc, item);
}
}
}
/**
* Go through the map and collect all the actors.
*/
@ -357,6 +394,8 @@ public class Game implements IGame {
itemFactories.put("S", Sword::new);
itemFactories.put("c", Chest::new);
itemFactories.put("B", Boss::new);
itemFactories.put("s", Staff::new);
itemFactories.put("b", Bow::new);
}
@Override
@ -392,13 +431,37 @@ public class Game implements IGame {
printer.clearLine(Main.LINE_MSG1);
printer.clearLine(Main.LINE_MSG2);
printer.clearLine(Main.LINE_MSG3);
int maxLen = 80; //The maximum length of a message to not overflow.
boolean secondLineWritten = false;
boolean thirdLineWritten = false;
if (lastMessages.size() > 0) {
printer.printAt(1, Main.LINE_MSG1, lastMessages.get(0));
String message = lastMessages.get(0);
if (message.length() > 2 * maxLen) {
printer.printAt(1, Main.LINE_MSG1, message.substring(0, maxLen));
printer.printAt(1, Main.LINE_MSG2, message.substring(maxLen, 2 * maxLen));
printer.printAt(1, Main.LINE_MSG3, message.substring(2 * maxLen));
secondLineWritten = thirdLineWritten = true;
} else if (message.length() > maxLen) {
printer.printAt(1, Main.LINE_MSG1, message.substring(0, maxLen));
printer.printAt(1, Main.LINE_MSG2, message.substring(maxLen));
secondLineWritten = true;
} else {
printer.printAt(1, Main.LINE_MSG1, message);
}
}
if (lastMessages.size() > 1) {
printer.printAt(1, Main.LINE_MSG2, lastMessages.get(1));
if (lastMessages.size() > 1 && !secondLineWritten) {
String message = lastMessages.get(1);
if (message.length() > maxLen) {
printer.printAt(1, Main.LINE_MSG2, message.substring(0, maxLen));
printer.printAt(1, Main.LINE_MSG3, message.substring(maxLen));
thirdLineWritten = true;
} else {
printer.printAt(1, Main.LINE_MSG2, message);
}
}
if (lastMessages.size() > 2) {
if (lastMessages.size() > 2 && !thirdLineWritten) {
printer.printAt(1, Main.LINE_MSG3, lastMessages.get(2));
}
System.out.println("Message: «" + s + "»");
@ -412,9 +475,11 @@ public class Game implements IGame {
}
public void draw() {
//map.draw(painter, printer);
GameMap aMap = (GameMap) map;
aMap.drawVisible(painter, printer, this);
if (numPlayers == 0) {
map.draw(painter, printer);
} else {
((GameMap) map).drawVisible(painter, printer);
}
}
@Override
@ -426,6 +491,15 @@ public class Game implements IGame {
return false;
}
@Override
public boolean dropAt(ILocation loc, IItem item) {
if (item != null) {
map.add(loc, item);
return true;
} else
return false;
}
@Override
public void formatDebug(String s, Object... args) {
displayDebug(String.format(s, args));
@ -624,43 +698,51 @@ public class Game implements IGame {
}
@Override
public GridDirection locationDirection(ILocation start, ILocation target) {
public List<GridDirection> locationDirection(ILocation start, ILocation target) {
int targetX = target.getX(), targetY = target.getY();
int startX = start.getX(), startY = start.getY();
GridDirection dir = GridDirection.CENTER;
List<GridDirection> dirs = new ArrayList<>();
if (targetX > startX && targetY > startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.SOUTH;
dirs.add(GridDirection.SOUTH);
dirs.add(GridDirection.EAST);
} else {
dir = GridDirection.EAST;
dirs.add(GridDirection.EAST);
dirs.add(GridDirection.SOUTH);
}
} else if (targetX > startX && targetY < startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.NORTH;
dirs.add(GridDirection.NORTH);
dirs.add(GridDirection.EAST);
} else {
dir = GridDirection.EAST;
dirs.add(GridDirection.EAST);
dirs.add(GridDirection.NORTH);
}
} else if (targetX < startX && targetY > startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.SOUTH;
dirs.add(GridDirection.SOUTH);
dirs.add(GridDirection.WEST);
} else {
dir = GridDirection.WEST;
dirs.add(GridDirection.WEST);
dirs.add(GridDirection.SOUTH);
}
} else if (targetX < startX && targetY < startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.NORTH;
dirs.add(GridDirection.NORTH);
dirs.add(GridDirection.WEST);
} else {
dir = GridDirection.WEST;
dirs.add(GridDirection.WEST);
dirs.add(GridDirection.NORTH);
}
} else if (targetX > startX) {
dir = GridDirection.EAST;
dirs.add(GridDirection.EAST);
} else if (targetX < startX) {
dir = GridDirection.WEST;
dirs.add(GridDirection.WEST);
} else if (targetY > startY) {
dir = GridDirection.SOUTH;
dirs.add(GridDirection.SOUTH);
} else if (targetY < startY) {
dir = GridDirection.NORTH;
dirs.add(GridDirection.NORTH);
}
return dir;
return dirs;
}
}

View File

@ -165,6 +165,15 @@ public interface IGame {
*/
boolean drop(IItem item);
/**
* Does the same as drop, but for a specified location.
*
* @param loc The location to drop the location
* @param item The item to drop
* @return True if the item was dropped. False otherwise
*/
boolean dropAt(ILocation loc, IItem item);
/**
* Clear the unused graphics area (you can fill it with whatever you want!)
*/
@ -319,12 +328,12 @@ public interface IGame {
Random getRandom();
/**
* Gets the best direction to go from current to neighbour.
* Gets a list of the best directions to go from current to neighbour.
* If the target is positioned diagonally from the start, the direction requiring the most steps is chosen.
*
* @param current The location to go from
* @param neighbour The location to go to
* @return A direction
*/
GridDirection locationDirection(ILocation current, ILocation neighbour);
List<GridDirection> locationDirection(ILocation current, ILocation neighbour);
}

View File

@ -4,6 +4,7 @@ import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Backpack implements IContainer {
@ -14,7 +15,7 @@ public class Backpack implements IContainer {
/**
* The maximum amount of items allowed in a single backpack.
*/
private final int MAX_SIZE = 50;
private final int MAX_SIZE = 5;
@Override
public int getCurrentHealth() {
@ -54,14 +55,10 @@ public class Backpack implements IContainer {
/**
* Retrieves the current size of the Backpack.
*
* @return
* @return The size
*/
public int size() {
int totalSize = 0;
for (IItem item : content) {
totalSize += item.getSize();
}
return totalSize;
return content.size();
}
/**
@ -77,7 +74,7 @@ public class Backpack implements IContainer {
* Tries to add an item to the Backpack
*
* @param item The item to add
* @return
* @return True if the item was added. False if the backpack is full
*/
public boolean add(IItem item) {
if (size() < MAX_SIZE) {
@ -107,13 +104,23 @@ public class Backpack implements IContainer {
return content.get(i);
}
@Override
public boolean addItem(IItem item) {
if (content.size() < MAX_SIZE) {
content.add(item);
return true;
} else {
return false;
}
}
/**
* Gets the content List for direct manipulation.
*
* @return A list of T
*/
public List<IItem> getContent() {
return content;
return Collections.unmodifiableList(content);
}
@Override

View File

@ -40,9 +40,14 @@ public class Bow implements IRangedWeapon {
return 2;
}
@Override
public String getPrintSymbol() {
return "\uD83C\uDFF9";
}
@Override
public String getSymbol() {
return "B";
return "b";
}
@Override

View File

@ -4,32 +4,48 @@ import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Chest implements IContainer, IStatic {
private List<IItem> container;
private int MAX_SIZE = 10;
public Chest() {
this.container = new ArrayList<>();
}
public Chest(int lvl) {
this.container = new ArrayList<>();
fill(lvl);
}
public Chest(List<IItem> items) {
this.container = items;
}
/**
* Randomly fills chest with random items based on dungeon level.
*
* @param lvl The current dungeon level
*/
private void fill (int lvl) {
//TODO: Implement
}
@Override
public IItem get(int i) {
return null;
}
@Override
public List getContent() {
return container;
public List<IItem> getContent() {
return Collections.unmodifiableList(container);
}
@Override
public boolean isFull() {
return false;
return container.size() >= MAX_SIZE;
}
@Override
@ -47,18 +63,13 @@ public class Chest implements IContainer, IStatic {
return "Chest";
}
@Override
public String getInteractMessage() {
return "Items in " + getName() + ": ";
}
@Override
public int getSize() {
return 10000;
}
public String getPrintSymbol() {
return "\uD83D\uDDC3";
return "\u001b[94m" + "\uD83D\uDDC3" + "\u001b[0m";
}
@Override
@ -70,4 +81,14 @@ public class Chest implements IContainer, IStatic {
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
@Override
public boolean addItem(IItem item) {
if (container.size() < MAX_SIZE) {
container.add(item);
return true;
} else {
return false;
}
}
}

View File

@ -14,6 +14,14 @@ public interface IContainer extends IItem {
*/
IItem get(int i);
/**
* Adds an item to a container.
*
* @praram item The item to add to the container
* @return True if the container was not full
*/
boolean addItem(IItem item);
/**
* Gets a list with everything inside a container.
*
@ -27,4 +35,13 @@ public interface IContainer extends IItem {
* @return True if it has no space left
*/
boolean isFull();
/**
* Returns the message to show the user upon interacting with the container.
*
* @return A message
*/
default String getInteractMessage() {
return "Items in " + getName() + ": ";
}
}

View File

@ -12,11 +12,4 @@ public interface IStatic extends IItem {
default int getSize() {
return 10000;
}
/**
* A message to display when an interaction with the player happens.
*
* @return A message
*/
String getInteractMessage();
}

View File

@ -40,6 +40,11 @@ public class Staff implements IMagicWeapon {
return 0;
}
@Override
public String getPrintSymbol() {
return "";
}
@Override
public String getSymbol() {
return "s";

View File

@ -40,6 +40,11 @@ public class Sword implements IMeleeWeapon {
return 2;
}
@Override
public String getPrintSymbol() {
return "";
}
@Override
public String getSymbol() {
return "S";

View File

@ -119,7 +119,7 @@ public class GameMap implements IGameMap {
try {
for (ILocation loc : cells) {
List<IItem> list = grid.get(loc);
String sym = ".";
String sym = " ";
if (!list.isEmpty()) {
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
@ -132,7 +132,7 @@ public class GameMap implements IGameMap {
if (!dontPrint) {
sym = list.get(0).getPrintSymbol();
}
}
}
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
}
} finally {
@ -144,13 +144,13 @@ public class GameMap implements IGameMap {
}
/**
* Draws only the tiles visible to the player.
* Writes a black void unto the whole map.
* Properly draws only the tiles visible to the player.
*
* @param painter
* @param printer
* @param game
* @param painter A painter
* @param printer A printer
*/
public void drawVisible(ITurtle painter, Printer printer, IGame game) {
public void drawVisible(ITurtle painter, Printer printer) {
Iterable<ILocation> cells;
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
if (dirtyLocs.isEmpty())
@ -176,11 +176,15 @@ public class GameMap implements IGameMap {
ILocation playerPos = null;
for (ILocation loc : cells) {
printer.printAt(loc.getX() + 1, loc.getY() + 1, " ");
//We need to get the player and its location from somewhere.
if (this.hasActors(loc) && this.getActors(loc).get(0) instanceof IPlayer) {
player = (IPlayer) this.getActors(loc).get(0);
playerPos = loc;
}
}
if (player == null) {
return;
}
List<ILocation> positions = getVisible(getNeighbourhood(playerPos,player.getVision()), playerPos);
positions.add(playerPos);
for (ILocation loc : positions) {
@ -351,24 +355,20 @@ public class GameMap implements IGameMap {
int startX = loc.getX();
int startY = loc.getY();
for (int i = 1; i <= dist; i++) {
int leftX = startX - i;
int rightX = startX + i;
int topY = startY - i;
int bottomY = startY + i;
for (int x = leftX + 1; x < rightX; x++) {
if (grid.isValid(x, topY)) {
neighbours.add(getLocation(x, topY));
for (int x = startX - i + 1; x < startX + i; x++) { //Top and bottom
if (grid.isValid(x, startY - i)) {
neighbours.add(getLocation(x, startY - i));
}
if (grid.isValid(x, bottomY)) {
neighbours.add(getLocation(x, bottomY));
if (grid.isValid(x, startY + i)) {
neighbours.add(getLocation(x, startY + i));
}
}
for (int y = topY; y <= bottomY; y++) {
if (grid.isValid(leftX, y)) {
neighbours.add(getLocation(leftX, y));
for (int y = startY - i; y <= startY + i; y++) { //Sides
if (grid.isValid(startX - i, y)) {
neighbours.add(getLocation(startX - i, y));
}
if (grid.isValid(rightX, y)) {
neighbours.add(getLocation(rightX, y));
if (grid.isValid(startX + i, y)) {
neighbours.add(getLocation(startX + i, y));
}
}
}

View File

@ -0,0 +1,21 @@
40 20
########################################
# #
# #
# #
# #### #### ## ## #### #
# # # # # # # # # #
# # ## #### # # # #### #
# # # # # # # # #
# #### # # # # #### #
# #
# #
# #### # # #### #### #
# # # # # # # # #
# # # # # #### #### #
# # # # # # # # #
# #### ## #### # # #
# #
# #
# #
########################################

View File

@ -7,8 +7,8 @@
# #
############### # #
# # #
# S # #
# @ ######## #########
# S s # #
# b @ ######## #########
# # #
# # #
# # #

View File

@ -2,6 +2,7 @@ package inf101.v18.rogue101.objects;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.shared.NPC;
@ -47,7 +48,10 @@ public class Player implements IPlayer {
} 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 <= 5 && keyValue > 0) {
if (keyValue <= 9 && keyValue >= 0) {
if (keyValue == 0) {
keyValue = 10;
}
if (dropping) {
drop(game, keyValue - 1);
dropping = false;
@ -70,14 +74,20 @@ public class Player implements IPlayer {
if (item == null) {
game.displayMessage("You do not have a ranged weapon.");
} else {
turnConsumed = NPC.tryAttackRanged(game, 3);
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 = NPC.tryAttackRanged(game, 2);
turnConsumed = rangedAttack(game, 2);
if (!turnConsumed) {
game.displayMessage("No enemies within range.");
}
}
}
showStatus(game);
@ -136,17 +146,20 @@ public class Player implements IPlayer {
}
private void openChest(IGame game, List<IItem> items) {
IStatic item = (IStatic)items.get(0);
StringBuilder msg = new StringBuilder(item.getInteractMessage());
IContainer container = (IContainer) item;
IContainer container = (IContainer) items.get(0);
items = container.getContent();
for (int i = 0; i < Math.min(items.size(), 5); i++) {
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(items.get(i).getName()));
}
game.displayMessage(msg.toString());
game.displayMessage(container.getInteractMessage() + niceList(items, 10));
exploringChest = true;
}
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.
*
@ -154,10 +167,10 @@ public class Player implements IPlayer {
* @return True if a turn was consumed. False otherwise
*/
private boolean dropInit(IGame game) {
if (equipped.getContent().size() == 1) {
if (equipped.size() == 1) {
drop(game, 0);
return true;
} else if (equipped.getContent().size() < 1) {
} else if (equipped.size() < 1) {
game.displayMessage("You have nothing to drop");
} else {
StringBuilder msg = new StringBuilder("Items to drop:");
@ -200,10 +213,8 @@ public class Player implements IPlayer {
*/
private void loot(IGame game, int i) {
if (!equipped.isFull()) {
List<IItem> items = game.getLocalItems();
IItem item = items.get(0);
if (item instanceof IStatic && item instanceof IContainer) {
IContainer container = (IContainer) item;
IContainer container = getStaticContainer(game);
if (container != null && i < container.getContent().size()) {
IItem loot = container.getContent().get(i);
equipped.add(loot);
container.getContent().remove(i);
@ -216,6 +227,19 @@ public class Player implements IPlayer {
}
}
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.
*
@ -224,6 +248,17 @@ public class Player implements IPlayer {
*/
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))) {
equipped.remove(i);
game.displayMessage("Item stored in container.");
return;
} else {
game.displayMessage(container.getName() + " is full.");
return;
}
}
if (game.drop(equipped.get(i))) {
equipped.remove(i);
game.displayMessage("Item dropped.");
@ -348,4 +383,22 @@ public class Player implements IPlayer {
//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) {
for (GridDirection dir : dirs) {
game.rangedAttack(dir, actor);
return true;
}
}
}
}
return false;
}
}

View File

@ -2,11 +2,13 @@ package inf101.v18.rogue101.shared;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.enemies.Girl;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.map.IGameMap;
import inf101.v18.rogue101.map.IMapView;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.states.Occupation;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.objects.IPlayer;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
@ -18,52 +20,40 @@ import java.util.List;
*/
public class NPC {
/**
* Makes a NPC attack anything it can, or move closer to any enemy in its line of sight.
*
* @param game An IGame object
* @return True if the NPC made a move. False otherwise
*/
public static boolean tryAttack(IGame game) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) {
ILocation current = game.getLocation();
GridDirection dir = game.locationDirection(current, neighbour);
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
if (current.gridDistanceTo(neighbour) <= 1 && current.canGo(dir) && game.getMap().has(current.go(dir), actor) && !actor.isDestroyed()) {
game.attack(dir, actor);
return true;
} else if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
return false;
}
private NPC() {}
/**
* Performs a ranged attack on the closest actor (if any) observable by the current actor.
* Performs a ranged attack on the closest actor (if any) observable by the current actor if range is > 1.
* Performs a melee attack on an actor (if any) on a neighbouring tile if range == 1.
* Moves closer to an enemy if an attack is not possible.
*
* @param game An IGame object
* @param range The range of the equipped weapon
* @return True if an attack or a move towards an enemy was successful. False otherwise
*/
public static boolean tryAttackRanged(IGame game, int range) {
// Ranged attacks don't care about walls.
List<ILocation> neighbours = game.getMap().getNeighbourhood(game.getLocation(), game.getActor().getVision());
public static boolean tryAttack(IGame game, int range) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) {
IMapView map = game.getMap();
if (map.hasActors(neighbour) && map.getActors(neighbour).get(0) instanceof IPlayer) {
ILocation current = game.getLocation();
GridDirection dir = game.locationDirection(current, neighbour);
List<GridDirection> dirs = game.locationDirection(current, neighbour);
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
if (current.gridDistanceTo(neighbour) <= range && !actor.isDestroyed()) {
game.rangedAttack(dir, actor);
return true;
} else if (game.canGo(dir)) {
game.move(dir);
return true;
for (GridDirection dir : dirs) {
if (range == 1 && current.gridDistanceTo(neighbour) <= 1 && current.canGo(dir) && game.getMap().has(current.go(dir), actor)) {
game.attack(dir, actor);
return true;
}
if (range > 1 && current.gridDistanceTo(neighbour) <= range) {
game.rangedAttack(dir, actor);
return true;
}
}
for (GridDirection dir : dirs) {
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
}
@ -80,22 +70,20 @@ public class NPC {
public static boolean trackItem(IGame game, List<Class<?>> validItems) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
boolean hasItem = false;
for (IItem item : game.getMap().getAll(neighbour)) {
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
hasItem = true;
ILocation current = game.getLocation();
List<GridDirection> dirs = game.locationDirection(current, neighbour);
for (GridDirection dir : dirs) {
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
}
}
if (hasItem) {
ILocation current = game.getLocation();
GridDirection dir = game.locationDirection(current, neighbour);
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
return false;
}
@ -130,10 +118,12 @@ public class NPC {
for (ILocation neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) {
ILocation current = game.getLocation();
GridDirection dir = reverseDir(game.locationDirection(current, neighbour));
if (game.canGo(dir)) {
game.move(dir);
return true;
List<GridDirection> dirs = reverseDir(game.locationDirection(current, neighbour));
for (GridDirection dir : dirs) {
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
}
@ -141,21 +131,25 @@ public class NPC {
}
/**
* Reverses the main four directions.
* Reverses a list of directions.
*
* @param dir A direction
* @return An opposite direction
* @param dirs A list directions
* @return A list of the opposite directions
*/
private static GridDirection reverseDir(GridDirection dir) {
if (dir == GridDirection.SOUTH) {
return GridDirection.NORTH;
} else if (dir == GridDirection.NORTH) {
return GridDirection.SOUTH;
} else if (dir == GridDirection.WEST) {
return GridDirection.EAST;
} else {
return GridDirection.WEST;
private static List<GridDirection> reverseDir(List<GridDirection> dirs) {
for (int i = 0; i < dirs.size(); i++) {
GridDirection dir = dirs.get(i);
if (dir == GridDirection.SOUTH) {
dirs.set(i, GridDirection.NORTH);
} else if (dir == GridDirection.NORTH) {
dirs.set(i, GridDirection.SOUTH);
} else if (dir == GridDirection.WEST) {
dirs.set(i, GridDirection.EAST);
} else {
dirs.set(i, GridDirection.WEST);
}
}
return dirs;
}
/**