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:
2018-03-19 19:11:09 +01:00
parent bec5f90efd
commit 27e4259f00
17 changed files with 406 additions and 179 deletions

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