package ring.mobiles; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; import ring.commands.CommandHandler; import ring.commands.CommandSender; import ring.commands.WorldObjectSearch; import ring.effects.Affectable; import ring.effects.Effect; import ring.events.listeners.MobileListener; import ring.items.Armor; import ring.items.Item; import ring.magic.SpellCaster; import ring.mobiles.backbone.Equipment; import ring.mobiles.npc.NPC; import ring.movement.Room; import ring.persistence.RingConstants; import ring.players.PlayerCharacter; import ring.world.TickerEvent; import ring.world.TickerListener; import ring.world.WorldObject; import ring.world.WorldObjectMetadata; /** * The main business object for Mobiles. Aggregates all the mobile data models and * provides business methods. * @author projectmoon * */ @XmlSeeAlso({ NPC.class, PlayerCharacter.class }) @XmlAccessorType(XmlAccessType.PROPERTY) @XmlRootElement @XmlType( namespace = RingConstants.RING_NAMESPACE, propOrder= { "baseModel", "dynamicModel", "combatModel" }) public abstract class Mobile extends WorldObject implements CommandSender, TickerListener, SpellCaster { public static final long serialVersionUID = 1; //Model variables: store various aspects of this Mobile's information. private MobileBaseModel baseModel = new MobileBaseModel(); private MobileDynamicModel dynamicModel = new MobileDynamicModel(); private MobileCombatModel combatModel = new MobileCombatModel(); //If the mob is locked: if so, they cannot take actions. private boolean isLocked; //CommandHandler. This CommandHandler is protected so it can drop down into //the PlayerCharacter class. protected transient CommandHandler handler = new CommandHandler(this); //The time left before this mob can act again. protected int lockTimeRemaining; //default messages to display while the mob is locked, and when it is finished. protected String lockMessage = "You are currently focused on an activity."; protected String lockFinishedMessage = "You become aware of the world again."; //event listeners private List<MobileListener> listeners = new ArrayList<MobileListener>(); public Mobile() { baseModel = new MobileBaseModel(); dynamicModel = new MobileDynamicModel(); combatModel = new MobileCombatModel(); } // Creates a mobile from the given models. public Mobile(MobileBaseModel base, MobileDynamicModel dynamics, MobileCombatModel combat) { baseModel = base; dynamicModel = dynamics; combatModel = combat; } @XmlElement public MobileBaseModel getBaseModel() { return baseModel; } public void setBaseModel(MobileBaseModel model) { baseModel = model; } @XmlElement public MobileDynamicModel getDynamicModel() { return dynamicModel; } public void setDynamicModel(MobileDynamicModel model) { dynamicModel = model; } @XmlElement public MobileCombatModel getCombatModel() { return combatModel; } public void setCombatModel(MobileCombatModel model) { combatModel = model; } public void addMobileListener(MobileListener listener) { listeners.add(listener); } public boolean removeMobileListener(MobileListener listener) { return listeners.remove(listener); } public List<MobileListener> getMobileListeners() { return listeners; } /** * Gets the modifier for a given score based on the standard * formula: (score - 10) / 2, rounded down. * @param score * @return the modifier */ public int getModifier(int score) { return (score - 10) / 2; } /** * Checks to see if this Mobile is a player. * @return true or false */ @XmlTransient public boolean isPlayer() { //TODO implement isPlayer //return this instanceof PlayerCharacter; return false; } /** * Checks to see if this Mobile is an NPC. * @return true or false */ @XmlTransient public boolean isNPC() { //TODO implement isNPC //return this instanceof NPC; return false; } @XmlTransient public boolean isLocked() { return isLocked; } // getLockTimeRemaining method. // Returns the time left on this mob's locked status. @XmlTransient public int getLockTimeRemaining() { return lockTimeRemaining; } // setLocked method. // This method will set if the mob is locked or not. public void setLocked(boolean locked) { isLocked = locked; } public boolean attack(Affectable target, boolean melee) { //start calculations by finding the mobile's base attack bonus. int attackModifier = (int) (getBaseModel().getMobileClass().getBaseAttackBonus() .getModifier() * getBaseModel().getLevel()); //handle ranged vs melee modifiers if (melee) { attackModifier += getModifier(getBaseModel().getStrength()); } else { attackModifier += getModifier(getBaseModel().getDexterity()); } //add any other generic bonuses attackModifier += getCombatModel().getAttackBonus(); //now do the actual attack roll Random gen = new Random(System.nanoTime()); int opponentAC = target.getAC(); int baseRoll = gen.nextInt(20); //results: // always fail on a natural 1, always hit on a natural 20. if (baseRoll == 1) return false; if (baseRoll == 20) return true; //otherwise we compare. if ((baseRoll + opponentAC) >= opponentAC) return true; else return false; } /** * This method gives this Mobile XP. If the XP is enough for a * levelup, a levelup is automatically performed. * @param amount */ public void gainXP(int amount) { } /** * Convenience method for adding an item to a Mobile's inventory. * * @param item * the item to add * @return true or false based on whether item adding was successful. */ public boolean addItemToInventory(Item item) { return getDynamicModel().getInventory().addItem(item); } /** * Convenience method that removes an item from a Mobile's inventory. * * @param item * @return true or false if the removal was successful or not. */ public boolean removeItemFromInventory(Item item) { return getDynamicModel().getInventory().removeItem(item); } /** * Convenience method that equips an item on a body part for the mobile. * * @param part * @param item * @return true if equipping was successful, false otherwise. */ public boolean equip(BodyPart part, Item item) { Equipment equipment = getDynamicModel().getEquipment(); if (!equipment.hasItem(part)) { equipment.putItem(part, item); applyEffectsFromItem(item); return true; } else { return false; } } /** * Convenience method that dequips an item. * * @param part * @return the Item removed, if there was one. null otherwise. */ public Item dequip(BodyPart part) { Item i = getDynamicModel().getEquipment().removeItem(part); // Now we need to unapply all effects........ unapplyEffectsFromItem(i); // Return the item for some use later or something. return i; } // applyEffectsFromItem method. // This method applies all effects from an item to the mobile (AC, Stat // boosts, etc). public void applyEffectsFromItem(Item item) { System.out.println("Checking AC..."); // First: AC. if (item instanceof Armor) { System.out.println("It's an armor... Applying AC bonus..."); Armor a = (Armor) item; getCombatModel().changeCurrentAC(a.getAC()); } //TODO implement apply effects from item. //System.out.println("Applying the spells..."); //Effect itemEffects = item.getPassiveEffects(); //itemEffects.setTarget(this); //super.addEffect(itemEffects); } // unapplyEffectsFromItem method. // This method unapplies all effects from an item to the mobile... It's the // inverse of the // above. public void unapplyEffectsFromItem(Item item) { // First: AC. if (item instanceof Armor) { Armor a = (Armor) item; getCombatModel().changeCurrentAC(-1 * a.getAC()); } // Second: Loop through the SpellList and decast everything. Effect effects = item.getPassiveEffects(); effects.endEffect(); // Third: ... There is no third since the special programming is one // time. If necessary // Make the un-specialness code in your specialCode(...) method.... } // regenHP method. // This method regenerates HP every tick that the mobile is below max HP // based on // a regeneration rate determined by race. // TODO: implement the actual regen rate. Currently it heals at 2 hp per // sec. // TODO: investigate issues with bonus HP and regen. private void regenHP() { MobileCombatModel model = getCombatModel(); if (model.getCurrentHP() + 2 > model.getMaxHP()) model.setCurrentHP(model.getMaxHP()); // make sure we don't go over. else model.changeCurrentHP(2); } // regenMV method. // This method regenerates 5 MV every 3 ticks. It does this if the mobile is // below max MV. private void regenMV() { MobileDynamicModel model = getDynamicModel(); if (model.getCurrentMV() + 1 > model.getMaxMV()) model.setCurrentMV(model.getMaxMV()); else model.changeCurrentMV(1); } // levelUp method. // This method levels the Mobile up! public void levelup() { throw new UnsupportedOperationException("Leveling up not yet implemented"); } // increaseLockTime method. // Increases the current amount of time the mob is locked for. public void increaseLockTime(int time) { lockTimeRemaining += time; if (lockTimeRemaining > 0) this.setLocked(true); } public void decrementLockTime() { lockTimeRemaining--; } // setLockFinishedMessage method. // This method sets the message to display when the mobile is able to send // commands again. public void setLockFinishedMessage(String msg) { lockFinishedMessage = msg; } // setLockMessage method. // This method sets the message to display if it is locked. public void setLockMessage(String msg) { lockMessage = msg; } /** * This is the main processing method for Mobiles, both NPC and PC. Any class * that extends Mobile should call this method in order to get basic command * locking and stat regen facilities. */ public void processTick(TickerEvent e) { //TODO pending effects system changes //super.removeDeadEffects(); // Deal with locking. if (this.isLocked) this.decrementLockTime(); if (this.getLockTimeRemaining() <= 0) { this.setLocked(false); lockMessage = "You are currently focused on an activity."; lockFinishedMessage = "You become aware of the world again."; } // Regenerate some movement and HP. if ((getDynamicModel().getCurrentMV() < getDynamicModel().getMaxMV()) && (e.getCurrentTick() % 3 == 0)) regenMV(); if (getCombatModel().getCurrentHP() < getCombatModel().getMaxHP()) regenHP(); } public boolean canMove() { if (getBaseModel().isFighting()) { //("[GREEN]You may not leave during combat![WHITE]"); return false; } else if (getDynamicModel().getCurrentMV() - 1 <= 0) { //("[R][WHITE]You are too exhausted to move any further. Rest for awhile to regain your strength."); return true; } else { return true; } } @XmlTransient public Room getLocation() { return getDynamicModel().getCurrLocation(); } public void setLocation(Room loc) { getDynamicModel().setCurrLocation(loc); } @Override public void doCommand(String cmd) { } @Override public WorldObjectMetadata getMetadata() { WorldObjectMetadata metadata = new WorldObjectMetadata(); metadata.setName(getBaseModel().getName()); return metadata; } @Override public List<WorldObject> produceSearchList(Class<?> ... dataTypes) { List<WorldObject> objs = new ArrayList<WorldObject>(); objs.addAll(getDynamicModel().getEquipment().getItems()); objs.addAll(getDynamicModel().getInventory().getItems()); return WorldObjectSearch.filterByDataType(objs, dataTypes); } @Override public List<WorldObject> produceSearchList(List<Class<?>> dataTypes) { return produceSearchList(dataTypes.toArray(new Class<?>[0])); } /** * Aggregates information from the base model to return a short * description of this Mobile, used in at-a-glance descriptions, * such as when a player looks into a room. * @return A short description. */ @XmlTransient public String getShortDescription() { String res = getBaseModel().getName(); String lastName = getBaseModel().getLastName(); String title = getBaseModel().getTitle(); String raceName = getBaseModel().getRace().getName(); //Append these if they exist if (lastName != null && lastName.length() > 0) res += " " + lastName; if (title != null && title.length() > 0) res += " " + title; //Finallly add race and class name. res += " (" + raceName + ")"; //TODO add class name. return res; } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((baseModel == null) ? 0 : baseModel.hashCode()); result = prime * result + ((combatModel == null) ? 0 : combatModel.hashCode()); result = prime * result + ((dynamicModel == null) ? 0 : dynamicModel.hashCode()); result = prime * result + (isLocked ? 1231 : 1237); result = prime * result + ((listeners == null) ? 0 : listeners.hashCode()); result = prime * result + ((lockFinishedMessage == null) ? 0 : lockFinishedMessage .hashCode()); result = prime * result + ((lockMessage == null) ? 0 : lockMessage.hashCode()); result = prime * result + lockTimeRemaining; return result; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; Mobile other = (Mobile) obj; if (baseModel == null) { if (other.baseModel != null) return false; } else if (!baseModel.equals(other.baseModel)) return false; if (combatModel == null) { if (other.combatModel != null) return false; } else if (!combatModel.equals(other.combatModel)) return false; if (dynamicModel == null) { if (other.dynamicModel != null) return false; } else if (!dynamicModel.equals(other.dynamicModel)) return false; if (isLocked != other.isLocked) return false; if (listeners == null) { if (other.listeners != null) return false; } else if (!listeners.equals(other.listeners)) return false; if (lockFinishedMessage == null) { if (other.lockFinishedMessage != null) return false; } else if (!lockFinishedMessage.equals(other.lockFinishedMessage)) return false; if (lockMessage == null) { if (other.lockMessage != null) return false; } else if (!lockMessage.equals(other.lockMessage)) return false; if (lockTimeRemaining != other.lockTimeRemaining) return false; return true; } }