package game.npc; import game.strat.Intelligence; import game.strat.Strategy; import java.util.Vector; import gameEngine.drawable.*; import gameEngine.renderer.IRenderer; import gameEngine.drawable.particle.*; import gameEngine.scene.*; import graphicslib3D.*; /** * Acts as an abstract class for creating characters in Midnight Animus. This * class was origionally called Character but was changed due to the fact that * the Character class already exists in standard Java. Characters * must have a position in the world, a model which represents them, animation * keyframe indices, and hit points among other things. To create a specific * type of person, a subclass will need to be created which initializes * the model, textures, and stats for that particular person. *

* While this class contains an intelligence that is required for NPCs to * evaluate different strategies, a player character can be created by * overriding methods having to do with intelligence. *

* It is important to note that the x and y coordinates used in this class * are relative to the game plane - not the x and y positions of the characters * in 3D space. * * @author Steven Kodani and Kuen-Lung Yeh */ public abstract class Person extends Group implements IAnimationListener { //Height is in feet private final float TEXT_HEIGHT = 6.0f; //x, y, and heading should never be accessed directly private float x, y; private float heading; private Intelligence myInt; private int maxHP, currHP, baseAttack, armor; private float movementSpeed; //The variable myLists contains the lists that the character belongs to. //When the character "dies", they will be removed from those lists. private Vector> myLists; private MD5Model model; /** * Constructs a new Person by initializing their statistics, loading their * model and texture information, and setting their initial keyframe indices. * @param pX The x position of the person. * @param pY The y position of the person. * @param pHeading The direction that the person is facing in degrees. * @param pHP The maximum (and current) amount of HP that the character has. * @param pBaseAttack The default attack value for the character. * @param pArmor The armor rating of the character. * @param pMovementSpeed The movement speed of the character in ft/s. * @param texts The game's list of billboard texts. * @param cam The game camera used for calculating billboard text rotations. * @param pModel The model representing this person. */ public Person(float pX, float pY, float pHeading, int pHP, int pBaseAttack, int pArmor, int pMovementSpeed, MD5Model pModel) { super("Person"); x = 0; y = 0; heading = 0; addX(pX); addY(pY); setHeading(pHeading); maxHP = pHP; currHP = pHP; movementSpeed = pMovementSpeed; baseAttack = pBaseAttack; armor = pArmor; myInt = new Intelligence(); myLists = new Vector>(); //Set up the model for this character model = pModel; model.addListener(this); model.addController(new AnimationController()); addChildren(model); } /** * Removes all particle effects and damage texts from this person. */ public void clearTemporaryChildren() { removeAll(); addChildren(model); } /** * Returns the character's model. * @return The character's model. */ public MD5Model getModel() { return model; } /** * Sets the keyframe bounds for this person's model. Will not do anything * if the model has not yet been intialized. * @param start The keyframe that this person should start at. * @param end The keyframe that this person should end at. */ public void setKeyframeBounds(int start, int end) { if (model != null) model.setKeyframeBounds(start, end); } /** * Adds a strategy to this character's intelligence * @param s The character's new strategy. */ public void addStrategy(Strategy s) { myInt.addStrategy(s); } /** * Adds a list which the character belongs to. When the character "dies", * it will be removed from the all of the lists that it belongs to. * @param list A list that the character belongs to. */ public void addList(Vector list) { myLists.add(list); } /** * Returns the x position of the character. * @return The x position of the character. */ public float getX() { return x; } /** * Adds a given amount to the character's x coordinate. * @param dX The amount to add to the character's x coordinate. */ public void addX(float dX) { x += dX; super.translate(dX, 0, 0); } /** * Returns the y position of the character. * @return The y position of the character. */ public float getY() { return y; } /** * Adds a given amount to the character's y coordinate. * @param dY The amount to add to the character's y coordinate. */ public void addY(float dY) { y += dY; super.translate(0, 0, dY); } /** * Sets the character's heading based on an input parameter. * @param h The character's new heading in degrees. */ public void setHeading(float h) { heading = h; clearRotate(); super.rotate(0, (float) Math.toRadians(heading), 0); } /** * Rotates the character by a given amount. * @param dH The amount to rotate the character. */ public void alterHeading(float dH) { heading += dH; super.rotate(0, (float) Math.toRadians(dH), 0); } /** * Returns this person's heading. * @return This person's heading. */ public float getHeading() { return heading; } /** * Inflicts damage on a character. The amount of damage will be mitigated * by the amount of armor the character has. If the result of the damage * destroys the person, then the person will be removed from all of the lists * that they belong to. * @param dHP The amount of damage done to the character prior to armor mitigation. */ public void damageHP(int dHP) { int damage = Math.max(0, dHP - armor); currHP -= damage; if (currHP <= 0) { for (int i = 0; i < myLists.size(); i++) myLists.get(i).remove(this); LifeTimeController life = new LifeTimeController(4.5f); addController(life); myInt.forceStrategy(null); doAnimationDeath(); } //Create a particle effect meant to look like sparks float[] color = { 0.5f, 0.5f, 0.2f }; Emitter emit = new BurstEmitter(2, 10.0f, true); ParticleSystem particles = new ParticleSystem(color, 2.0f, 0.02f, emit, 0.1f, Math.min(20, armor * 2)); particles.addController(new AnimationController()); particles.addForce(new Vector3D(0.0, -32.174, 0.0)); particles.setFloorOffset(-4.0f); particles.setBounceMode(ParticleSystem.COLLIDE_BOUNCE); particles.translate(getX(), 4.0f, getY()); getParent().addChildren(particles); //Add a new damage text string which will float above the character for a short time StrokeText damageText = new StrokeText(Integer.toString(damage), true, true); damageText.setRenderQueueMode(IRenderer.RENDERMODE_OPAQUE); damageText.translate(0, 6, 0); damageText.setColor(0.8f, 0.1f, 0.1f); damageText.addController(new FloatController()); damageText.addController(new LifeTimeController(5)); addChildren(damageText); } /** * Returns the character's current HP count. * @return The character's current HP count. */ public int getHP() { return currHP; } /** * Returns the character's maximum HP value. * @return The character's maximum HP value. */ public int getMaxHP() { return maxHP; } /** * Returns the person's current HP to their maximum. */ public void restoreHP() { currHP = maxHP; } /** * Heals the character by a certain amount by adding the heal amount * to the character's current HP. If too much HP is healed, then the * character's current HP will be set to their maximum HP. * @param dHP The amount of HP to heal. */ public void healHP(int dHP) { int tempHP = currHP; currHP = Math.min(maxHP, currHP + dHP); //Add a new heal text string which will float above the character for a short time StrokeText healText = new StrokeText(Integer.toString(currHP - tempHP), true, true); healText.setRenderQueueMode(IRenderer.RENDERMODE_OPAQUE); healText.translate(0, 6, 0); healText.setColor(0.0f, 1.0f, 0.2f); healText.addController(new FloatController()); healText.addController(new LifeTimeController(5)); addChildren(healText); } /** * Returns the character's movement speed. * @return The character's movement speed. */ public float getMovementSpeed() { return movementSpeed; } /** * Sets the character's movement speed. * @param pMovementSpeed The character's new movement speed. */ public void setMovementSpeed(float pMovementSpeed) { movementSpeed = pMovementSpeed; } /** * Returns the character's base attack. * @return The character's base attack. */ public int getBaseAttack() { return baseAttack; } /** * Mutates this person's base attack value. * @param pBaseAttack This person's new base attack value. */ public void setBaseAttack(int pBaseAttack) { baseAttack = pBaseAttack; } /** * Returns the character's armor rating. * @return The character's armor rating. */ public int getArmor() { return armor; } /** * Sets the character's armor rating. * @param pArmor The character's new armor rating. */ public void setArmor(int pArmor) { armor = pArmor; } /** * Returns the person currently targeted by this character's current * strategy. Will return null if there is no current strategy selected * or if the selected strategy doesn't have a target. * @return The person that this person is currently targeting. */ public Person getTarget() { if (myInt.getCurrentStrategy() == null) return null; else return myInt.getCurrentStrategy().getTarget(); } /** * Sets the target of this person by setting the current strategy's target * if there is a current strategy. If they don't have a current strategy, * then their target will not be set. * @param p The person who will be this person's new target. */ public void setTarget(Person p) { if (myInt.getCurrentStrategy() != null) myInt.getCurrentStrategy().setTarget(p); } /** * Returns the intelligence for this person. * @return The intelligence for this person. */ public Intelligence getIntelligence() { return myInt; } /** * Elapses time in the person's intelligence. This method "drives" the * actions of the character. * @param elapsedTime The amount of time that has elapsed in milliseconds * since the last time that this method was called. */ public void tick(int elapsedTime) { myInt.tick(elapsedTime); } /** * Moves the character toward or away from a specified point. This method * has the potential to change the character's heading. The character * moves at a speed specified by their movement speed property. * @param pX The x coordinate of the point which the character is moving to. * @param pY The y coordinate of the point which the character is moving to. * @param forward A boolean variable specifiying whether the character is * moving toward the specified point or if the character is moving away * from the specified point. * @param elapsedTime The number of milliseconds that have elapsed since * the last movement. This is used to calculate how far the character moves. */ public void move(float pX, float pY, boolean forward, int elapsedTime) { //Change the heading to the specified point lookAt(pX, pY, forward); //Add a certain amount to the current position based on the new heading of the character double dX = (movementSpeed * Math.sin(Math.toRadians(heading))) * elapsedTime / 1000; double dY = (movementSpeed * Math.cos(Math.toRadians(heading))) * elapsedTime / 1000; addX((float) dX); addY((float) dY); doAnimationRun(); } /** * Moves the character forward or backward based on their current heading. * The character moves at a speed specified by their movement speed property. * @param forward A boolean variable specifiying whether or not the character * should move forward or backward. * @param elapsedTime The number of milliseconds that have elapsed since * the last movement. This is used to calculate how far the character moves. */ public void move(boolean forward, int elapsedTime) { //Add a certain amount to the current position based on the new heading of the character double dX = (movementSpeed * Math.sin(Math.toRadians(heading))) * elapsedTime / 1000; double dY = (movementSpeed * Math.cos(Math.toRadians(heading))) * elapsedTime / 1000; if (!forward) { dX *= -1; dY *= -1; } addX((float) dX); addY((float) dY); doAnimationRun(); } /** * Adjust this character's heading so that they will be facing a specified * point. * @param pX The x-component of the point the character should look at. * @param pY The y-component of the point the character should look at. * @param forward Whether the character should look at the specified point * or look away from it. */ public void lookAt(float pX, float pY, boolean forward) { //Calculate the distance between the character and the target point double dist = Math.sqrt((x - pX) * (x - pX) + (y - pY) * (y - pY)); //Protect from divide by zero if (dist < .001 && dist > -.001) dist = 1; //If the point to move toward isn't in a strait line on the Y axis, //calculate a new heading for the character if ((pX - x) > 0.001 || (pX - x) < -0.001) setHeading((float) (Math.toDegrees(Math.acos((pY - y) / dist)) * Math.signum(pX - x))); else if ((pY - y) > 0) setHeading(0); else setHeading(180); //If looking away, subtract 180 degrees from the heading if (!forward) setHeading(getHeading() - 180); } /** * Is called when this person's model has finished a cycle of its current * animation. When the animation cycle has finished set the character to * use their idle animation. */ public void cycleCompleted(MD5Model model) { doAnimationIdle(); } /** * Triggers the character's idle animation. The keyframe range used for the * idle animation varies depending on the model so this method must be * implemented in a character's concrete subclass. */ public abstract void doAnimationIdle(); /** * Triggers the person's run animation. If not overriden in a subclass * then the character's idle animation will be used instead. */ public void doAnimationRun() { doAnimationIdle(); } /** * Triggers the person's attack animation. If not overriden in a subclass * then the character's idle animation will be used instead. */ public void doAnimationDefaultAttack() { doAnimationIdle(); } /** * Triggers the person's special attack animation. If not overriden in a * subclass then the character's idle animation will be used instead. */ public void doAnimationEnhancedAttack() { doAnimationIdle(); } /** * Triggers the person's spell casting animation. If not overriden in a * subclass then the character's idle animation will be used instead. */ public void doAnimationCastSpell() { doAnimationIdle(); } /** * Triggers the person's ultimate ability animation. If not overriden in a * subclass then the character's idle animation will be used instead. */ public void doAnimationUltimateAbility() { doAnimationIdle(); } /** * Triggers the person's miscellaneous animation. If not overriden in a * subclass then the character's idle animation will be used instead. */ public void doAnimationMiscellaneous() { doAnimationIdle(); } /** * Triggers the person's death animation. If not overriden in a subclass * then the character's idle animation will be used instead. */ public void doAnimationDeath() { doAnimationIdle(); } }