Java tutorial
/** * Copyright (C) 2013 Rusty Gerard * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.callidusrobotics.object.actor; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import com.callidusrobotics.Message; import com.callidusrobotics.attribute.Inventoriable; import com.callidusrobotics.command.Command; import com.callidusrobotics.locale.Coordinate; import com.callidusrobotics.locale.MutableCoordinate; import com.callidusrobotics.locale.Room; import com.callidusrobotics.object.AbstractObject; import com.callidusrobotics.object.EquipableData; import com.callidusrobotics.object.EquipmentSlot; import com.callidusrobotics.object.InventoriableData; import com.callidusrobotics.object.Item; import com.callidusrobotics.object.UseableData; import com.callidusrobotics.swing.Console; import com.callidusrobotics.swing.ConsoleGraphic; import com.callidusrobotics.util.TrueColor; @SuppressWarnings("PMD.TooManyMethods") public abstract class AbstractActor extends AbstractObject { protected MutableCoordinate lastPosition; protected StatBlock statBlock; protected boolean npcsAreBarriers = true; protected boolean ignoreBarriers = false; protected final Map<EquipmentSlot, Item> equipment; protected final Random random = new Random(); protected AbstractActor(final ConsoleGraphic consoleGraphic, final Coordinate position, final String name, final String description, final StatBlock statBlock) { super(consoleGraphic, position, name, description); lastPosition = new MutableCoordinate(position); this.statBlock = new StatBlock(statBlock); this.equipment = new HashMap<EquipmentSlot, Item>(); } public String getNameFirstPerson() { return "The " + StringUtils.lowerCase(name); } public final boolean hasMoved() { return !position.equals(lastPosition); } @Override public String getDescription() { final StringBuffer description = new StringBuffer(64); description.append(super.getDescription()); if (!StringUtils.isBlank(description)) { description.append(' '); } description.append("It looks "); final StatBlock stats = getCurrentStatBlock(); final int healthPercent = stats.currentHp * 100 / stats.maxHp; if (healthPercent == 100) { description.append("unhurt."); } else if (healthPercent > 75) { description.append("grazed."); } else if (healthPercent > 50) { description.append("wounded."); } else if (healthPercent > 25) { description.append("severely wounded."); } else { description.append("nearly dead."); } return description.toString(); } @Override public void setPosition(final int row, final int col) { lastPosition.setRow(position.getRow()); lastPosition.setCol(position.getCol()); position.setRow(row); position.setCol(col); } @Override public void setPosition(final Coordinate position) { lastPosition.setRow(this.position.getRow()); lastPosition.setCol(this.position.getCol()); this.position.setRow(position.getRow()); this.position.setCol(position.getCol()); } @Override public void draw(final Console console) { console.print(getRow(), getCol(), getConsoleGraphic()); } public int getLastRow() { return lastPosition.getRow(); } public int getLastCol() { return lastPosition.getCol(); } public Coordinate getLastPosition() { return lastPosition; } public StatBlock getCurrentStatBlock() { StatBlock currentStats = new StatBlock(statBlock); for (final Item item : equipment.values()) { if (item.getStatBlock() != null) { currentStats = currentStats.combine(item.getStatBlock()); } } if (currentStats.currentHp > currentStats.maxHp) { currentStats.currentHp = currentStats.maxHp; } statBlock.currentHp = currentStats.currentHp; return currentStats; } public void modifyStatBlock(final StatBlock statModifiers) { statBlock = statBlock.combine(statModifiers); final StatBlock modifiedStats = getCurrentStatBlock(); if (statBlock.currentHp > modifiedStats.maxHp) { statBlock.currentHp = modifiedStats.maxHp; } } public void setCurrentHp(final int currentHp) { final StatBlock currentStats = getCurrentStatBlock(); if (currentStats.maxHp < currentHp) { statBlock.setCurrentHp(currentStats.maxHp); } else { statBlock.setCurrentHp(currentHp); } } public Message attack(final AbstractActor other, final Command command, final Room currentRoom) { // Quick and dirty attack system final int toHitRoll = 1 + random.nextInt(20); final StatBlock attackerStats = getCurrentStatBlock(); final StatBlock defenderStats = other.getCurrentStatBlock(); Message message = new Message(command, getNameFirstPerson() + " misses.", getForeground(), TrueColor.BLACK); final int damageMean = command == Command.ATTACKMELEE ? getDamage() + attackerStats.getStrength() : getDamage(); final int damageStdev = 1 + damageMean / 4; final int damageRoll = (int) (random.nextGaussian() * damageStdev) + damageMean; if (toHitRoll == 20) { // Automatic hit other.setCurrentHp(defenderStats.currentHp - damageRoll); message = new Message(command, getNameFirstPerson() + " critically strikes.", TrueColor.BLACK, TrueColor.RED); } else if (toHitRoll != 1 && toHitRoll + attackerStats.agility > defenderStats.agility) { // Chance to hit final int damage = Math.max(0, damageRoll - defenderStats.defense); other.setCurrentHp(defenderStats.currentHp - damage); if (damage > 0) { message = new Message(command, getNameFirstPerson() + " strikes.", getForeground(), TrueColor.BLACK); } else { message = new Message(command, getNameFirstPerson() + " strikes a glancing blow.", getForeground(), TrueColor.BLACK); } } final Item weapon = this.equipment.get(EquipmentSlot.WEAPON); if (weapon != null) { weapon.fire(this, currentRoom); } return message; } protected int getDamage() { final Item weapon = equipment.get(EquipmentSlot.WEAPON); if (weapon != null) { return weapon.getDamage(); } return 1; } protected int getRange() { final Item weapon = equipment.get(EquipmentSlot.WEAPON); if (weapon != null && weapon.getRange() > 0) { return weapon.getRange(); } return 0; } public Item removeEquippedItem(final EquipmentSlot slot) { return equipment.remove(slot); } public Inventoriable getEquippedItem(final EquipmentSlot slot) { return equipment.get(slot); } public boolean equipItemInInventoryToEquipmentSlot(final int index, final EquipmentSlot slot) { final Item item = inventory.get(index); if (item.isEquipable(slot)) { inventory.remove(index); if (equipment.containsKey(slot)) { addItemToInventory(equipment.get(slot)); } equipment.put(slot, item); } return item.isEquipable(slot); } @Override public List<Item> removeAllItemsFromInventory() { addItemsToInventory(new ArrayList<Item>(equipment.values())); equipment.clear(); return super.removeAllItemsFromInventory(); } protected boolean canMove(final Room currentRoom, final Coordinate offset) { if (offset == null) { return false; } final Coordinate desiredPosition = new Coordinate(getRow() + offset.getRow(), getCol() + offset.getCol()); final boolean isInsideMap = currentRoom.checkCoordinatesRelative(desiredPosition); final boolean isBarrier = isInsideMap && ((npcsAreBarriers ? currentRoom.isBarrierRelative(desiredPosition) : false) || (ignoreBarriers ? false : currentRoom.getTileRelative(desiredPosition).isBarrier())); final boolean onExitTile = currentRoom.getTileRelative(position).getRoomExit() != null; return (isInsideMap && !isBarrier) || (!isInsideMap && onExitTile); } protected boolean move(final Room currentRoom, final Coordinate offset) { if (offset == null) { return false; } final int rowOffset = offset.getRow(); final int colOffset = offset.getCol(); final Coordinate desiredPosition = new Coordinate(getRow() + rowOffset, getCol() + colOffset); final boolean isInsideMap = currentRoom.checkCoordinatesRelative(desiredPosition); // Update the last position so hasMoved() will be accurate setPosition(position); if (!canMove(currentRoom, offset)) { return false; } if (isInsideMap) { // Simple movement inside the room with no collision setPosition(desiredPosition); } else { // Player is trying to back out of the room he just entered -- make the GameMediator think we just stepped on the exit tile setPosition(getRow() - rowOffset, getCol() - colOffset); setPosition(getRow() + rowOffset, getCol() + colOffset); } return true; } public MutableCoordinate desiredDirection(final Command command) { boolean tryToMove = false; int rowOffset = 0; int colOffset = 0; if (command.toString().contains("NORTH")) { rowOffset = -1; tryToMove = true; } if (command.toString().contains("SOUTH")) { rowOffset = 1; tryToMove = true; } if (command.toString().contains("EAST")) { colOffset = 1; tryToMove = true; } if (command.toString().contains("WEST")) { colOffset = -1; tryToMove = true; } if (command == Command.ASCEND || command == Command.DECEND || command == Command.REST) { tryToMove = true; } if (tryToMove) { return new MutableCoordinate(rowOffset, colOffset); } return null; } public MutableCoordinate desiredPosition(final Command command) { final Coordinate position = desiredDirection(command); if (position != null) { return new MutableCoordinate(getRow() + position.getRow(), getCol() + position.getCol()); } return new MutableCoordinate(this.position); } public Message processCommand(final Room currentRoom, final Command command) { Validate.notNull(currentRoom); Validate.notNull(command); // Clear the last position so that hasMoved() is up to date setPosition(getPosition()); final Coordinate desiredDirection = desiredDirection(command); if (desiredDirection != null) { final boolean moved = move(currentRoom, desiredDirection); if (moved) { return new Message(command, null, null, null); } return new Message(Command.REST, null, null, null); } return new Message(Command.UNKNOWN, null, null, null); } public Item makeCorpse() { // TODO: Refactor this to use the item factory? final String identifier = StringUtils.replace(name, "\\s+", "_") + "_corpse"; return new Item(identifier, new ConsoleGraphic('%', getForeground(), getBackground()), 1, new InventoriableData(name + " Corpse", "The remains of a " + name + ".", false), new UseableData(false, false, -1, null, null, null, null), new EquipableData(0, 0, null, null)); } }