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

View File

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

View File

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

View File

@ -91,7 +91,7 @@ public class Game implements IGame {
} }
// Prints some helpful information. // 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++) { for (int i = 0; i < info.length; i++) {
this.printer.printAt(map.getWidth() + 2, 1 + i, info[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)) { if (!map.has(loc, target)) {
throw new IllegalMoveException("Target isn't there!"); throw new IllegalMoveException("Target isn't there!");
} }
//TODO: Detect the weapon used
IWeapon weapon = (IWeapon) currentActor.getItem(IWeapon.class); IWeapon weapon = (IWeapon) currentActor.getItem(IWeapon.class);
if (weapon != null) { if (weapon != null) {
NPC.playSound(weapon.getSound()); NPC.playSound(weapon.getSound());
@ -189,7 +190,9 @@ public class Game implements IGame {
} }
if (getAttack() >= getDefence(target)) { if (getAttack() >= getDefence(target)) {
int actualDamage = target.handleDamage(this, target, getDamage(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 { } else {
formatMessage("%s tried to hit %s, but missed", currentActor.getName(), target.getName()); formatMessage("%s tried to hit %s, but missed", currentActor.getName(), target.getName());
} }
@ -243,18 +246,21 @@ public class Game implements IGame {
beginTurn(); beginTurn();
} }
if (random.nextInt(100) < 20) { /*if (random.nextInt(100) < 20) {
ILocation loc = map.getLocation(random.nextInt(map.getWidth()), random.nextInt(map.getHeight())); ILocation loc = map.getLocation(random.nextInt(map.getWidth()), random.nextInt(map.getHeight()));
if (!map.hasActors(loc) && !map.hasItems(loc) && !map.hasWall(loc)) { if (!map.hasActors(loc) && !map.hasItems(loc) && !map.hasWall(loc)) {
map.add(loc, new Carrot()); 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 // process actors one by one; for the IPlayer, we return and wait for keypresses
// Possible for INonPlayer, we could also return early (returning // Possible for INonPlayer, we could also return early (returning
// *false*), and then insert a little timer delay between each non-player move // *false*), and then insert a little timer delay between each non-player move
// (the timer // (the timer
// is already set up in Main) // is already set up in Main)
if (numPlayers == 0) {
kill();
}
while (!actors.isEmpty()) { while (!actors.isEmpty()) {
// get the next player or non-player in the queue // get the next player or non-player in the queue
currentActor = actors.remove(0); currentActor = actors.remove(0);
@ -275,11 +281,11 @@ public class Game implements IGame {
} else if (currentActor instanceof IPlayer) { } else if (currentActor instanceof IPlayer) {
if (currentActor.isDestroyed()) { if (currentActor.isDestroyed()) {
// a dead human player gets removed from the game // a dead human player gets removed from the game
// TODO: you might want to be more clever here //This never actually triggers, because of map.clean();
displayMessage("YOU DIE!!!"); /*displayMessage("YOU DIE!!!");
map.remove(currentLocation, currentActor); map.remove(currentLocation, currentActor);
currentActor = null; currentActor = null;
currentLocation = null; currentLocation = null;*/
} else { } else {
// For the human player, we need to wait for input, so we just return. // 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 // Further keypresses will cause keyPressed() to be called, and once the human
@ -300,6 +306,37 @@ public class Game implements IGame {
return true; 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. * 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("S", Sword::new);
itemFactories.put("c", Chest::new); itemFactories.put("c", Chest::new);
itemFactories.put("B", Boss::new); itemFactories.put("B", Boss::new);
itemFactories.put("s", Staff::new);
itemFactories.put("b", Bow::new);
} }
@Override @Override
@ -392,13 +431,37 @@ public class Game implements IGame {
printer.clearLine(Main.LINE_MSG1); printer.clearLine(Main.LINE_MSG1);
printer.clearLine(Main.LINE_MSG2); printer.clearLine(Main.LINE_MSG2);
printer.clearLine(Main.LINE_MSG3); 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) { 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) { if (lastMessages.size() > 1 && !secondLineWritten) {
printer.printAt(1, Main.LINE_MSG2, lastMessages.get(1)); 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)); printer.printAt(1, Main.LINE_MSG3, lastMessages.get(2));
} }
System.out.println("Message: «" + s + "»"); System.out.println("Message: «" + s + "»");
@ -412,9 +475,11 @@ public class Game implements IGame {
} }
public void draw() { public void draw() {
//map.draw(painter, printer); if (numPlayers == 0) {
GameMap aMap = (GameMap) map; map.draw(painter, printer);
aMap.drawVisible(painter, printer, this); } else {
((GameMap) map).drawVisible(painter, printer);
}
} }
@Override @Override
@ -426,6 +491,15 @@ public class Game implements IGame {
return false; return false;
} }
@Override
public boolean dropAt(ILocation loc, IItem item) {
if (item != null) {
map.add(loc, item);
return true;
} else
return false;
}
@Override @Override
public void formatDebug(String s, Object... args) { public void formatDebug(String s, Object... args) {
displayDebug(String.format(s, args)); displayDebug(String.format(s, args));
@ -624,43 +698,51 @@ public class Game implements IGame {
} }
@Override @Override
public GridDirection locationDirection(ILocation start, ILocation target) { public List<GridDirection> locationDirection(ILocation start, ILocation target) {
int targetX = target.getX(), targetY = target.getY(); int targetX = target.getX(), targetY = target.getY();
int startX = start.getX(), startY = start.getY(); int startX = start.getX(), startY = start.getY();
GridDirection dir = GridDirection.CENTER; List<GridDirection> dirs = new ArrayList<>();
if (targetX > startX && targetY > startY) { if (targetX > startX && targetY > startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) { if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.SOUTH; dirs.add(GridDirection.SOUTH);
dirs.add(GridDirection.EAST);
} else { } else {
dir = GridDirection.EAST; dirs.add(GridDirection.EAST);
dirs.add(GridDirection.SOUTH);
} }
} else if (targetX > startX && targetY < startY) { } else if (targetX > startX && targetY < startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) { if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.NORTH; dirs.add(GridDirection.NORTH);
dirs.add(GridDirection.EAST);
} else { } else {
dir = GridDirection.EAST; dirs.add(GridDirection.EAST);
dirs.add(GridDirection.NORTH);
} }
} else if (targetX < startX && targetY > startY) { } else if (targetX < startX && targetY > startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) { if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.SOUTH; dirs.add(GridDirection.SOUTH);
dirs.add(GridDirection.WEST);
} else { } else {
dir = GridDirection.WEST; dirs.add(GridDirection.WEST);
dirs.add(GridDirection.SOUTH);
} }
} else if (targetX < startX && targetY < startY) { } else if (targetX < startX && targetY < startY) {
if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) { if (Math.abs(targetX - startX) < Math.abs(targetY - startY)) {
dir = GridDirection.NORTH; dirs.add(GridDirection.NORTH);
dirs.add(GridDirection.WEST);
} else { } else {
dir = GridDirection.WEST; dirs.add(GridDirection.WEST);
dirs.add(GridDirection.NORTH);
} }
} else if (targetX > startX) { } else if (targetX > startX) {
dir = GridDirection.EAST; dirs.add(GridDirection.EAST);
} else if (targetX < startX) { } else if (targetX < startX) {
dir = GridDirection.WEST; dirs.add(GridDirection.WEST);
} else if (targetY > startY) { } else if (targetY > startY) {
dir = GridDirection.SOUTH; dirs.add(GridDirection.SOUTH);
} else if (targetY < startY) { } 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); 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!) * Clear the unused graphics area (you can fill it with whatever you want!)
*/ */
@ -319,12 +328,12 @@ public interface IGame {
Random getRandom(); 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. * 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 current The location to go from
* @param neighbour The location to go to * @param neighbour The location to go to
* @return A direction * @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 inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class Backpack implements IContainer { public class Backpack implements IContainer {
@ -14,7 +15,7 @@ public class Backpack implements IContainer {
/** /**
* The maximum amount of items allowed in a single backpack. * The maximum amount of items allowed in a single backpack.
*/ */
private final int MAX_SIZE = 50; private final int MAX_SIZE = 5;
@Override @Override
public int getCurrentHealth() { public int getCurrentHealth() {
@ -54,14 +55,10 @@ public class Backpack implements IContainer {
/** /**
* Retrieves the current size of the Backpack. * Retrieves the current size of the Backpack.
* *
* @return * @return The size
*/ */
public int size() { public int size() {
int totalSize = 0; return content.size();
for (IItem item : content) {
totalSize += item.getSize();
}
return totalSize;
} }
/** /**
@ -77,7 +74,7 @@ public class Backpack implements IContainer {
* Tries to add an item to the Backpack * Tries to add an item to the Backpack
* *
* @param item The item to add * @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) { public boolean add(IItem item) {
if (size() < MAX_SIZE) { if (size() < MAX_SIZE) {
@ -107,13 +104,23 @@ public class Backpack implements IContainer {
return content.get(i); 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. * Gets the content List for direct manipulation.
* *
* @return A list of T * @return A list of T
*/ */
public List<IItem> getContent() { public List<IItem> getContent() {
return content; return Collections.unmodifiableList(content);
} }
@Override @Override

View File

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

View File

@ -4,32 +4,48 @@ import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class Chest implements IContainer, IStatic { public class Chest implements IContainer, IStatic {
private List<IItem> container; private List<IItem> container;
private int MAX_SIZE = 10;
public Chest() { public Chest() {
this.container = new ArrayList<>(); this.container = new ArrayList<>();
} }
public Chest(int lvl) {
this.container = new ArrayList<>();
fill(lvl);
}
public Chest(List<IItem> items) { public Chest(List<IItem> items) {
this.container = 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 @Override
public IItem get(int i) { public IItem get(int i) {
return null; return null;
} }
@Override @Override
public List getContent() { public List<IItem> getContent() {
return container; return Collections.unmodifiableList(container);
} }
@Override @Override
public boolean isFull() { public boolean isFull() {
return false; return container.size() >= MAX_SIZE;
} }
@Override @Override
@ -47,18 +63,13 @@ public class Chest implements IContainer, IStatic {
return "Chest"; return "Chest";
} }
@Override
public String getInteractMessage() {
return "Items in " + getName() + ": ";
}
@Override @Override
public int getSize() { public int getSize() {
return 10000; return 10000;
} }
public String getPrintSymbol() { public String getPrintSymbol() {
return "\uD83D\uDDC3"; return "\u001b[94m" + "\uD83D\uDDC3" + "\u001b[0m";
} }
@Override @Override
@ -70,4 +81,14 @@ public class Chest implements IContainer, IStatic {
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(IGame game, IItem source, int amount) {
return 0; 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); 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. * 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 * @return True if it has no space left
*/ */
boolean isFull(); 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() { default int getSize() {
return 10000; 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; return 0;
} }
@Override
public String getPrintSymbol() {
return "";
}
@Override @Override
public String getSymbol() { public String getSymbol() {
return "s"; return "s";

View File

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

View File

@ -119,7 +119,7 @@ public class GameMap implements IGameMap {
try { try {
for (ILocation loc : cells) { for (ILocation loc : cells) {
List<IItem> list = grid.get(loc); List<IItem> list = grid.get(loc);
String sym = "."; String sym = " ";
if (!list.isEmpty()) { if (!list.isEmpty()) {
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) { if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h); ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
@ -132,7 +132,7 @@ public class GameMap implements IGameMap {
if (!dontPrint) { if (!dontPrint) {
sym = list.get(0).getPrintSymbol(); sym = list.get(0).getPrintSymbol();
} }
} }
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym); printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
} }
} finally { } 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 painter A painter
* @param printer * @param printer A printer
* @param game
*/ */
public void drawVisible(ITurtle painter, Printer printer, IGame game) { public void drawVisible(ITurtle painter, Printer printer) {
Iterable<ILocation> cells; Iterable<ILocation> cells;
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) { if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
if (dirtyLocs.isEmpty()) if (dirtyLocs.isEmpty())
@ -176,11 +176,15 @@ public class GameMap implements IGameMap {
ILocation playerPos = null; ILocation playerPos = null;
for (ILocation loc : cells) { for (ILocation loc : cells) {
printer.printAt(loc.getX() + 1, loc.getY() + 1, " "); 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) { if (this.hasActors(loc) && this.getActors(loc).get(0) instanceof IPlayer) {
player = (IPlayer) this.getActors(loc).get(0); player = (IPlayer) this.getActors(loc).get(0);
playerPos = loc; playerPos = loc;
} }
} }
if (player == null) {
return;
}
List<ILocation> positions = getVisible(getNeighbourhood(playerPos,player.getVision()), playerPos); List<ILocation> positions = getVisible(getNeighbourhood(playerPos,player.getVision()), playerPos);
positions.add(playerPos); positions.add(playerPos);
for (ILocation loc : positions) { for (ILocation loc : positions) {
@ -351,24 +355,20 @@ public class GameMap implements IGameMap {
int startX = loc.getX(); int startX = loc.getX();
int startY = loc.getY(); int startY = loc.getY();
for (int i = 1; i <= dist; i++) { for (int i = 1; i <= dist; i++) {
int leftX = startX - i; for (int x = startX - i + 1; x < startX + i; x++) { //Top and bottom
int rightX = startX + i; if (grid.isValid(x, startY - i)) {
int topY = startY - i; neighbours.add(getLocation(x, startY - i));
int bottomY = startY + i;
for (int x = leftX + 1; x < rightX; x++) {
if (grid.isValid(x, topY)) {
neighbours.add(getLocation(x, topY));
} }
if (grid.isValid(x, bottomY)) { if (grid.isValid(x, startY + i)) {
neighbours.add(getLocation(x, bottomY)); neighbours.add(getLocation(x, startY + i));
} }
} }
for (int y = topY; y <= bottomY; y++) { for (int y = startY - i; y <= startY + i; y++) { //Sides
if (grid.isValid(leftX, y)) { if (grid.isValid(startX - i, y)) {
neighbours.add(getLocation(leftX, y)); neighbours.add(getLocation(startX - i, y));
} }
if (grid.isValid(rightX, y)) { if (grid.isValid(startX + i, y)) {
neighbours.add(getLocation(rightX, 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.GridDirection;
import inf101.v18.grid.ILocation; import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*; import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.shared.NPC; import inf101.v18.rogue101.shared.NPC;
@ -47,7 +48,10 @@ public class Player implements IPlayer {
} else if (key.isDigitKey()) { } else if (key.isDigitKey()) {
//Takes care of all digit keys, but we only use the first 5 for picking up and dropping. //Takes care of all digit keys, but we only use the first 5 for picking up and dropping.
int keyValue = Integer.parseInt(key.getName()); int keyValue = Integer.parseInt(key.getName());
if (keyValue <= 5 && keyValue > 0) { if (keyValue <= 9 && keyValue >= 0) {
if (keyValue == 0) {
keyValue = 10;
}
if (dropping) { if (dropping) {
drop(game, keyValue - 1); drop(game, keyValue - 1);
dropping = false; dropping = false;
@ -70,14 +74,20 @@ public class Player implements IPlayer {
if (item == null) { if (item == null) {
game.displayMessage("You do not have a ranged weapon."); game.displayMessage("You do not have a ranged weapon.");
} else { } else {
turnConsumed = NPC.tryAttackRanged(game, 3); turnConsumed = rangedAttack(game, 3);
if (!turnConsumed) {
game.displayMessage("No enemies within range.");
}
} }
} else if (key == KeyCode.F) { } else if (key == KeyCode.F) {
IItem item = getItem(IMagicWeapon.class); IItem item = getItem(IMagicWeapon.class);
if (item == null) { if (item == null) {
game.displayMessage("You do not have a magic weapon."); game.displayMessage("You do not have a magic weapon.");
} else { } else {
turnConsumed = NPC.tryAttackRanged(game, 2); turnConsumed = rangedAttack(game, 2);
if (!turnConsumed) {
game.displayMessage("No enemies within range.");
}
} }
} }
showStatus(game); showStatus(game);
@ -136,17 +146,20 @@ public class Player implements IPlayer {
} }
private void openChest(IGame game, List<IItem> items) { private void openChest(IGame game, List<IItem> items) {
IStatic item = (IStatic)items.get(0); IContainer container = (IContainer) items.get(0);
StringBuilder msg = new StringBuilder(item.getInteractMessage());
IContainer container = (IContainer) item;
items = container.getContent(); items = container.getContent();
for (int i = 0; i < Math.min(items.size(), 5); i++) { game.displayMessage(container.getInteractMessage() + niceList(items, 10));
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(items.get(i).getName()));
}
game.displayMessage(msg.toString());
exploringChest = true; 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. * 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 * @return True if a turn was consumed. False otherwise
*/ */
private boolean dropInit(IGame game) { private boolean dropInit(IGame game) {
if (equipped.getContent().size() == 1) { if (equipped.size() == 1) {
drop(game, 0); drop(game, 0);
return true; return true;
} else if (equipped.getContent().size() < 1) { } else if (equipped.size() < 1) {
game.displayMessage("You have nothing to drop"); game.displayMessage("You have nothing to drop");
} else { } else {
StringBuilder msg = new StringBuilder("Items to drop:"); StringBuilder msg = new StringBuilder("Items to drop:");
@ -200,10 +213,8 @@ public class Player implements IPlayer {
*/ */
private void loot(IGame game, int i) { private void loot(IGame game, int i) {
if (!equipped.isFull()) { if (!equipped.isFull()) {
List<IItem> items = game.getLocalItems(); IContainer container = getStaticContainer(game);
IItem item = items.get(0); if (container != null && i < container.getContent().size()) {
if (item instanceof IStatic && item instanceof IContainer) {
IContainer container = (IContainer) item;
IItem loot = container.getContent().get(i); IItem loot = container.getContent().get(i);
equipped.add(loot); equipped.add(loot);
container.getContent().remove(i); 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. * Drops an item at index i.
* *
@ -224,6 +248,17 @@ public class Player implements IPlayer {
*/ */
private void drop(IGame game, int i) { private void drop(IGame game, int i) {
if (!equipped.isEmpty() && equipped.size() > 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))) { if (game.drop(equipped.get(i))) {
equipped.remove(i); equipped.remove(i);
game.displayMessage("Item dropped."); game.displayMessage("Item dropped.");
@ -348,4 +383,22 @@ public class Player implements IPlayer {
//TODO: Increase vision based on equipped items //TODO: Increase vision based on equipped items
return 3; 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.GridDirection;
import inf101.v18.grid.ILocation; import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.enemies.Girl;
import inf101.v18.rogue101.game.IGame; 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.IActor;
import inf101.v18.rogue101.objects.IItem; 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.Media;
import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaPlayer;
@ -18,52 +20,40 @@ import java.util.List;
*/ */
public class NPC { public class NPC {
/** private 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;
}
/** /**
* 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 game An IGame object
* @param range The range of the equipped weapon * @param range The range of the equipped weapon
* @return True if an attack or a move towards an enemy was successful. False otherwise * @return True if an attack or a move towards an enemy was successful. False otherwise
*/ */
public static boolean tryAttackRanged(IGame game, int range) { public static boolean tryAttack(IGame game, int range) {
// Ranged attacks don't care about walls. List<ILocation> neighbours = game.getVisible();
List<ILocation> neighbours = game.getMap().getNeighbourhood(game.getLocation(), game.getActor().getVision());
for (ILocation neighbour : neighbours) { 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(); 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. IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
if (current.gridDistanceTo(neighbour) <= range && !actor.isDestroyed()) { for (GridDirection dir : dirs) {
game.rangedAttack(dir, actor); if (range == 1 && current.gridDistanceTo(neighbour) <= 1 && current.canGo(dir) && game.getMap().has(current.go(dir), actor)) {
return true; game.attack(dir, actor);
} else if (game.canGo(dir)) { return true;
game.move(dir); }
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) { public static boolean trackItem(IGame game, List<Class<?>> validItems) {
List<ILocation> neighbours = game.getVisible(); List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) { for (ILocation neighbour : neighbours) {
boolean hasItem = false;
for (IItem item : game.getMap().getAll(neighbour)) { for (IItem item : game.getMap().getAll(neighbour)) {
for (Class<?> validItem : validItems) { for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) { 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; return false;
} }
@ -130,10 +118,12 @@ public class NPC {
for (ILocation neighbour : neighbours) { for (ILocation neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) { if (game.getMap().hasActors(neighbour)) {
ILocation current = game.getLocation(); ILocation current = game.getLocation();
GridDirection dir = reverseDir(game.locationDirection(current, neighbour)); List<GridDirection> dirs = reverseDir(game.locationDirection(current, neighbour));
if (game.canGo(dir)) { for (GridDirection dir : dirs) {
game.move(dir); if (game.canGo(dir)) {
return true; 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 * @param dirs A list directions
* @return An opposite direction * @return A list of the opposite directions
*/ */
private static GridDirection reverseDir(GridDirection dir) { private static List<GridDirection> reverseDir(List<GridDirection> dirs) {
if (dir == GridDirection.SOUTH) { for (int i = 0; i < dirs.size(); i++) {
return GridDirection.NORTH; GridDirection dir = dirs.get(i);
} else if (dir == GridDirection.NORTH) { if (dir == GridDirection.SOUTH) {
return GridDirection.SOUTH; dirs.set(i, GridDirection.NORTH);
} else if (dir == GridDirection.WEST) { } else if (dir == GridDirection.NORTH) {
return GridDirection.EAST; dirs.set(i, GridDirection.SOUTH);
} else { } else if (dir == GridDirection.WEST) {
return GridDirection.WEST; dirs.set(i, GridDirection.EAST);
} else {
dirs.set(i, GridDirection.WEST);
}
} }
return dirs;
} }
/** /**