Improvements

Randomized Girl damage
Better attack range calculation
Fixes some errors
Better checking for which weapon was used
Weapons now increase attack
Manga is now consumable
Adds a missing wall
Stops calling consumables potions
Better player stats
Removes hp numbers
This commit is contained in:
Kristian Knarvik 2018-03-21 18:24:13 +01:00
parent ab31b9b4e0
commit 781f893226
13 changed files with 204 additions and 70 deletions

View File

@ -25,7 +25,7 @@ Dette prosjektet inneholder [Semesteroppgave 1](SEM-1.md). Du kan også [lese op
# Fyll inn egne svar/beskrivelse/kommentarer til prosjektet under
* Levert av: *Kristian Knarvik* (*kkn015*)
* Del A: [x] helt ferdig, [ ] delvis ferdig
* Del B: [x] helt ferdig, [x] delvis ferdig
* Del B: [x] helt ferdig, [ ] delvis ferdig
* Del C: [ ] helt ferdig, [X] delvis ferdig
* [ ] hele semesteroppgaven er ferdig og klar til retting!
@ -81,10 +81,12 @@ d)
* 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.
* 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. Det er alltid den første consumable som blir brukt.
* 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.
* Spillet har ganske få fiender, men jeg fokuserte mest på uforutsigbarhet rundt fiendetypen Girl. Den viktigste faktoren som bestemmer om du kan vinne eller tape er RNG. Det er ganske vanskelig å lage et variert og balansert spill på kort tid.
* Grafisk er spillet blandet. Fonten fungerer relativt dårlig til gridet. I det minste er det bedre enn bokstaver, og lettere enn å bruke TurtlePainter.
* Dersom spilleren kan gå over noe, har det en handling (plukke opp, åpne, transporter). Bakken er ganske tom, men det er for å forhindre at spilleren får tak i alle mulige items med en gang. Kister kan lagre ting droppet av spilleren.
* En boss er i stand til å legge ned alt den har på seg når den dør. Dette blir ikke brukt, siden det bare blir brukt en boss, men vil være nyttig for en lengre dungeon med flere bosser.
### Tredjepartsfiler
* Bow Fire Arrow (http://soundbible.com, Stephan Schutze, Noncommercial 3.0)

View File

@ -25,7 +25,8 @@ public class Girl implements INonPlayer {
private int hp;
private int attack;
private int defence;
private int visibility;
private int visibility;
private int damage;
private final Backpack backpack = new Backpack();
private static final Random random = new Random();
private List<Class<?>> validItems;
@ -42,30 +43,35 @@ public class Girl implements INonPlayer {
maxhp = 30;
attack = 10;
defence = 50;
damage = 1 + random.nextInt(5);
visibility = 1;
break;
case CHILD:
maxhp = 50;
attack = 20;
defence = 40;
damage = 2 + random.nextInt(5);
visibility = 2;
break;
case TEEN:
maxhp = 70;
attack = 25;
defence = 30;
damage = 3 + random.nextInt(5);
visibility = 3;
break;
case ADULT:
maxhp = 100;
attack = 30;
defence = 20;
damage = 4 + random.nextInt(5);
visibility = 4;
break;
case ELDER:
maxhp = 70;
attack = 15;
defence = 35;
damage = 3 + random.nextInt(5);
visibility = 2;
break;
}
@ -169,11 +175,11 @@ public class Girl implements INonPlayer {
}
break;
case MAGE:
if (NPC.tryAttack(game, 2, Attack.MAGIC)) {
if (NPC.tryAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC)) {
return true;
}
case BOWSMAN:
if (NPC.tryAttack(game, 4, Attack.RANGED)) {
if (NPC.tryAttack(game, getVision(), Attack.RANGED)) {
return true;
}
}
@ -213,7 +219,7 @@ public class Girl implements INonPlayer {
@Override
public int getDamage() {
return 5;
return damage;
}
@Override

View File

@ -20,7 +20,7 @@ import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.enemies.Boss;
import inf101.v18.rogue101.enemies.Girl;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.examples.Manga;
import inf101.v18.rogue101.items.Manga;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.examples.Rabbit;
import inf101.v18.rogue101.map.GameMap;
@ -67,7 +67,7 @@ public class Game implements IGame {
private final ITurtle painter;
private final Printer printer;
private int numPlayers = 0;
boolean won = false;
private boolean won = false;
public Game(Screen screen, ITurtle painter, Printer printer) {
this.painter = painter;
@ -91,7 +91,7 @@ public class Game implements IGame {
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"};
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 consumable"};
for (int i = 0; i < info.length; i++) {
this.printer.printAt(Main.COLUMN_RIGHTSIDE_START, 1 + i, info[i]);
}
@ -125,18 +125,46 @@ public class Game implements IGame {
map.add(currentLocation, item);
}
/**
* Goes up one floor if possible.
*/
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");
}
}
/**
* Goes down one floor if possible.
*/
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");
}
}
/**
* Calculates the attack of an IActor based on equipped items.
*
* @return The attack
*/
private int getAttack() {
int damage = currentActor.getAttack() + random.nextInt(20) + 1;
IBuffItem buff = (IBuffItem)currentActor.getItem(IBuffItem.class);
if (buff != null) {
damage += buff.getBuffDamage();
private int getAttack(Attack type) {
int attack = currentActor.getAttack() + random.nextInt(20) + 1;
IWeapon weapon = NPC.getWeapon(type, currentActor);
if (weapon != null) {
attack += weapon.getWeaponAttack();
}
return damage;
return attack;
}
/**
@ -155,42 +183,23 @@ 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.
*
* @param target The target to evaluate.
* @return The damage done to the target.
*/
private int getDamage(IItem target) {
private int getDamage(IItem target, Attack type) {
int damage = currentActor.getDamage();
IWeapon weapon = (IWeapon)currentActor.getItem(IWeapon.class);
IWeapon weapon = NPC.getWeapon(type, currentActor);
if (weapon != null) {
damage += weapon.getWeaponDamage();
}
IActor actor = (IActor) target;
IBuffItem item = (IBuffItem) actor.getItem(IBuffItem.class);
IBuffItem buff = (IBuffItem)currentActor.getItem(IBuffItem.class);
if (buff != null) {
damage += buff.getBuffDamage();
}
IBuffItem item = (IBuffItem)((IActor)target).getItem(IBuffItem.class);
if (item != null) {
damage -= item.getBuffDamageReduction();
}
@ -209,8 +218,8 @@ public class Game implements IGame {
} else {
NPC.playSound("audio/Realistic_Punch-Mark_DiAngelo-1609462330.wav");
}
if (getAttack() >= getDefence(target)) {
int actualDamage = target.handleDamage(this, target, getDamage(target));
if (getAttack(Attack.MELEE) >= getDefence(target)) {
int actualDamage = target.handleDamage(this, target, getDamage(target, Attack.MELEE));
if (currentActor != null) {
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
}
@ -244,8 +253,8 @@ public class Game implements IGame {
} else {
NPC.playSound("audio/Snow Ball Throw And Splat-SoundBible.com-992042947.wav");
}
if (getAttack() >= getDefence(target)) {
int damage = getDamage(target) / loc.gridDistanceTo(map.getLocation(target));
if (getAttack(type) >= getDefence(target)) {
int damage = getDamage(target, type) / loc.gridDistanceTo(map.getLocation(target));
int actualDamage = target.handleDamage(this, target, damage);
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
} else {
@ -500,6 +509,7 @@ public class Game implements IGame {
boolean secondLineWritten = false;
boolean thirdLineWritten = false;
//Makes long messages overflow to the next line.
if (lastMessages.size() > 0) {
String message = lastMessages.get(0);
if (message.length() > 2 * maxLen) {

View File

@ -15,6 +15,11 @@ public class Bow implements IRangedWeapon {
return damage;
}
@Override
public int getWeaponAttack() {
return 15;
}
@Override
public int getCurrentHealth() {
return hp;

View File

@ -23,13 +23,14 @@ public class Chest implements IContainer, IStatic {
*/
public void fill (int lvl) {
Random random = new Random();
int itemChance = 5;
int itemChance = 3;
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());
items.add(new Manga());
for (int i = 0; i < MAX_SIZE; i++) {
int num = random.nextInt(100);
boolean added = false;

View File

@ -10,6 +10,13 @@ public interface IWeapon extends IItem {
*/
int getWeaponDamage();
/**
* Returns the attack ponts of a weapon.
*
* @return an int
*/
int getWeaponAttack();
/**
* Retrieves the string path of the sound to play upon an attack.
*

View File

@ -1,9 +1,9 @@
package inf101.v18.rogue101.examples;
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
public class Manga implements IItem {
public class Manga implements IConsumable {
private int hp = getMaxHealth();
@Override
@ -46,4 +46,19 @@ public class Manga implements IItem {
hp -= amount;
return amount;
}
@Override
public int hpIncrease() {
return 0;
}
@Override
public int attackIncrease() {
return 5;
}
@Override
public int defenceIncrease() {
return 5;
}
}

View File

@ -15,6 +15,11 @@ public class Staff implements IMagicWeapon {
return damage;
}
@Override
public int getWeaponAttack() {
return 10;
}
@Override
public int getCurrentHealth() {
return hp;

View File

@ -15,6 +15,11 @@ public class Sword implements IMeleeWeapon {
return damage;
}
@Override
public int getWeaponAttack() {
return 5;
}
@Override
public int getCurrentHealth() {
return hp;

View File

@ -13,7 +13,7 @@ import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.game.IllegalMoveException;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.examples.Manga;
import inf101.v18.rogue101.items.Manga;
import inf101.v18.rogue101.objects.*;
import javafx.scene.canvas.GraphicsContext;

View File

@ -3,7 +3,7 @@
# G = #
# c G = < #
# G = #
# ############################### #
################################# #
# #
# G #
# ###############################

View File

@ -46,7 +46,7 @@ public class Player implements IPlayer {
tryToMove(game, GridDirection.SOUTH);
turnConsumed = true;
} else if (key == KeyCode.E) {
turnConsumed = pickUpInit(game);
turnConsumed = interactInit(game);
} else if (key == KeyCode.Q) {
turnConsumed = dropInit(game);
} else if (key == KeyCode.C) {
@ -74,9 +74,9 @@ public class Player implements IPlayer {
game.displayMessage("Please enter your name: ");
writing = true;
} else if (key == KeyCode.R) {
turnConsumed = nonMeleeAttack(game, 3, Attack.RANGED, "ranged");
turnConsumed = nonMeleeAttack(game, getVision(), Attack.RANGED, "ranged");
} else if (key == KeyCode.F) {
turnConsumed = nonMeleeAttack(game, 2, Attack.MAGIC, "magic");
turnConsumed = nonMeleeAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC, "magic");
}
showStatus(game);
return turnConsumed;
@ -136,17 +136,17 @@ public class Player implements IPlayer {
}
/**
* Initializes the picking up of an item, and does what is needed.
* Initializes the interaction with an item, and does what is needed.
*
* @param game An IGame object
* @return True if a turn was consumed. False otherwise
*/
private boolean pickUpInit(IGame game) {
private boolean interactInit(IGame game) {
List<IItem> items = game.getLocalItems();
if (items.size() < 1) {
game.displayMessage("There is nothing to pick up");
} 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.
if (items.get(0) instanceof IStatic && items.get(0) instanceof IContainer) { //A Static item is 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();
@ -174,19 +174,19 @@ public class Player implements IPlayer {
* @return True if a potion was used. False otherwise
*/
private boolean useConsumable(IGame game) {
IConsumable potion = (IConsumable) equipped.getFirst(IConsumable.class);
if (potion != null) {
hp += potion.hpIncrease();
IConsumable consumable = (IConsumable) equipped.getFirst(IConsumable.class);
if (consumable != null) {
hp += consumable.hpIncrease();
if (hp > getMaxHealth()) {
hp = getMaxHealth();
}
attack += potion.attackIncrease();
defence += potion.defenceIncrease();
equipped.remove(potion);
game.displayMessage("Used " + potion.getName());
attack += consumable.attackIncrease();
defence += consumable.defenceIncrease();
equipped.remove(consumable);
game.displayMessage("Used " + consumable.getName());
return true;
} else {
game.displayMessage("You don't have any potions.");
game.displayMessage("You don't have any consumables.");
return false;
}
}
@ -335,8 +335,16 @@ public class Player implements IPlayer {
* @param game An IGame object
*/
private void showStatus(IGame game) {
//TODO: Add item bonuses to visible stats.
game.formatStatus("HP: %s %d/%d ATK: %d DEF: %s DMG: %s", NPC.hpBar(this), hp, getMaxHealth(), getAttack(), getDefence(), getDamage());
game.formatStatus("HP: %s ATK: %d RATK: %d MATK: %d DEF: %s DMG: %s RDMG: %s MDMG: %s",
NPC.hpBar(this),
getAttack(Attack.MELEE),
getAttack(Attack.RANGED),
getAttack(Attack.MAGIC),
getDefence(),
getDamage(Attack.MELEE),
getDamage(Attack.RANGED),
getDamage(Attack.MAGIC)
);
printInventory(game);
}
@ -380,16 +388,36 @@ public class Player implements IPlayer {
if (game.canGo(dir)) {
game.move(dir);
} else if (loc.canGo(dir) && game.getMap().hasActors(loc.go(dir))) {
NPC.playSound("audio/Sword Swing-SoundBible.com-639083727.wav");
game.attack(dir, game.getMap().getActors(loc.go(dir)).get(0));
} else {
game.displayMessage("Umm, it is not possible to move in that direction.");
}
}
@Override
public int getAttack() {
return attack;
IBuffItem buff = (IBuffItem)getItem(IBuffItem.class);
if (buff != null) {
return attack + buff.getBuffDamage();
} else {
return attack;
}
}
/**
* Gets the attack with added weapon attack
*
* @param type The attack type corresponding to the weapon type.
* @return Attack as int
*/
private int getAttack(Attack type) {
IWeapon weapon = NPC.getWeapon(type, this);
if (weapon != null) {
return getAttack() + weapon.getWeaponAttack();
} else {
return getAttack();
}
}
@Override
@ -397,6 +425,21 @@ public class Player implements IPlayer {
return 10;
}
/**
* Gets the damage with added weapon damage
*
* @param type The attack type corresponding to the weapon type.
* @return Damage as int
*/
private int getDamage(Attack type) {
IWeapon weapon = NPC.getWeapon(type, this);
if (weapon != null) {
return getDamage() + weapon.getWeaponDamage();
} else {
return getDamage();
}
}
@Override
public IItem getItem(Class<?> type) {
for (IItem item : equipped.getContent()) {
@ -414,7 +457,12 @@ public class Player implements IPlayer {
@Override
public int getDefence() {
return defence;
IBuffItem buff = (IBuffItem)getItem(IBuffItem.class);
if (buff != null) {
return defence + buff.getBuffDamage();
} else {
return defence;
}
}
@Override
@ -462,6 +510,14 @@ public class Player implements IPlayer {
}
}
/**
* Performs a ranged attack on the nearest visible enemy (if any);
*
* @param game An IGame object
* @param range The range of the attack
* @param type The attack type
* @return True if the player attacked. False otherwise
*/
private boolean rangedAttack(IGame game, int range, Attack type) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {

View File

@ -4,6 +4,7 @@ import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.map.IMapView;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem;
@ -194,4 +195,25 @@ public class NPC {
str.append(BlocksAndBoxes.BLOCK_HALF);
return str.toString();
}
/**
* Gets the weapon appropriate for the used attack.
*
* @param type The attack type
* @return An IWeapon or null
*/
public static IWeapon getWeapon(Attack type, IActor actor) {
IWeapon weapon = null;
switch (type) {
case MELEE:
weapon = (IWeapon)actor.getItem(IMeleeWeapon.class);
break;
case RANGED:
weapon = (IWeapon)actor.getItem(IRangedWeapon.class);
break;
case MAGIC:
weapon = (IWeapon)actor.getItem(IMagicWeapon.class);
}
return weapon;
}
}