package com.asteria.game.character.combat; import java.util.concurrent.TimeUnit; import com.asteria.game.NodeType; import com.asteria.game.World; import com.asteria.game.character.CharacterNode; import com.asteria.game.character.npc.Npc; import com.asteria.game.character.player.Player; import com.asteria.task.EventListener; /** * Controls and gives access to the main parts of the combat process such as * starting and ending combat sessions. * * @author lare96 <http://github.com/lare96> */ public final class CombatBuilder { /** * The character in control of this combat builder. */ private final CharacterNode character; /** * The current character that the controller is attacking. */ private CharacterNode currentVictim; /** * The last character that attacked the controller. */ private CharacterNode lastAttacker; /** * The task that handles the entire combat process. */ private CombatSession combatTask; /** * The task that handles the pre-combat process. */ private CombatDistanceListener distanceTask; /** * The cache of damage dealt to this controller during combat. */ private final CombatDamage damageCache = new CombatDamage(); /** * The combat strategy this character attacking with. */ private CombatStrategy strategy; /** * The timer that controls how long this character must wait to attack. */ private int attackTimer; /** * The cooldown timer used when the character breaks the combat session. */ private int cooldown; /** * Creates a new {@link CombatBuilder}. * * @param character * the character in control of this combat builder. */ public CombatBuilder(CharacterNode character) { this.character = character; } /** * Prompts the controller to attack {@code target}. If the controller is * already attacking the target this method has no effect. * * @param target * the character that this controller will be prompted to attack. */ public void attack(CharacterNode target) { if (character.equals(target)) return; if (target.equals(currentVictim)) { determineStrategy(); if (character.getPosition().withinDistance(currentVictim.getPosition(), strategy.attackDistance(character))) { character.getMovementQueue().reset(); } } character.getMovementQueue().follow(target); if (combatTask != null && combatTask.isRunning()) { currentVictim = target; if (character.getType() == NodeType.PLAYER) { Player player = (Player) character; if (player.isAutocast() || player.getCastSpell() == null || attackTimer < 1) { cooldown = 0; } } return; } if (distanceTask != null && distanceTask.isRunning()) distanceTask.cancel(); distanceTask = new CombatDistanceListener(this, target); World.submit(distanceTask); } /** * Instantly executes the combat task regardless of its state. Should really * only be used for instant special attacks. */ public void instant() { combatTask.execute(); } /** * Resets this builder by discarding various values associated with the * combat process. */ public void reset() { if (distanceTask != null) distanceTask.cancel(); if (combatTask != null) combatTask.cancel(); currentVictim = null; combatTask = null; attackTimer = 0; strategy = null; cooldown = 0; character.faceCharacter(null); character.setFollowing(false); } /** * Resets the attack timer to it's original value based on the combat * strategy. */ public void resetAttackTimer() { if (strategy == null) return; attackTimer = strategy.attackDelay(character); } /** * Sets the attack timer to a value of {@code 0}. */ public void clearAttackTimer() { attackTimer = 0; } /** * Starts the cooldown sequence for this controller. * * @param resetAttack * if the attack timer should be reset. */ public void cooldown(boolean resetAttack) { if (strategy == null) return; cooldown = 10; character.setFollowing(false); if (resetAttack) { attackTimer = strategy.attackDelay(character); } } /** * Calculates and sets the combat strategy. */ public void determineStrategy() { this.strategy = character.determineStrategy(); } /** * Determines if this character is attacking another character. * * @return {@code true} if this character is attacking another character, * {@code false} otherwise. */ public boolean isAttacking() { return currentVictim != null; } /** * Determines if this character is being attacked by another character. * * @return {@code true} if this character is being attacked by another * character, {@code false} otherwise. */ public boolean isBeingAttacked() { return !character.getLastCombat().elapsed(5, TimeUnit.SECONDS); } /** * Determines if this character is being attacked by or attacking another * character. * * @return {@code true} if this player is in combat, {@code false} * otherwise. */ public boolean inCombat() { return isAttacking() || isBeingAttacked(); } /** * Determines if this combat builder is in cooldown mode. * * @return {@code true} if this combat builder is in cooldown mode, * {@code false} otherwise. */ public boolean isCooldown() { return cooldown > 0; } /** * Gets the cooldown timer used when the character breaks the combat * session. * * @return the cooldown timer. */ public int getCooldown() { return cooldown; } /** * Decrements the cooldown timer used when the character breaks the combat * session. */ public void decrementCooldown() { cooldown--; } /** * Gets the timer that controls how long this character must wait to attack. * * @return the timer determines when the controller attacks. */ public int getAttackTimer() { return attackTimer; } /** * Decrements the timer that controls how long this character must wait to * attack. */ public void decrementAttackTimer() { attackTimer--; } /** * Gets the character in control of this combat builder. * * @return the character in control. */ public CharacterNode getCharacter() { return character; } /** * Gets the current character that the controller is attacking. * * @return the character the controller is attacking */ public CharacterNode getVictim() { return currentVictim; } /** * Gets the last character that attacked the controller. * * @return the last character that attacked. */ public CharacterNode getLastAttacker() { return lastAttacker; } /** * Sets the value for {@link CombatBuilder#lastAttacker}. * * @param lastAttacker * the new value to set. */ public void setLastAttacker(CharacterNode lastAttacker) { this.lastAttacker = lastAttacker; } /** * Gets the combat strategy this character attacking with. * * @return the combat strategy. */ public CombatStrategy getStrategy() { return strategy; } /** * Gets the task that handles the entire combat process. * * @return the task for the combat process. */ public CombatSession getCombatTask() { return combatTask; } /** * Gets the cache of damage dealt to this controller during combat. * * @return the cache of damage. */ public CombatDamage getDamageCache() { return damageCache; } /** * An {@link EventListener} implementation that is used to listen for the * controller to become in proper range of the victim. * * @author lare96 <http://github.com/lare96> */ private static final class CombatDistanceListener extends EventListener { /** * The combat builder owned by the controller. */ private final CombatBuilder builder; /** * The victim that will be listened for. */ private final CharacterNode victim; /** * Create a new {@link CombatDistanceListener}. * * @param builder * the combat builder owned by the controller. * @param victim * the victim that will be listened for. */ public CombatDistanceListener(CombatBuilder builder, CharacterNode victim) { super.attach(builder.getCharacter().getType() == NodeType.PLAYER ? ((Player) builder.getCharacter()) : ((Npc) builder .getCharacter())); this.builder = builder; this.victim = victim; } @Override public boolean canExecute() { builder.determineStrategy(); builder.attackTimer = 0; builder.cooldown = 0; if (!builder.character.getPosition().isViewableFrom(victim.getPosition())) { builder.reset(); this.cancel(); return true; } if (builder.character.getType() == NodeType.NPC) { Npc npc = (Npc) builder.character; if (!npc.getPosition().isViewableFrom(npc.getOriginalPosition()) && npc.getDefinition().isRetreats()) { npc.getMovementQueue().walk(npc.getOriginalPosition()); builder.reset(); this.cancel(); return true; } } return builder.character.getPosition().withinDistance(victim.getPosition(), builder.strategy.attackDistance(builder.getCharacter())); } @Override public void run() { builder.getCharacter().getMovementQueue().reset(); builder.currentVictim = victim; if (builder.combatTask == null || !builder.combatTask.isRunning()) { builder.combatTask = new CombatSession(builder); World.submit(builder.combatTask); } } } }