Adds item storage and more NPC behavior

Makes NPC look for and pick up items of specified types.
Adds a Backpack and IContainer interface.
Lets items use separate sounds. Not completed.
This commit is contained in:
Kristian Knarvik 2018-03-12 21:26:36 +01:00
parent d1b4f4a316
commit 7d0f0dc4f8
13 changed files with 369 additions and 74 deletions

View File

@ -2,6 +2,7 @@ package inf101.v18.rogue101.enemies;
import inf101.v18.grid.GridDirection;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.shared.NPC;
@ -9,6 +10,7 @@ import inf101.v18.rogue101.states.Age;
import inf101.v18.rogue101.states.Occupation;
import inf101.v18.rogue101.states.Personality;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
@ -23,11 +25,13 @@ public class Girl implements INonPlayer {
private int attack;
private int defence;
private int visibility;
private IItem equipped;
private Backpack<IItem> backpack = new Backpack<>();
private static final Random random = new Random();
private List<Class<?>> validItems;
public Girl() {
setStats();
setValidItems();
}
private void setStats() {
@ -63,7 +67,7 @@ public class Girl implements INonPlayer {
visibility = 2;
break;
}
if (occupation == Occupation.MAGE) {
if (occupation == Occupation.KNIGHT) {
attack += 10; //Knights are quite powerful.
}
if (occupation == Occupation.MAGE) {
@ -75,6 +79,24 @@ public class Girl implements INonPlayer {
defence += (int)(random.nextGaussian() * 5);
}
/**
* Specified which items the current Girl should be able to pick up.
*/
private void setValidItems() {
validItems = new ArrayList<>();
switch (occupation) {
case BOWSMAN:
validItems.add(IRangedWeapon.class);
break;
case MAGE:
validItems.add(IMagicWeapon.class);
break;
case KNIGHT:
validItems.add(IMeleeWeapon.class);
}
validItems.add(IBuffItem.class);
}
private String randomName() {
//TODO: Choose from a list of names, or generate name.
return "Girl";
@ -82,44 +104,23 @@ public class Girl implements INonPlayer {
@Override
public void doTurn(IGame game) {
if (equipped == null) {
//TODO: Check if there is a item on the ground to pick up.
if (!backpack.isFull()) {
IItem item = NPC.pickUp(game, validItems);
if (item != null) {
backpack.add(item);
return;
}
if (NPC.trackItem(game, validItems)) {
return;
}
}
boolean attack = false;
switch (personality) {
case CALM:
if (hp < maxhp) {
attack = true;
}
break;
case AFRAID:
if (NPC.flee(game)) {
return;
} else {
attack = true;
}
break;
case AGRESSIVE:
attack = true;
break;
if (personality == Personality.AFRAID && NPC.flee(game)) {
return;
}
if (attack) {
switch (occupation) {
case KNIGHT:
if (NPC.tryAttack(game)) {
return;
}
break;
case MAGE:
if (NPC.tryAttackRanged(game, 2)) {
return;
}
case BOWSMAN:
if (NPC.tryAttackRanged(game, 4)) {
return;
}
}
if (willAttack(game) && attack(game)) {
return;
}
List<GridDirection> possibleMoves = game.getPossibleMoves();
@ -129,6 +130,62 @@ public class Girl implements INonPlayer {
}
}
/**
* Tries to attack with an attack specified by the Girl's occupation
*
* @param game An IGame object
* @return True if an attack occurred. False otherwise
*/
private boolean attack(IGame game) {
switch (occupation) {
case KNIGHT:
if (NPC.tryAttack(game)) {
return true;
}
break;
case MAGE:
if (NPC.tryAttackRanged(game, 2)) {
return true;
}
case BOWSMAN:
if (NPC.tryAttackRanged(game, 4)) {
return true;
}
}
return false;
}
/**
* Checks if the current girl will try to attack.
*
* @param game An IGame object
* @return True if the girl will attack. False otherwise
*/
private boolean willAttack(IGame game) {
boolean attack = false;
switch (personality) {
case CALM:
if (hp < maxhp) {
attack = true;
}
break;
case AFRAID:
if (game.getPossibleMoves().isEmpty()) {
attack = true;
}
break;
case AGRESSIVE:
attack = true;
break;
}
return attack;
}
/**
* Retrieves a girl's occupation.
*
* @return
*/
public Occupation getOccupation() {
return occupation;
}
@ -165,7 +222,7 @@ public class Girl implements INonPlayer {
@Override
public int getSize() {
return 8;
return 30;
}
@Override

View File

@ -1,5 +1,6 @@
package inf101.v18.rogue101.examples;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -13,6 +14,10 @@ import inf101.v18.rogue101.shared.NPC;
public class Rabbit implements INonPlayer {
private int food = 0;
private int hp = getMaxHealth();
private static List<Class<?>> validItems = new ArrayList<>();
static {
validItems.add(Carrot.class);
}
@Override
public void doTurn(IGame game) {
@ -42,7 +47,7 @@ public class Rabbit implements INonPlayer {
}
}
if (NPC.trackItem(game, Carrot.class)) {
if (NPC.trackItem(game, validItems)) {
return;
}

View File

@ -21,6 +21,7 @@ import inf101.v18.rogue101.enemies.Girl;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.items.Manga;
import inf101.v18.rogue101.examples.Rabbit;
import inf101.v18.rogue101.items.Sword;
import inf101.v18.rogue101.map.GameMap;
import inf101.v18.rogue101.map.IGameMap;
import inf101.v18.rogue101.map.IMapView;
@ -124,8 +125,10 @@ public class Game implements IGame {
if (!map.has(loc, target)) {
throw new IllegalMoveException("Target isn't there!");
}
//TODO: Add attack and defence power when appropriate items are equipped.
int damage = currentActor.getAttack() + random.nextInt(20) + 1;
if (damage >= target.getDefence() + 10) {
int defence = target.getDefence() + 10;
if (damage >= defence) {
int actualDamage = target.handleDamage(this, target, damage);
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
} else {
@ -268,6 +271,7 @@ public class Game implements IGame {
itemFactories.put("M", Manga::new);
itemFactories.put("G", Girl::new);
itemFactories.put(".", Dust::new);
itemFactories.put("S", Sword::new);
}
@Override

View File

@ -0,0 +1,129 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import java.util.ArrayList;
import java.util.List;
public class Backpack<T extends IItem> implements IContainer {
/**
* A list containing everything in the backpack.
*/
private List<T> content = new ArrayList<>();
/**
* The maximum amount of items allowed in a single backpack.
*/
private final int MAX_SIZE = 5;
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "Backpack";
}
@Override
public int getSize() {
return 20;
}
@Override
public String getSymbol() {
return "B";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
/**
* Retrieves the current size of the Backpack.
*
* @return
*/
public int size() {
return content.size();
}
/**
* Checks if the Backpack is empty
*
* @return True if empty. False otherwise
*/
public boolean isEmpty() {
return content.isEmpty();
}
/**
* Tries to add an item to the Backpack
*
* @param item The item to add
* @return
*/
public boolean add(T item) {
if (content.size() < MAX_SIZE) {
content.add(item);
return true;
} else {
return false;
}
}
/**
* Removes an element at index i
*
* @param i The index of an element
*/
public void remove(int i) {
content.remove(i);
}
/**
* Gets a T at index i,
*
* @param i The index of an element
* @return An object of type T
*/
public T get(int i) {
return content.get(i);
}
/**
* Gets the content List for direct manipulation.
*
* @return A list of T
*/
public List<T> getContent() {
return content;
}
@Override
public boolean isFull() {
if (content.size() >= MAX_SIZE) {
return true;
} else {
return false;
}
}
//This shows as an error, but is necessary to compile.
@Override
public int compareTo(Object o) {
return compareTo((IItem)o);
}
}

View File

@ -2,7 +2,7 @@ package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
interface IBuffItem extends IItem {
public interface IBuffItem extends IItem {
/**
* Retrieve damage increase done by the buff item.
*

View File

@ -0,0 +1,38 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
import java.util.List;
/**
* A container for storing anything extending IItem.
*
* @param <T> The item type to store
*/
public interface IContainer<T extends IItem> extends IItem {
/**
* Retrieves an item from a container in index i
*
* @param i The index of an element
* @return An IItem
* @throws IndexOutOfBoundsException If the index is out of range.
*/
IItem get(int i);
/**
* Gets a list with everything inside a container.
*
* @return A list of Objects extending IItem
*/
List<T> getContent();
/**
* Checks if we can add anything at all to the container.
*
* @return True if it has no space left
*/
boolean isFull();
}

View File

@ -1,12 +1,12 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface IMagicWeapon extends IWeapon {
interface IMagicWeapon extends IItem {
/**
* Retrieves the damage points of a weapon.
*
* @return
*/
int getWeaponDamage();
@Override
default String getSound() {
return "audio/Bow_Fire_Arrow-Stephan_Schutze-2133929391.wav";
}
}

View File

@ -1,12 +1,12 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface IMeleeWeapon extends IWeapon {
interface IMeleeWeapon extends IItem {
/**
* Retrieves the damage points of a weapon.
*
* @return
*/
int getWeaponDamage();
@Override
default String getSound() {
return "audio/Sword Swing-SoundBible.com-639083727.wav";
}
}

View File

@ -1,12 +1,12 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface IRangedWeapon extends IWeapon {
interface IRangedWeapon extends IItem {
/**
* Retrieves the damage points of a weapon.
*
* @return
*/
int getWeaponDamage();
@Override
default String getSound() {
return "audio/Bow_Fire_Arrow-Stephan_Schutze-2133929391.wav";
}
}

View File

@ -0,0 +1,19 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
public interface IWeapon extends IItem {
/**
* Retrieves the damage points of a weapon.
*
* @return
*/
int getWeaponDamage();
/**
* Retrieves the string path of the sound to play upon an attack.
*
* @return A string path
*/
String getSound();
}

View File

@ -7,7 +7,7 @@
# #
############### # #
# # #
# # #
# S # #
# @ ####################### #
# # #
# # #

View File

@ -3,6 +3,7 @@ package inf101.v18.rogue101.objects;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.Backpack;
import inf101.v18.rogue101.shared.NPC;
import javafx.scene.input.KeyCode;
import java.util.ArrayList;
@ -10,7 +11,7 @@ import java.util.List;
public class Player implements IPlayer {
private int hp = getMaxHealth();
private final List<IItem> equipped = new ArrayList<>();
private final Backpack<IItem> equipped = new Backpack<>();
@Override
public void keyPressed(IGame game, KeyCode key) {
@ -82,7 +83,7 @@ public class Player implements IPlayer {
private void showStatus(IGame game) {
List<String> items = new ArrayList<>();
for (IItem item : equipped) {
for (IItem item : equipped.getContent()) {
String name = item.getName();
items.add(Character.toUpperCase(name.charAt(0)) + name.substring(1));
}

View File

@ -3,6 +3,7 @@ package inf101.v18.rogue101.shared;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.enemies.Girl;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem;
@ -13,6 +14,9 @@ import javafx.scene.media.MediaPlayer;
import java.io.File;
import java.util.List;
/**
* A holder class for methods used by IActors.
*/
public class NPC {
/**
@ -79,17 +83,19 @@ public class NPC {
/**
* Tries to find and reach an item of a specified class.
*
* @param game An IGame object
* @param element The class of the objects we are looking for
* @return True if an appropriate item was found in the range of the current actor. False otherwise.
* @param game An IGame object
* @param validItems A list of classes the NPC should look for
* @return True if an appropriate item was found in the range of the current actor. False otherwise.
*/
public static boolean trackItem(IGame game, Class<?> element) {
public static boolean trackItem(IGame game, List<Class<?>> validItems) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
boolean hasItem = false;
for (IItem item : game.getMap().getAll(neighbour)) {
if (element.isInstance(item)) {
hasItem = true;
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
hasItem = true;
}
}
}
if (hasItem) {
@ -104,6 +110,31 @@ public class NPC {
return false;
}
/**
* Picks up an item of one of the specified class types.
*
* @param game An IGame object
* @param validItems A list of classes the NPC should accept
* @return An item if it was successfully picked up. Null otherwise
*/
public static IItem pickUp(IGame game, List<Class<?>> validItems) {
for (IItem item : game.getLocalItems()) {
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
return game.pickUp(item);
}
}
}
return null;
}
/**
* Makes an IActor go to the opposite direction of the closest enemy.
* Will return false if there are no visible enemies.
*
* @param game An IGame object
* @return True if the IActor fled. False otherwise
*/
public static boolean flee(IGame game) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
@ -119,6 +150,12 @@ public class NPC {
return false;
}
/**
* Reverses the main four directions.
*
* @param dir A direction
* @return An opposite direction
*/
private static GridDirection reverseDir(GridDirection dir) {
if (dir == GridDirection.SOUTH) {
return GridDirection.NORTH;
@ -131,6 +168,11 @@ public class NPC {
}
}
/**
* Plays a sound.
*
* @param filename The String path of the audio file to play
*/
public static void playSound(String filename) {
Media sound = new Media(new File(filename).toURI().toString());
MediaPlayer mediaPlayer = new MediaPlayer(sound);