diff --git a/src/inf101/v18/rogue101/enemies/Girl.java b/src/inf101/v18/rogue101/enemies/Girl.java index 8cc6406..b0788a5 100644 --- a/src/inf101/v18/rogue101/enemies/Girl.java +++ b/src/inf101/v18/rogue101/enemies/Girl.java @@ -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 backpack = new Backpack<>(); private static final Random random = new Random(); + private List> 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 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 diff --git a/src/inf101/v18/rogue101/examples/Rabbit.java b/src/inf101/v18/rogue101/examples/Rabbit.java index 818c6c9..d1e7a0f 100644 --- a/src/inf101/v18/rogue101/examples/Rabbit.java +++ b/src/inf101/v18/rogue101/examples/Rabbit.java @@ -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> 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; } diff --git a/src/inf101/v18/rogue101/game/Game.java b/src/inf101/v18/rogue101/game/Game.java index a6af7e0..9fcca18 100644 --- a/src/inf101/v18/rogue101/game/Game.java +++ b/src/inf101/v18/rogue101/game/Game.java @@ -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 diff --git a/src/inf101/v18/rogue101/items/Backpack.java b/src/inf101/v18/rogue101/items/Backpack.java new file mode 100644 index 0000000..f0194b0 --- /dev/null +++ b/src/inf101/v18/rogue101/items/Backpack.java @@ -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 implements IContainer { + /** + * A list containing everything in the backpack. + */ + private List 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 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); + } +} diff --git a/src/inf101/v18/rogue101/items/IBuffItem.java b/src/inf101/v18/rogue101/items/IBuffItem.java index ef208ea..5b600c4 100644 --- a/src/inf101/v18/rogue101/items/IBuffItem.java +++ b/src/inf101/v18/rogue101/items/IBuffItem.java @@ -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. * diff --git a/src/inf101/v18/rogue101/items/IContainer.java b/src/inf101/v18/rogue101/items/IContainer.java new file mode 100644 index 0000000..886a9b5 --- /dev/null +++ b/src/inf101/v18/rogue101/items/IContainer.java @@ -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 The item type to store + */ +public interface IContainer 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 getContent(); + + /** + * Checks if we can add anything at all to the container. + * + * @return True if it has no space left + */ + boolean isFull(); + + + +} diff --git a/src/inf101/v18/rogue101/items/IMagicWeapon.java b/src/inf101/v18/rogue101/items/IMagicWeapon.java index e1ca4fc..2b07da2 100644 --- a/src/inf101/v18/rogue101/items/IMagicWeapon.java +++ b/src/inf101/v18/rogue101/items/IMagicWeapon.java @@ -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"; + } } diff --git a/src/inf101/v18/rogue101/items/IMeleeWeapon.java b/src/inf101/v18/rogue101/items/IMeleeWeapon.java index 2a745b6..ccdf971 100644 --- a/src/inf101/v18/rogue101/items/IMeleeWeapon.java +++ b/src/inf101/v18/rogue101/items/IMeleeWeapon.java @@ -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"; + } } diff --git a/src/inf101/v18/rogue101/items/IRangedWeapon.java b/src/inf101/v18/rogue101/items/IRangedWeapon.java index 414560b..416d579 100644 --- a/src/inf101/v18/rogue101/items/IRangedWeapon.java +++ b/src/inf101/v18/rogue101/items/IRangedWeapon.java @@ -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"; + } } diff --git a/src/inf101/v18/rogue101/items/IWeapon.java b/src/inf101/v18/rogue101/items/IWeapon.java new file mode 100644 index 0000000..fb3d6ed --- /dev/null +++ b/src/inf101/v18/rogue101/items/IWeapon.java @@ -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(); +} diff --git a/src/inf101/v18/rogue101/map/maps/testmap.txt b/src/inf101/v18/rogue101/map/maps/testmap.txt index 8fa4863..f3d4e9b 100644 --- a/src/inf101/v18/rogue101/map/maps/testmap.txt +++ b/src/inf101/v18/rogue101/map/maps/testmap.txt @@ -7,7 +7,7 @@ # # ############### # # # # # -# # # +# S # # # @ ####################### # # # # # # # diff --git a/src/inf101/v18/rogue101/objects/Player.java b/src/inf101/v18/rogue101/objects/Player.java index c33fe6a..2f12437 100644 --- a/src/inf101/v18/rogue101/objects/Player.java +++ b/src/inf101/v18/rogue101/objects/Player.java @@ -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 equipped = new ArrayList<>(); + private final Backpack 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 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)); } diff --git a/src/inf101/v18/rogue101/shared/NPC.java b/src/inf101/v18/rogue101/shared/NPC.java index bb9cf58..82e9c25 100644 --- a/src/inf101/v18/rogue101/shared/NPC.java +++ b/src/inf101/v18/rogue101/shared/NPC.java @@ -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> validItems) { List 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> 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 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);