Adds a new type of NPC
This commit is contained in:
parent
5ec539fda2
commit
478a8f02a3
182
src/inf101/v18/rogue101/enemies/Girl.java
Normal file
182
src/inf101/v18/rogue101/enemies/Girl.java
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package inf101.v18.rogue101.enemies;
|
||||||
|
|
||||||
|
import inf101.v18.grid.GridDirection;
|
||||||
|
import inf101.v18.rogue101.game.IGame;
|
||||||
|
import inf101.v18.rogue101.objects.IItem;
|
||||||
|
import inf101.v18.rogue101.objects.INonPlayer;
|
||||||
|
import inf101.v18.rogue101.shared.NPC;
|
||||||
|
import inf101.v18.rogue101.states.Age;
|
||||||
|
import inf101.v18.rogue101.states.Occupation;
|
||||||
|
import inf101.v18.rogue101.states.Personality;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class Girl implements INonPlayer {
|
||||||
|
private String name = randomName();
|
||||||
|
private Age age = Age.getRandom();
|
||||||
|
private Personality personality = Personality.getRandom();
|
||||||
|
private Occupation occupation = Occupation.getRandom();
|
||||||
|
private int maxhp;
|
||||||
|
private int hp;
|
||||||
|
private int attack;
|
||||||
|
private int defence;
|
||||||
|
private int visibility;
|
||||||
|
private IItem equipped;
|
||||||
|
private static Random random = new Random();
|
||||||
|
|
||||||
|
public Girl() {
|
||||||
|
setStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStats() {
|
||||||
|
switch (age) {
|
||||||
|
case TODDLER:
|
||||||
|
maxhp = 100;
|
||||||
|
attack = 10;
|
||||||
|
defence = 50;
|
||||||
|
visibility = 1;
|
||||||
|
break;
|
||||||
|
case CHILD:
|
||||||
|
maxhp = 150;
|
||||||
|
attack = 20;
|
||||||
|
defence = 40;
|
||||||
|
visibility = 2;
|
||||||
|
break;
|
||||||
|
case TEEN:
|
||||||
|
maxhp = 200;
|
||||||
|
attack = 25;
|
||||||
|
defence = 30;
|
||||||
|
visibility = 3;
|
||||||
|
break;
|
||||||
|
case ADULT:
|
||||||
|
maxhp = 250;
|
||||||
|
attack = 30;
|
||||||
|
defence = 20;
|
||||||
|
visibility = 4;
|
||||||
|
break;
|
||||||
|
case ELDER:
|
||||||
|
maxhp = 200;
|
||||||
|
attack = 15;
|
||||||
|
defence = 35;
|
||||||
|
visibility = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (occupation == Occupation.MAGE) {
|
||||||
|
attack += 10; //Knights are quite powerful.
|
||||||
|
}
|
||||||
|
if (occupation == Occupation.MAGE) {
|
||||||
|
attack += 5; // Mages have lesser range than bowsmen, but more damage.
|
||||||
|
}
|
||||||
|
maxhp += (int)(random.nextGaussian() * 10);
|
||||||
|
hp = maxhp;
|
||||||
|
attack += (int)(random.nextGaussian() * 5);
|
||||||
|
defence += (int)(random.nextGaussian() * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String randomName() {
|
||||||
|
//TODO: Choose from a list of names, or generate name.
|
||||||
|
return "Girl";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doTurn(IGame game) {
|
||||||
|
if (equipped == null) {
|
||||||
|
//TODO: Check if there is a item on the ground to pick up.
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GridDirection> possibleMoves = game.getPossibleMoves();
|
||||||
|
if (!possibleMoves.isEmpty()) {
|
||||||
|
Collections.shuffle(possibleMoves);
|
||||||
|
game.move(possibleMoves.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAttack() {
|
||||||
|
return attack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDamage() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentHealth() {
|
||||||
|
return hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefence() {
|
||||||
|
return defence;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaxHealth() {
|
||||||
|
return maxhp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSymbol() {
|
||||||
|
return "G";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getVision() {
|
||||||
|
return visibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int handleDamage(IGame game, IItem source, int amount) {
|
||||||
|
hp -= amount;
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
}
|
@ -27,7 +27,6 @@ public class Rabbit implements INonPlayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: It seems the rabbit moves twice. We don't want that.
|
|
||||||
if (NPC.tryAttack(game)) {
|
if (NPC.tryAttack(game)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -39,8 +38,7 @@ public class Rabbit implements INonPlayer {
|
|||||||
if (eaten > 0) {
|
if (eaten > 0) {
|
||||||
System.out.println("ate carrot worth " + eaten + "!");
|
System.out.println("ate carrot worth " + eaten + "!");
|
||||||
food += eaten;
|
food += eaten;
|
||||||
game.displayMessage("You hear a faint crunching (" + getName() + " eats " + item.getArticle() + " "
|
game.displayMessage("You hear a faint crunching (" + getName() + " eats " + item.getArticle() + " " + item.getName() + ")");
|
||||||
+ item.getName() + ")");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import inf101.v18.grid.GridDirection;
|
|||||||
import inf101.v18.grid.IGrid;
|
import inf101.v18.grid.IGrid;
|
||||||
import inf101.v18.grid.ILocation;
|
import inf101.v18.grid.ILocation;
|
||||||
import inf101.v18.rogue101.Main;
|
import inf101.v18.rogue101.Main;
|
||||||
|
import inf101.v18.rogue101.enemies.Girl;
|
||||||
import inf101.v18.rogue101.examples.Carrot;
|
import inf101.v18.rogue101.examples.Carrot;
|
||||||
import inf101.v18.rogue101.items.Manga;
|
import inf101.v18.rogue101.items.Manga;
|
||||||
import inf101.v18.rogue101.examples.Rabbit;
|
import inf101.v18.rogue101.examples.Rabbit;
|
||||||
@ -258,6 +259,7 @@ public class Game implements IGame {
|
|||||||
itemFactories.put("C", Carrot::new);
|
itemFactories.put("C", Carrot::new);
|
||||||
itemFactories.put("R", Rabbit::new);
|
itemFactories.put("R", Rabbit::new);
|
||||||
itemFactories.put("M", Manga::new);
|
itemFactories.put("M", Manga::new);
|
||||||
|
itemFactories.put("G", Girl::new);
|
||||||
itemFactories.put(".", Dust::new);
|
itemFactories.put(".", Dust::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -435,8 +437,22 @@ public class Game implements IGame {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ILocation rangedAttack(GridDirection dir, IItem target) {
|
public ILocation rangedAttack(GridDirection dir, IItem target) {
|
||||||
|
ILocation loc = currentLocation;
|
||||||
|
int damage = (currentActor.getAttack() + random.nextInt(20) + 1)
|
||||||
|
/ loc.gridDistanceTo(map.getLocation(target)); //Close attacks will take more damage.
|
||||||
|
if (damage >= target.getDefence() + 10) {
|
||||||
|
int actualDamage = target.handleDamage(this, target, damage);
|
||||||
|
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
|
||||||
|
} else {
|
||||||
|
displayMessage("The attack missed.");
|
||||||
|
}
|
||||||
|
map.clean(loc);
|
||||||
|
if (target.isDestroyed() && map.has(currentLocation.go(dir), target)) {
|
||||||
|
return move(dir);
|
||||||
|
} else {
|
||||||
return currentLocation;
|
return currentLocation;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ITurtle getPainter() {
|
public ITurtle getPainter() {
|
||||||
|
@ -35,6 +35,40 @@ public class NPC {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a ranged attack on the closest actor (if any) observable by the current actor.
|
||||||
|
*
|
||||||
|
* @param game An IGame object
|
||||||
|
* @param range The range of the equipped weapon
|
||||||
|
* @return True if an attack or a move towards an enemy was successful. False otherwise
|
||||||
|
*/
|
||||||
|
public static boolean tryAttackRanged(IGame game, int range) {
|
||||||
|
// Ranged attacks don't care about walls.
|
||||||
|
List<ILocation> neighbours = game.getMap().getNeighbourhood(game.getLocation(), game.getActor().getVision());
|
||||||
|
for (ILocation neighbour : neighbours) {
|
||||||
|
if (game.getMap().hasActors(neighbour)) {
|
||||||
|
ILocation current = game.getLocation();
|
||||||
|
GridDirection dir = game.locationDirection(current, neighbour);
|
||||||
|
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
|
||||||
|
if (current.gridDistanceTo(neighbour) <= range && !actor.isDestroyed()) {
|
||||||
|
game.rangedAttack(dir, actor);
|
||||||
|
return true;
|
||||||
|
} else if (game.canGo(dir)) {
|
||||||
|
game.move(dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
public static boolean trackItem(IGame game, Class<?> element) {
|
public static boolean trackItem(IGame game, Class<?> element) {
|
||||||
List<ILocation> neighbours = game.getVisible();
|
List<ILocation> neighbours = game.getVisible();
|
||||||
for (ILocation neighbour : neighbours) {
|
for (ILocation neighbour : neighbours) {
|
||||||
@ -55,4 +89,31 @@ public class NPC {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean flee(IGame game) {
|
||||||
|
List<ILocation> neighbours = game.getVisible();
|
||||||
|
for (ILocation neighbour : neighbours) {
|
||||||
|
if (game.getMap().hasActors(neighbour)) {
|
||||||
|
ILocation current = game.getLocation();
|
||||||
|
GridDirection dir = reverseDir(game.locationDirection(current, neighbour));
|
||||||
|
if (game.canGo(dir)) {
|
||||||
|
game.move(dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GridDirection reverseDir(GridDirection dir) {
|
||||||
|
if (dir == GridDirection.SOUTH) {
|
||||||
|
return GridDirection.NORTH;
|
||||||
|
} else if (dir == GridDirection.NORTH) {
|
||||||
|
return GridDirection.SOUTH;
|
||||||
|
} else if (dir == GridDirection.WEST) {
|
||||||
|
return GridDirection.EAST;
|
||||||
|
} else {
|
||||||
|
return GridDirection.WEST;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
13
src/inf101/v18/rogue101/states/Age.java
Normal file
13
src/inf101/v18/rogue101/states/Age.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package inf101.v18.rogue101.states;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public enum Age {
|
||||||
|
TODDLER, CHILD, TEEN, ADULT, ELDER;
|
||||||
|
|
||||||
|
private static Random random = new Random();
|
||||||
|
|
||||||
|
public static Age getRandom() {
|
||||||
|
return values()[random.nextInt(values().length)];
|
||||||
|
}
|
||||||
|
}
|
13
src/inf101/v18/rogue101/states/Occupation.java
Normal file
13
src/inf101/v18/rogue101/states/Occupation.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package inf101.v18.rogue101.states;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public enum Occupation {
|
||||||
|
KNIGHT, MAGE, BOWSMAN;
|
||||||
|
|
||||||
|
private static Random random = new Random();
|
||||||
|
|
||||||
|
public static Occupation getRandom() {
|
||||||
|
return values()[random.nextInt(values().length)];
|
||||||
|
}
|
||||||
|
}
|
13
src/inf101/v18/rogue101/states/Personality.java
Normal file
13
src/inf101/v18/rogue101/states/Personality.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package inf101.v18.rogue101.states;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public enum Personality {
|
||||||
|
AGRESSIVE, CALM, AFRAID;
|
||||||
|
|
||||||
|
private static Random random = new Random();
|
||||||
|
|
||||||
|
public static Personality getRandom() {
|
||||||
|
return values()[random.nextInt(values().length)];
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user