Major changes

The game now has several levels which can be travelled between
Fake walls
Stairs going uo
Stairs going down
Binoculars
Shields can now be placed on the map
Chests can now be randomly filled
Inventory has been moved
Shiny new health bar
The player can use stairs
This commit is contained in:
Kristian Knarvik 2018-03-20 21:41:18 +01:00
parent 6ad8eb93d7
commit 89de233b6c
17 changed files with 476 additions and 80 deletions

View File

@ -80,6 +80,11 @@ d)
* Main og Game har blitt endret til å tillate spilleren å selv velge når den har utført en tur. Dette har blitt gjort for å kunne ignorere uønskede tastetrykk, og for å tilby spilleren valg.
* Mye har blitt endret siden A og B, så noen ting kan ha blitt fjernet (automatiske gulrøtter har blitt kommentert ut).
* NPC inneholder hjelpemetoder som er felles for alle NPC, men oppfører seg svært ulikt basert på input.
* Spilleren kan bare se i et område rundt seg (området kan økes ved å bruke et IBuffItem). Spilleren kan se vegger bak en annen vegg. Dette er for å lettere observere strukturen av området (ikke en bug), og kan være til hjelp for å finne potensielle falske vegger.
* Spilleren og fiender som har noe i sekken sin, får endrede "stats" basert på våpen brukt, eller første IBuffItem. Dette gjør at spilleren må ta en del valg om hva som er mest viktig å ha med seg.
* Jeg har for det meste fokusert på implementering av grensesnitt som gjør det enkelt å legge til mange nye gjenstander (selv om jeg bare hadde fantasi og tid til å implementere noen få).
* Spillet har ganske få fiender, men jeg fokuserte mest på uforutsigbarhet rundt fiendetypen Girl.
* Grafisk er spillet blandet. Fonten fungerer relativt dårlig til gridet. I det minste er det bedre enn bokstaver, og lettere enn å bruke TurtlePainter.
### Tredjepartsfiler
* Bow Fire Arrow Sound (http://soundbible.com, Stephan Schutze, Noncommercial 3.0)

View File

@ -87,7 +87,7 @@ public class Boss implements INonPlayer {
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
if (hp < 0 && backpack.size() > 0) {
if (hp < 0 && backpack.size() > 0) { //Will be useful in a dungeon with several bosses
boolean dropped = false;
for (IItem item : backpack.getContent()) {
if (game.dropAt(loc, item)) {

View File

@ -59,6 +59,8 @@ public class Game implements IGame {
* individual items don't.
*/
private IGameMap map;
private List<IGameMap> maps = new ArrayList<>();
private int currentLVL = 0;
private IActor currentActor;
private ILocation currentLocation;
private int movePoints = 0;
@ -79,18 +81,13 @@ public class Game implements IGame {
// inputGrid will be filled with single-character strings indicating what (if
// anything)
// should be placed at that map square
IGrid<String> inputGrid = MapReader.readFile("maps/testmap.txt");
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);
}
}
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 potion"};
@ -157,6 +154,28 @@ public class Game implements IGame {
return defence;
}
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");
}
}
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");
}
}
/**
* Gets the damage done against the current target.
*
@ -270,10 +289,13 @@ public class Game implements IGame {
kill();
}
while (!actors.isEmpty()) {
// get the next player or non-player in the queue
currentActor = actors.remove(0);
if (currentActor.isDestroyed()) // skip if it's dead
continue;
// 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)
}
if (currentActor.isDestroyed()) // skip if it's dead
continue;
currentLocation = map.getLocation(currentActor);
if (currentLocation == null) {
displayDebug("doTurn(): Whoops! Actor has disappeared from the map: " + currentActor);
@ -310,7 +332,7 @@ public class Game implements IGame {
/**
* Player is dead. It needs to be properly killed off.
*/
public void kill() {
private void kill() {
map.remove(currentLocation, currentActor);
currentActor = null;
currentLocation = null;
@ -330,13 +352,33 @@ public class Game implements IGame {
System.err.println("Map not found falling back to builtin map");
inputGrid = MapReader.readString(Main.BUILTIN_MAP);
}
this.map = new GameMap(inputGrid.getArea());
IGameMap map = new GameMap(inputGrid.getArea());
for (ILocation loc : inputGrid.locations()) {
IItem 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);
}
IGameMap map = new GameMap(inputGrid.getArea());
for (ILocation loc : inputGrid.locations()) {
IItem item = createItem(inputGrid.get(loc));
if (item instanceof Chest) {
((Chest) item).fill(lvl);
}
if (item != null) {
map.add(loc, item);
}
}
maps.add(map);
}
/**
@ -399,6 +441,11 @@ public class Game implements IGame {
itemFactories.put("s", Staff::new);
itemFactories.put("b", Bow::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

View File

@ -0,0 +1,66 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
public class Binoculars implements IBuffItem {
@Override
public int getBuffDamage() {
return 0;
}
@Override
public int getBuffDefence() {
return 0;
}
@Override
public int getBuffDamageReduction() {
return 0;
}
@Override
public int getBuffVisibility() {
return 2;
}
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "Binoculars";
}
@Override
public int getSize() {
return 0;
}
@Override
public String getPrintSymbol() {
return "\uD83D\uDD0E";
}
@Override
public String getSymbol() {
return "b";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
}

View File

@ -6,6 +6,7 @@ import inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class Chest implements IContainer, IStatic {
private final List<IItem> container;
@ -15,27 +16,40 @@ public class Chest implements IContainer, IStatic {
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
public void fill (int lvl) {
Random random = new Random();
int itemChance = 5;
List<IItem> items = new ArrayList<>();
items.add(new Staff());
items.add(new Sword());
items.add(new Bow());
items.add(new Binoculars());
items.add(new Shield());
for (int i = 0; i < MAX_SIZE; i++) {
int num = random.nextInt(100);
boolean added = false;
for (int j = 0; j < items.size(); j++) {
if (num < (itemChance * (j + 1)) + ((j + 1) * lvl)) {
addItem(items.get(j));
items.remove(j); //We don't want duplicates
added = true;
break;
}
}
if (!added && num > 70) {
addItem(new HealthPotion());
}
}
}
@Override
public IItem get(int i) {
return null;
return container.get(i);
}
@Override

View File

@ -51,9 +51,14 @@ public class Shield implements IBuffItem {
return 2;
}
@Override
public String getPrintSymbol() {
return "\uD83D\uDEE1";
}
@Override
public String getSymbol() {
return "";
return "f";
}
@Override

View File

@ -12,13 +12,9 @@ import inf101.v18.grid.MultiGrid;
import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.game.IllegalMoveException;
import inf101.v18.rogue101.items.Chest;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.examples.Manga;
import inf101.v18.rogue101.items.Sword;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.IPlayer;
import inf101.v18.rogue101.objects.Wall;
import inf101.v18.rogue101.objects.*;
import javafx.scene.canvas.GraphicsContext;
public class GameMap implements IGameMap {
@ -68,18 +64,30 @@ public class GameMap implements IGameMap {
public void addRandomItems(ILocation loc) {
if (!this.hasActors(loc) && !this.hasWall(loc)) {
Random random = new Random();
if (random.nextInt(100) < 20) {
if (random.nextInt(10) < 2) {
this.add(loc, new Carrot());
}
if (random.nextInt(100) < 1) {
if (random.nextInt(10) < 1) {
this.add(loc, new Manga());
}
if (random.nextInt(100) < 5) {
if (random.nextInt(2) < 1) {
this.add(loc, new Chest());
}
if (random.nextInt(100) < 5) {
if (random.nextInt(2) < 1) {
this.add(loc, new Sword());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Shield());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Staff());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Bow());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Backpack());
}
}
}
@ -209,6 +217,7 @@ public class GameMap implements IGameMap {
if (!dontPrint) {
sym = list.get(0).getPrintSymbol();
}
}
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
}
@ -341,7 +350,7 @@ public class GameMap implements IGameMap {
@Override
public boolean hasWall(ILocation loc) {
return grid.contains(loc, (i) -> i instanceof Wall);
return grid.contains(loc, (i) -> i instanceof Wall || i instanceof FakeWall);
}
@Override

View File

@ -1,21 +1,21 @@
40 20
########################################
#...... ..C.R ......R.R......... ..R...#
#.R@R...... ..........RC..R...... ... .#
#.......... ..R......R.R..R........R...#
#R. R......... R..R.........R......R.RR#
#... ..R........R......R. R........R.RR#
###############################....R..R#
#. ...R..C. ..R.R..........C.RC....... #
#..C.....R..... ........RR R..R.....R..#
#...R..R.R..............R .R..R........#
#.R.....R...M....RRR.......R.. .C....R.#
#.C.. ..R. .....R.RC..C....R...R..C. .#
#. R..............R R..R........C.....R#
#........###############################
# R.........R...C....R.....R...R.......#
# R......... R..R........R......R.RR..##
#. ..R........R.....R. ....C...R.RR...#
#....RC..R........R......R.RC......R...#
#.C.... ..... ......... .R..R....R...R.#
# # #
# # G #
# ###### # #
# # # G #
# # #
###=########################### #
# # #
# < # G #
############## # G #
# # #
# G # G #
# # #
#### ########################### #######
# # G s c S G # #
# b # #
# G # G G G # #
# ################## G #
# #
########################################

View File

@ -0,0 +1,21 @@
40 20
########################################
# #
# G # # G #
########## # ###########
# # # #
# # G # #
#### #################### #
# # #
# > # G G #
# # #
########## ####################
# #
# G #
# #
# ####################
#####=######## #
# G # < #
# G c # G #
# G # #
########################################

View File

@ -0,0 +1,21 @@
40 20
########################################
# G #
# c G < #
# G #
# ############################### #
# #
# G #
# ###############################
# # #
# # G # #
# # #
############################### #
# # G #
# G # # #
# # #
# ##################################
# # > #
# G # #
# #
########################################

View File

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

View File

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

View File

@ -11,11 +11,11 @@
# b H @ ######## #########
# # #
# H # #
# # #
# G #
# G #
# c #
# = #
# # G #
# G # #
################ c # #
# ####
# # B#
# G #
# G = #
########################################

View File

@ -0,0 +1,50 @@
package inf101.v18.rogue101.objects;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.rogue101.game.IGame;
public class FakeWall implements IItem {
private int hp = getMaxHealth();
@Override
public boolean draw(ITurtle painter, double w, double h) {
return false;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 10;
}
@Override
public int getMaxHealth() {
return 1;
}
@Override
public String getName() {
return "fake wall";
}
@Override
public int getSize() {
return Integer.MAX_VALUE;
}
@Override
public String getSymbol() {
return "\u001b[47m" + BlocksAndBoxes.BLOCK_FULL + "\u001b[0m";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount;
}
}

View File

@ -1,7 +1,11 @@
package inf101.v18.rogue101.objects;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.gfx.textmode.Printer;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.shared.NPC;
@ -148,6 +152,12 @@ public class Player implements IPlayer {
} else {
if (items.get(0) instanceof IStatic && items.get(0) instanceof IContainer) { //Static items are always the biggest (and hopefully the only) item on a tile.
openChest(game, items);
} else if (items.get(0) instanceof IStatic && items.get(0) instanceof StairsUp) {
((Game)game).goUp();
return true;
} else if (items.get(0) instanceof IStatic && items.get(0) instanceof StairsDown) {
((Game)game).goDown();
return true;
} else {
if (items.size() == 1) {
pickUp(game, 0);
@ -165,7 +175,7 @@ public class Player implements IPlayer {
* Uses the first consumable from the player's inventory
*
* @param game An IGame object
* @return
* @return True if a potion was used. False otherwise
*/
private boolean useConsumable(IGame game) {
IConsumable potion = (IConsumable) equipped.getFirst(IConsumable.class);
@ -181,8 +191,8 @@ public class Player implements IPlayer {
return true;
} else {
game.displayMessage("You don't have any potions.");
return false;
}
return false;
}
/**
@ -199,11 +209,11 @@ public class Player implements IPlayer {
}
/**
* Prints a set number of item names from a list.
* Creates a string containing a set number of item names from a list.
*
* @param list The list of items
* @param max The maximum number of items to print
* @return
* @return A string
*/
private String niceList(List<IItem> list, int max) {
StringBuilder msg = new StringBuilder();
@ -329,15 +339,35 @@ public class Player implements IPlayer {
* @param game An IGame object
*/
private void showStatus(IGame game) {
List<String> items = new ArrayList<>();
for (IItem item : equipped.getContent()) {
String name = item.getName();
if (name != null) {
items.add(firstCharToUpper(name));
}
}
//TODO: Add item bonuses to visible stats.
game.formatStatus("HP: %d/%d ATK: %d DEF: %s DMG: %s INV: %s", hp, getMaxHealth(), getAttack(), getDefence(), getDamage(), String.join(",", items));
game.formatStatus("HP: %s %d/%d ATK: %d DEF: %s DMG: %s", hpBar(), hp, getMaxHealth(), getAttack(), getDefence(), getDamage());
printInventory(game);
}
private void printInventory(IGame game) {
Printer printer = game.getPrinter();
List<IItem> items = equipped.getContent();
printer.clearRegion(Main.COLUMN_RIGHTSIDE_START, 13, 45, 5);
printer.printAt(Main.COLUMN_RIGHTSIDE_START, 12, "Inventory:");
for (int i = 0; i < items.size(); i++) {
printer.printAt(Main.COLUMN_RIGHTSIDE_START, 13 + i, items.get(i).getName());
}
}
private String hpBar() {
StringBuilder str = new StringBuilder(BlocksAndBoxes.BLOCK_HALF);
int hpLen = (int)((float)hp/getMaxHealth() * 10.0);
if (hp > 0 && hp < 100) {
hpLen++;
}
for (int i = 0; i < hpLen; i++) {
str.append("\u001b[91m" + BlocksAndBoxes.BLOCK_HALF + "\u001b[0m");
}
for (int i = 0; i < 10 - hpLen; i++) {
str.append(" ");
}
str.append(BlocksAndBoxes.BLOCK_HALF);
return str.toString();
}
/**
@ -439,8 +469,12 @@ public class Player implements IPlayer {
@Override
public int getVision() {
//TODO: Increase vision based on equipped items
return 3;
IBuffItem item = (IBuffItem) getItem(IBuffItem.class);
if (item != null) {
return 3 + item.getBuffVisibility();
} else {
return 3;
}
}
private boolean rangedAttack(IGame game, int range, Attack type) {

View File

@ -0,0 +1,41 @@
package inf101.v18.rogue101.objects;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.IStatic;
public class StairsDown implements IItem, IStatic {
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "Stairs going downward";
}
@Override
public int getSize() {
return 100;
}
@Override
public String getSymbol() {
return ">";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
}

View File

@ -0,0 +1,41 @@
package inf101.v18.rogue101.objects;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.IStatic;
public class StairsUp implements IItem, IStatic {
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "Stairs going upward";
}
@Override
public int getSize() {
return 100;
}
@Override
public String getSymbol() {
return "<";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
}