All checks were successful
EpicKnarvik97/Rogue101/pipeline/head This commit looks good
Renames some badly named interfaces and classes Changes a bunch of packages for better structure Adds some missing comments Improves path finding performance somewhat Adds an NPC death sound Makes fake walls noticeable Makes all NPCs drop their items when they die (as the NPCs may steal items from a player)
853 lines
30 KiB
Java
853 lines
30 KiB
Java
package inf101.v18.rogue101.game;
|
||
|
||
import inf101.v18.gfx.Screen;
|
||
import inf101.v18.gfx.gfxmode.ITurtle;
|
||
import inf101.v18.gfx.gfxmode.TurtlePainter;
|
||
import inf101.v18.gfx.textmode.Printer;
|
||
import inf101.v18.grid.GridDirection;
|
||
import inf101.v18.grid.IGrid;
|
||
import inf101.v18.grid.Location;
|
||
import inf101.v18.rogue101.Main;
|
||
import inf101.v18.rogue101.enemies.Boss;
|
||
import inf101.v18.rogue101.enemies.Girl;
|
||
import inf101.v18.rogue101.examples.Carrot;
|
||
import inf101.v18.rogue101.examples.Rabbit;
|
||
import inf101.v18.rogue101.items.buff.Binoculars;
|
||
import inf101.v18.rogue101.items.buff.BuffItem;
|
||
import inf101.v18.rogue101.items.buff.Shield;
|
||
import inf101.v18.rogue101.items.consumable.HealthPotion;
|
||
import inf101.v18.rogue101.items.consumable.Manga;
|
||
import inf101.v18.rogue101.items.container.Chest;
|
||
import inf101.v18.rogue101.items.container.Static;
|
||
import inf101.v18.rogue101.items.weapon.BasicBow;
|
||
import inf101.v18.rogue101.items.weapon.BasicSword;
|
||
import inf101.v18.rogue101.items.weapon.FireStaff;
|
||
import inf101.v18.rogue101.items.weapon.MagicWeapon;
|
||
import inf101.v18.rogue101.items.weapon.MeleeWeapon;
|
||
import inf101.v18.rogue101.items.weapon.RangedWeapon;
|
||
import inf101.v18.rogue101.items.weapon.Weapon;
|
||
import inf101.v18.rogue101.map.AGameMap;
|
||
import inf101.v18.rogue101.map.GameMap;
|
||
import inf101.v18.rogue101.map.MapReader;
|
||
import inf101.v18.rogue101.map.MapView;
|
||
import inf101.v18.rogue101.object.Actor;
|
||
import inf101.v18.rogue101.object.Dust;
|
||
import inf101.v18.rogue101.object.FakeWall;
|
||
import inf101.v18.rogue101.object.Item;
|
||
import inf101.v18.rogue101.object.NonPlayerCharacter;
|
||
import inf101.v18.rogue101.object.Player;
|
||
import inf101.v18.rogue101.object.PlayerCharacter;
|
||
import inf101.v18.rogue101.object.StairsDown;
|
||
import inf101.v18.rogue101.object.StairsUp;
|
||
import inf101.v18.rogue101.object.Wall;
|
||
import inf101.v18.rogue101.state.AttackType;
|
||
import inf101.v18.rogue101.state.Sound;
|
||
import inf101.v18.util.NPCHelper;
|
||
import javafx.scene.canvas.GraphicsContext;
|
||
import javafx.scene.input.KeyCode;
|
||
import javafx.scene.paint.Color;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.Collections;
|
||
import java.util.HashMap;
|
||
import java.util.Iterator;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.Random;
|
||
import java.util.function.Supplier;
|
||
|
||
public class RogueGame implements Game {
|
||
/**
|
||
* All the IActors that have things left to do this turn
|
||
*/
|
||
private List<Actor> actors = Collections.synchronizedList(new ArrayList<>());
|
||
/**
|
||
* For fancy solution to factory problem
|
||
*/
|
||
private final Map<String, Supplier<Item>> itemFactories = new HashMap<>();
|
||
/**
|
||
* Useful random generator
|
||
*/
|
||
private final Random random = new Random();
|
||
|
||
/**
|
||
* Saves the last three messages
|
||
*/
|
||
private final List<String> lastMessages = new ArrayList<>();
|
||
|
||
/**
|
||
* The game map. {@link GameMap} gives us a few more details than
|
||
* {@link MapView} (write access to item lists); the game needs this but
|
||
* individual items don't.
|
||
*/
|
||
private GameMap map;
|
||
private final List<GameMap> maps = new ArrayList<>();
|
||
private int currentLVL = 0;
|
||
private Actor currentActor;
|
||
private Location currentLocation;
|
||
private int movePoints = 0;
|
||
private final ITurtle painter;
|
||
private final Printer printer;
|
||
private int numberOfPlayers = 0;
|
||
private boolean won = false;
|
||
private List<Location> visible = null;
|
||
|
||
public RogueGame(Screen screen, ITurtle painter, Printer printer) {
|
||
this.painter = painter;
|
||
this.printer = printer;
|
||
|
||
addFactory();
|
||
|
||
// NOTE: in a more realistic situation, we will have multiple levels (one map
|
||
// per level), and (at least for a Roguelike game) the levels should be
|
||
// generated
|
||
//
|
||
// inputGrid will be filled with single-character strings indicating what (if
|
||
// anything)
|
||
// should be placed at that map square
|
||
loadMap("level1.txt", 1);
|
||
loadMap("level2.txt", 2);
|
||
loadMap("level3.txt", 3);
|
||
loadMap("level4.txt", 4);
|
||
loadMap("level5.txt", 5);
|
||
map = maps.get(0);
|
||
map.add(map.getLocation(3, 3), new Player()); //We must be sure there is never more than one player
|
||
|
||
// 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-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", "C to use a consumable"};
|
||
for (int i = 0; i < info.length; i++) {
|
||
this.printer.printAt(Main.COLUMN_RIGHT_SIDE_START, 1 + i, info[i]);
|
||
}
|
||
}
|
||
|
||
public RogueGame(String mapString) {
|
||
printer = new Printer(1280, 720);
|
||
painter = new TurtlePainter(1280, 720);
|
||
|
||
addFactory();
|
||
|
||
IGrid<String> inputGrid = MapReader.readString(mapString);
|
||
this.map = new AGameMap(inputGrid.getArea());
|
||
for (Location loc : inputGrid.locations()) {
|
||
Item item = createItem(inputGrid.get(loc));
|
||
if (item != null) {
|
||
map.add(loc, item);
|
||
}
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void addItem(Item item) {
|
||
map.add(currentLocation, item);
|
||
}
|
||
|
||
@Override
|
||
public void addItem(String sym) {
|
||
Item item = createItem(sym);
|
||
if (item != null) {
|
||
map.add(currentLocation, item);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Goes up one floor if possible.
|
||
*/
|
||
public void goUp() {
|
||
if (currentLVL + 1 < maps.size()) {
|
||
map = maps.get(++currentLVL);
|
||
if (!map.has(currentLocation, currentActor)) {
|
||
map.add(currentLocation, currentActor);
|
||
}
|
||
actors = new ArrayList<>();
|
||
displayMessage("You went up the stairs");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Goes down one floor if possible.
|
||
*/
|
||
public void goDown() {
|
||
if (currentLVL > 0) {
|
||
map = maps.get(--currentLVL);
|
||
if (!map.has(currentLocation, currentActor)) {
|
||
map.add(currentLocation, currentActor);
|
||
}
|
||
actors = new ArrayList<>();
|
||
displayMessage("You went down the stairs");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Calculates the attack of an IActor based on equipped items.
|
||
*
|
||
* @return The attack
|
||
*/
|
||
private int getAttack(AttackType type) {
|
||
int attack = currentActor.getAttack() + random.nextInt(20) + 1;
|
||
Weapon weapon = NPCHelper.getWeapon(type, currentActor);
|
||
if (weapon != null) {
|
||
attack += weapon.getWeaponAttack();
|
||
}
|
||
return attack;
|
||
}
|
||
|
||
/**
|
||
* Gets the defence of the current target.
|
||
*
|
||
* @param target The target to evaluate
|
||
* @return The defence of the target
|
||
*/
|
||
private int getDefence(Item target) {
|
||
int defence = target.getDefense() + 10;
|
||
Actor actor = (Actor) target;
|
||
BuffItem item = (BuffItem) actor.getItem(BuffItem.class);
|
||
if (item != null) {
|
||
defence += item.getBuffDefene();
|
||
}
|
||
return defence;
|
||
}
|
||
|
||
/**
|
||
* Gets the damage done against the current target.
|
||
*
|
||
* @param target The target to evaluate.
|
||
* @return The damage done to the target.
|
||
*/
|
||
private int getDamage(Item target, AttackType type) {
|
||
int damage = currentActor.getDamage();
|
||
Weapon weapon = NPCHelper.getWeapon(type, currentActor);
|
||
if (weapon != null) {
|
||
damage += weapon.getWeaponDamage();
|
||
}
|
||
BuffItem buff = (BuffItem) currentActor.getItem(BuffItem.class);
|
||
if (buff != null) {
|
||
damage += buff.getBuffDamage();
|
||
}
|
||
BuffItem item = (BuffItem) ((Actor) target).getItem(BuffItem.class);
|
||
if (item != null) {
|
||
damage -= item.getBuffDamageReduction();
|
||
}
|
||
return damage;
|
||
}
|
||
|
||
@Override
|
||
public Location attack(GridDirection dir, Item target) {
|
||
Location loc = currentLocation.go(dir);
|
||
if (!map.has(loc, target)) {
|
||
throw new IllegalMoveException("Target isn't there!");
|
||
}
|
||
Weapon weapon = (Weapon) currentActor.getItem(MeleeWeapon.class);
|
||
if (weapon != null) {
|
||
weapon.getSound().play();
|
||
} else {
|
||
Sound.MELEE_NO_WEAPON.play();
|
||
}
|
||
if (getAttack(AttackType.MELEE) >= getDefence(target)) {
|
||
int actualDamage = target.handleDamage(this, target, getDamage(target, AttackType.MELEE));
|
||
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());
|
||
}
|
||
|
||
map.clean(loc);
|
||
|
||
if (target.isDestroyed() && currentLocation != null) {
|
||
return move(dir);
|
||
} else {
|
||
movePoints--;
|
||
return currentLocation;
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public Location rangedAttack(GridDirection dir, Item target, AttackType type) {
|
||
Location loc = currentLocation;
|
||
Weapon weapon = null;
|
||
switch (type) {
|
||
case MAGIC:
|
||
weapon = (Weapon) currentActor.getItem(MagicWeapon.class);
|
||
break;
|
||
case RANGED:
|
||
weapon = (Weapon) currentActor.getItem(RangedWeapon.class);
|
||
}
|
||
if (weapon != null) {
|
||
weapon.getSound().play();
|
||
} else {
|
||
Sound.RANGED_NO_WEAPON.play();
|
||
}
|
||
if (getAttack(type) >= getDefence(target)) {
|
||
int damage = getDamage(target, type) / loc.gridDistanceTo(map.getLocation(target));
|
||
int actualDamage = target.handleDamage(this, target, damage);
|
||
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());
|
||
}
|
||
map.clean(map.getLocation(target));
|
||
if (target.isDestroyed() && map.has(currentLocation.go(dir), target)) {
|
||
return move(dir);
|
||
} else {
|
||
return currentLocation;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Begin a new game turn, or continue to the previous turn
|
||
*
|
||
* @return True if the game should wait for more user input
|
||
*/
|
||
public boolean doTurn() {
|
||
do {
|
||
if (actors.isEmpty()) {
|
||
// System.err.println("new turn!");
|
||
|
||
// no one in the queue, we're starting a new turn!
|
||
// first collect all the actors:
|
||
beginTurn();
|
||
}
|
||
|
||
/*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 (numberOfPlayers == 0 && !won) {
|
||
kill();
|
||
}
|
||
while (!actors.isEmpty()) {
|
||
// get the next player or non-player in the queue
|
||
currentActor = actors.remove(0);
|
||
if (currentActor == null) {
|
||
return false; //TODO: Find out why a girl suddenly becomes null (only after map change, not caught in beginTurn, happens randomly)
|
||
}
|
||
// skip if it's dead
|
||
if (currentActor.isDestroyed()) {
|
||
continue;
|
||
}
|
||
currentLocation = map.getLocation(currentActor);
|
||
if (currentLocation == null) {
|
||
displayDebug("doTurn(): Whoops! Actor has disappeared from the map: " + currentActor);
|
||
}
|
||
movePoints = 1; // everyone gets to do one thing
|
||
visible = null;
|
||
|
||
if (currentActor instanceof NonPlayerCharacter) {
|
||
// computer-controlled players do their stuff right away
|
||
((NonPlayerCharacter) currentActor).doTurn(this);
|
||
// remove any dead items from current location
|
||
map.clean(currentLocation);
|
||
return false;
|
||
} else if (currentActor instanceof PlayerCharacter) {
|
||
if (!currentActor.isDestroyed()) {
|
||
// 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
|
||
// makes a move, it'll lose its movement point and doTurn() will be called again
|
||
//
|
||
// NOTE: currentActor and currentLocation are set to the IPlayer (above),
|
||
// so the game remembers who the player is whenever new keypresses occur. This
|
||
// is also how e.g., getLocalItems() work – the game always keeps track of
|
||
// whose turn it is.
|
||
return true;
|
||
}
|
||
} else {
|
||
displayDebug("doTurn(): Hmm, this is a very strange actor: " + currentActor);
|
||
}
|
||
}
|
||
} while (numberOfPlayers > 0); // we can safely repeat if we have players, since we'll return (and break out of
|
||
// the loop) once we hit the player
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Player is dead. It needs to be properly killed off.
|
||
*/
|
||
private void kill() {
|
||
map.remove(currentLocation, currentActor);
|
||
currentActor = null;
|
||
currentLocation = null;
|
||
actors = new ArrayList<>();
|
||
loadMap("gameover.txt");
|
||
Sound.GAME_OVER.play();
|
||
}
|
||
|
||
public void win() { //Trigger when the boss dies
|
||
map.remove(currentLocation, currentActor);
|
||
currentActor = null;
|
||
currentLocation = null;
|
||
actors = new ArrayList<>();
|
||
loadMap("victory.txt");
|
||
map.draw(painter, printer);
|
||
Sound.WIN.play();
|
||
won = true;
|
||
}
|
||
|
||
/**
|
||
* 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);
|
||
}
|
||
GameMap map = new AGameMap(inputGrid.getArea());
|
||
for (Location loc : inputGrid.locations()) {
|
||
Item item = createItem(inputGrid.get(loc));
|
||
if (item != null) {
|
||
map.add(loc, item);
|
||
}
|
||
}
|
||
this.map = map;
|
||
}
|
||
|
||
private void loadMap(String mapName, int lvl) {
|
||
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);
|
||
}
|
||
GameMap map = new AGameMap(inputGrid.getArea());
|
||
for (Location loc : inputGrid.locations()) {
|
||
Item item = createItem(inputGrid.get(loc));
|
||
if (item instanceof Chest) {
|
||
((Chest) item).fill(lvl);
|
||
} else if (item instanceof Girl) {
|
||
((Girl) item).giveWeapon(lvl);
|
||
}
|
||
if (item != null) {
|
||
map.add(loc, item);
|
||
}
|
||
}
|
||
maps.add(map);
|
||
}
|
||
|
||
/**
|
||
* Go through the map and collect all the actors.
|
||
*/
|
||
private void beginTurn() {
|
||
numberOfPlayers = 0;
|
||
// this extra fancy iteration over each map location runs *in parallel* on
|
||
// multicore systems!
|
||
// that makes some things more tricky, hence the "synchronized" block and
|
||
// "Collections.synchronizedList()" in the initialization of "actors".
|
||
// NOTE: If you want to modify this yourself, it might be a good idea to replace
|
||
// "parallelStream()" by "stream()", because weird things can happen when many
|
||
// things happen
|
||
// at the same time! (or do INF214 or DAT103 to learn about locks and threading)
|
||
map.getArea().parallelStream().forEach((loc) -> { // will do this for each location in map
|
||
List<Item> list = map.getAllModifiable(loc); // all items at loc
|
||
Iterator<Item> li = list.iterator(); // manual iterator lets us remove() items
|
||
while (li.hasNext()) { // this is what "for(IItem item : list)" looks like on the inside
|
||
Item item = li.next();
|
||
if (item.getCurrentHealth() < 0) {
|
||
// normally, we expect these things to be removed when they are destroyed, so
|
||
// this shouldn't happen
|
||
synchronized (this) {
|
||
formatDebug("beginTurn(): found and removed leftover destroyed item %s '%s' at %s%n",
|
||
item.getName(), item.getSymbol(), loc);
|
||
}
|
||
li.remove();
|
||
map.remove(loc, item); // need to do this too, to update item map
|
||
} else if (item instanceof PlayerCharacter) {
|
||
actors.add(0, (Actor) item); // we let the human player go first
|
||
synchronized (this) {
|
||
numberOfPlayers++;
|
||
}
|
||
} else if (item instanceof Actor) {
|
||
actors.add((Actor) item); // add other actors to the end of the list
|
||
} else if (item instanceof Carrot) {
|
||
((Carrot) item).doTurn();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
@Override
|
||
public boolean canGo(GridDirection dir) {
|
||
return map.canGo(currentLocation, dir);
|
||
}
|
||
|
||
private void addFactory() {
|
||
itemFactories.put("#", Wall::new);
|
||
itemFactories.put("@", Player::new);
|
||
itemFactories.put("C", Carrot::new);
|
||
itemFactories.put("R", Rabbit::new);
|
||
itemFactories.put("M", Manga::new);
|
||
itemFactories.put("G", Girl::new);
|
||
itemFactories.put(".", Dust::new);
|
||
itemFactories.put("S", BasicSword::new);
|
||
itemFactories.put("c", Chest::new);
|
||
itemFactories.put("B", Boss::new);
|
||
itemFactories.put("s", FireStaff::new);
|
||
itemFactories.put("b", BasicBow::new);
|
||
itemFactories.put("H", HealthPotion::new);
|
||
itemFactories.put("=", FakeWall::new);
|
||
itemFactories.put("<", StairsUp::new);
|
||
itemFactories.put(">", StairsDown::new);
|
||
itemFactories.put("m", Binoculars::new);
|
||
itemFactories.put("f", Shield::new);
|
||
}
|
||
|
||
@Override
|
||
public Item createItem(String symbol) {
|
||
if (" ".equals(symbol)) {
|
||
return null;
|
||
}// alternative/advanced method
|
||
Supplier<Item> factory = itemFactories.get(symbol);
|
||
if (factory != null) {
|
||
return factory.get();
|
||
} else {
|
||
System.err.println("createItem: Don't know how to create a '" + symbol + "'");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public void displayDebug(String s) {
|
||
printer.clearLine(Main.LINE_DEBUG);
|
||
printer.printAt(1, Main.LINE_DEBUG, s, Color.DARKRED);
|
||
System.err.println(s);
|
||
}
|
||
|
||
@Override
|
||
public void displayMessage(String s) {
|
||
if (lastMessages.size() >= 3) {
|
||
lastMessages.remove(2);
|
||
}
|
||
lastMessages.add(0, s);
|
||
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;
|
||
|
||
//Makes long messages overflow to the next line.
|
||
if (lastMessages.size() > 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 && !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 && !thirdLineWritten) {
|
||
printer.printAt(1, Main.LINE_MSG3, lastMessages.get(2));
|
||
}
|
||
//System.out.println("Message: «" + s + "»");
|
||
}
|
||
|
||
@Override
|
||
public void displayStatus(String s) {
|
||
printer.clearLine(Main.LINE_STATUS);
|
||
printer.printAt(1, Main.LINE_STATUS, s);
|
||
//System.out.println("Status: «" + s + "»");
|
||
}
|
||
|
||
public void draw() {
|
||
if (numberOfPlayers == 0) {
|
||
map.draw(painter, printer);
|
||
} else {
|
||
((AGameMap) map).drawVisible(painter, printer);
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean drop(Item item) {
|
||
if (item != null) {
|
||
map.add(currentLocation, item);
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public boolean dropAt(Location loc, Item 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));
|
||
}
|
||
|
||
@Override
|
||
public void formatMessage(String s, Object... args) {
|
||
displayMessage(String.format(s, args));
|
||
}
|
||
|
||
@Override
|
||
public void formatStatus(String s, Object... args) {
|
||
displayStatus(String.format(s, args));
|
||
}
|
||
|
||
@Override
|
||
public int getHeight() {
|
||
return map.getHeight();
|
||
}
|
||
|
||
@Override
|
||
public List<Item> getLocalItems() {
|
||
return map.getItems(currentLocation);
|
||
}
|
||
|
||
@Override
|
||
public Location getLocation() {
|
||
return currentLocation;
|
||
}
|
||
|
||
@Override
|
||
public Location getLocation(GridDirection dir) {
|
||
if (currentLocation.canGo(dir)) {
|
||
return currentLocation.go(dir);
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Return the game map. {@link GameMap} gives us a few more details than
|
||
* {@link MapView} (write access to item lists); the game needs this but
|
||
* individual items don't.
|
||
*/
|
||
@Override
|
||
public MapView getMap() {
|
||
return map;
|
||
}
|
||
|
||
@Override
|
||
public List<GridDirection> getPossibleMoves() {
|
||
List<GridDirection> moves = new ArrayList<>();
|
||
for (GridDirection dir : GridDirection.FOUR_DIRECTIONS) {
|
||
if (canGo(dir)) {
|
||
moves.add(dir);
|
||
}
|
||
}
|
||
return moves;
|
||
}
|
||
|
||
@Override
|
||
public List<Location> getVisible() {
|
||
if (this.visible != null) {
|
||
return this.visible;
|
||
}
|
||
List<Location> neighbours = this.map.getNeighbourhood(this.currentLocation, this.currentActor.getVision());
|
||
List<Location> invalid = new ArrayList<>();
|
||
for (Location neighbour : neighbours) {
|
||
for (Location tile : this.currentLocation.gridLineTo(neighbour)) {
|
||
if (this.map.hasWall(tile)) {
|
||
invalid.add(neighbour);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
neighbours.removeAll(invalid);
|
||
this.visible = neighbours;
|
||
return neighbours;
|
||
}
|
||
|
||
@Override
|
||
public int getWidth() {
|
||
return this.map.getWidth();
|
||
}
|
||
|
||
public boolean keyPressed(KeyCode code) {
|
||
// only an IPlayer/human can handle keypresses, and only if it's the human's
|
||
// turn
|
||
return !(currentActor instanceof PlayerCharacter) || !((PlayerCharacter) currentActor).keyPressed(this, code);
|
||
}
|
||
|
||
@Override
|
||
public Location move(GridDirection dir) {
|
||
if (movePoints < 1) {
|
||
throw new IllegalMoveException("You're out of moves!");
|
||
}
|
||
Location newLoc = map.go(currentLocation, dir);
|
||
map.remove(currentLocation, currentActor);
|
||
map.add(newLoc, currentActor);
|
||
currentLocation = newLoc;
|
||
movePoints--;
|
||
return currentLocation;
|
||
}
|
||
|
||
@Override
|
||
public Item pickUp(Item item) {
|
||
if (item != null && map.has(currentLocation, item) && !(item instanceof Static)) {
|
||
if (item instanceof Actor) {
|
||
if (item.getCurrentHealth() / item.getMaxHealth() < 3) {
|
||
map.remove(currentLocation, item);
|
||
return item;
|
||
} else {
|
||
return null;
|
||
}
|
||
} else if (currentActor.getAttack() > item.getDefense()) {
|
||
map.remove(currentLocation, item);
|
||
return item;
|
||
} else {
|
||
return null;
|
||
}
|
||
} else {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
@Override
|
||
public ITurtle getPainter() {
|
||
return painter;
|
||
}
|
||
|
||
@Override
|
||
public Printer getPrinter() {
|
||
return printer;
|
||
}
|
||
|
||
@Override
|
||
public int[] getFreeTextAreaBounds() {
|
||
int[] area = new int[4];
|
||
area[0] = getWidth() + 1;
|
||
area[1] = 1;
|
||
area[2] = printer.getLineWidth();
|
||
area[3] = printer.getPageHeight() - 5;
|
||
return area;
|
||
}
|
||
|
||
@Override
|
||
public void clearFreeTextArea() {
|
||
printer.clearRegion(getWidth() + 1, 1, printer.getLineWidth() - getWidth(), printer.getPageHeight() - 5);
|
||
}
|
||
|
||
@Override
|
||
public void clearFreeGraphicsArea() {
|
||
painter.as(GraphicsContext.class).clearRect(getWidth() * printer.getCharWidth(), 0,
|
||
painter.getWidth() - getWidth() * printer.getCharWidth(),
|
||
(printer.getPageHeight() - 5) * printer.getCharHeight());
|
||
}
|
||
|
||
@Override
|
||
public double[] getFreeGraphicsAreaBounds() {
|
||
double[] area = new double[4];
|
||
area[0] = getWidth() * printer.getCharWidth();
|
||
area[1] = 0;
|
||
area[2] = painter.getWidth();
|
||
area[3] = getHeight() * printer.getCharHeight();
|
||
return area;
|
||
}
|
||
|
||
@Override
|
||
public Actor getActor() {
|
||
return currentActor;
|
||
}
|
||
|
||
public Location setCurrent(Actor actor) {
|
||
currentLocation = map.getLocation(actor);
|
||
if (currentLocation != null) {
|
||
currentActor = actor;
|
||
movePoints = 1;
|
||
}
|
||
return currentLocation;
|
||
}
|
||
|
||
public Actor setCurrent(Location loc) {
|
||
List<Actor> list = map.getActors(loc);
|
||
if (!list.isEmpty()) {
|
||
currentActor = list.get(0);
|
||
currentLocation = loc;
|
||
movePoints = 1;
|
||
}
|
||
return currentActor;
|
||
}
|
||
|
||
public Actor setCurrent(int x, int y) {
|
||
return setCurrent(map.getLocation(x, y));
|
||
}
|
||
|
||
@Override
|
||
public Random getRandom() {
|
||
return random;
|
||
}
|
||
|
||
@Override
|
||
public List<GridDirection> locationDirection(Location start, Location target) {
|
||
int targetX = target.getX(), targetY = target.getY();
|
||
int startX = start.getX(), startY = start.getY();
|
||
List<GridDirection> dirs = new ArrayList<>();
|
||
boolean yDifferenceIsLarger = Math.abs(targetX - startX) < Math.abs(targetY - startY);
|
||
if (targetX > startX && targetY > startY) {
|
||
if (yDifferenceIsLarger) {
|
||
dirs.add(GridDirection.SOUTH);
|
||
dirs.add(GridDirection.EAST);
|
||
} else {
|
||
dirs.add(GridDirection.EAST);
|
||
dirs.add(GridDirection.SOUTH);
|
||
}
|
||
} else if (targetX > startX && targetY < startY) {
|
||
if (yDifferenceIsLarger) {
|
||
dirs.add(GridDirection.NORTH);
|
||
dirs.add(GridDirection.EAST);
|
||
} else {
|
||
dirs.add(GridDirection.EAST);
|
||
dirs.add(GridDirection.NORTH);
|
||
}
|
||
} else if (targetX < startX && targetY > startY) {
|
||
if (yDifferenceIsLarger) {
|
||
dirs.add(GridDirection.SOUTH);
|
||
dirs.add(GridDirection.WEST);
|
||
} else {
|
||
dirs.add(GridDirection.WEST);
|
||
dirs.add(GridDirection.SOUTH);
|
||
}
|
||
} else if (targetX < startX && targetY < startY) {
|
||
if (yDifferenceIsLarger) {
|
||
dirs.add(GridDirection.NORTH);
|
||
dirs.add(GridDirection.WEST);
|
||
} else {
|
||
dirs.add(GridDirection.WEST);
|
||
dirs.add(GridDirection.NORTH);
|
||
}
|
||
} else if (targetX > startX) {
|
||
dirs.add(GridDirection.EAST);
|
||
} else if (targetX < startX) {
|
||
dirs.add(GridDirection.WEST);
|
||
} else if (targetY > startY) {
|
||
dirs.add(GridDirection.SOUTH);
|
||
} else if (targetY < startY) {
|
||
dirs.add(GridDirection.NORTH);
|
||
}
|
||
return dirs;
|
||
}
|
||
}
|