package net.scapeemulator.game.model.mob;
import net.scapeemulator.game.model.Entity;
import net.scapeemulator.game.model.Position;
import net.scapeemulator.game.model.SpotAnimation;
import net.scapeemulator.game.model.World;
import net.scapeemulator.game.model.area.Area;
import net.scapeemulator.game.model.area.PositionArea;
import net.scapeemulator.game.model.area.QuadArea;
import net.scapeemulator.game.model.mob.action.CombatAction;
import net.scapeemulator.game.model.mob.combat.CombatBonuses;
import net.scapeemulator.game.model.mob.combat.CombatHandler;
import net.scapeemulator.game.model.mob.combat.HitType;
import net.scapeemulator.game.model.mob.combat.Hits;
import net.scapeemulator.game.model.pathfinding.Path;
import net.scapeemulator.game.model.player.Player;
import net.scapeemulator.game.model.player.action.PlayerDeathAction;
import net.scapeemulator.game.model.player.skills.prayer.HeadIcon;
import net.scapeemulator.game.task.Action;
/**
* This represents an in-game Mob. An Entity with (mainly) {@link Animation},
* {@link CombatHandler} and {@link WalkingQueue}.
*/
public abstract class Mob extends Entity {
private static final Animation CANCEL_ANIMATION = new Animation(-1);
protected int id;
protected boolean teleporting;
protected boolean clipped = true;
protected final WalkingQueue walkingQueue = new WalkingQueue(this);
protected Direction firstDirection = Direction.NONE;
protected Direction secondDirection = Direction.NONE;
protected Direction mostRecentDirection = Direction.SOUTH;
protected int size = 1;
protected Animation animation;
protected SpotAnimation spotAnimation;
protected Position turnToPosition;
protected Hits hits = new Hits(this);
protected String forcedChat;
protected Mob currentTarget;
protected int turnToTargetId = -1;
protected int frozen;
protected HeadIcon headIcon = HeadIcon.NONE;
protected Action<? extends Mob> action;
protected boolean hidden;
protected CombatHandler<? extends Mob> combatHandler;
protected CombatBonuses combatBonuses;
/**
* Gets the id of this {@link Mob}.
*
* @return The id.
*/
public int getId() {
return id;
}
/**
* Reset the id of this {@link Mob} to 0.
*/
public void resetId() {
this.id = 0;
}
/**
* Set the id of this {@link Mob} to id.
*
* @param id The id to set it to.
*/
public void setId(int id) {
this.id = id;
}
/**
* Set the size of this {@link Mob} to size.
*
* @param size The size to set it to.
*/
public void setSize(int size) {
if (size > 0) {
this.size = size;
}
}
/**
* Gets the size of this {@link Mob}.
*
* @return The size.
*/
public int getSize() {
return size;
}
/**
* Gets whether this {@link Mob} is active or not.
*
* @return Whether the {@link getId()} isn't 0.
*/
public boolean isActive() {
return id != 0;
}
/**
* Start action. When different from the current Action, the currentAction
* is stopped and action is scheduled.
*
* @param action The {@link Action} to start.
* @see stopAction()
*/
public void startAction(Action<?> action) {
if (this.action != null) {
if (this.action instanceof PlayerDeathAction) {
System.out.println("SHOULDN'T BE ABLE TO DO THAT!!! " + action.toString());
}
if (this.action.equals(action)) {
return;
}
stopAction();
}
this.action = action;
World.getWorld().getTaskScheduler().schedule(action);
}
/**
* Stop the current {@link Action}.
*
* @see Action#stop()
*/
public void stopAction() {
if (action != null) {
Action<? extends Mob> oldAction = action;
action = null;
oldAction.stop();
}
}
/**
* Gets whether this {@link Mob} is teleporting.
*
* @return Whether this is teleporting.
* @see teleport(Position)
*/
public boolean isTeleporting() {
return teleporting;
}
/**
* Gets whether or not this Mob has forced overhead chat to send to all
* players during the update process.
*
* @return if there is forced chat to be sent in the update blocks
*/
public boolean isChatForced() {
return forcedChat != null;
}
public void setForcedChat(String forcedChat) {
this.forcedChat = forcedChat;
}
/**
* Gets the forced overhead chat of this Mob for the update blocks.
*
* @return the chat text
*/
public String getForcedChat() {
return forcedChat;
}
/**
* Teleport this {@link Mob} to the specified Position. This implies {@link
* getPosition()} is equal to position, {@link isTeleporting()} == true and
* the {@link WalkingQueue} will be reset.
*
* @param position The {@link Position} to teleport to.
* @see WalkingQueue#reset()
*/
public void teleport(Position position) {
this.position = position;
this.teleporting = true;
this.walkingQueue.reset();
}
/**
* Gets the {@link WalkingQueue} used by this {@link Mob}.
*
* @return The {@link WalkingQueue}.
*/
public WalkingQueue getWalkingQueue() {
return walkingQueue;
}
/**
* Gets the first {@link Direction} of this {@link Mob}.
*
* @return The first {@link Direction}.
*/
public Direction getFirstDirection() {
return firstDirection;
}
/**
* Gets the second {@link Direction} of this {@link Mob}.
*
* @return The second {@link Direction}.
*/
public Direction getSecondDirection() {
return secondDirection;
}
/**
* Gets whether this {@link Mob} isn't walking. Determined by {@link
* getFirstDirection()} and {@link getSecondDirection()} both being
* {@link Direction#NONE}.
*
* @return Whether this isn't walking.
*/
public boolean notWalking() {
return firstDirection == Direction.NONE && secondDirection == Direction.NONE;
}
/**
* Get the most recent {@link Direction} of this {@link Mob}.
*
* @return The previous non {@link Direction#NONE},
* {@link getSecondDirection} or {@link getFirstDirection}
*/
public Direction getMostRecentDirection() {
return mostRecentDirection;
}
/**
* Set the {@link getFirstDirection} and {@link getSecondDirection} to
* firstDirection and secondDirection. {@link getMostRecentDirection()} will
* be set to the current non {@link Direction#NONE},
* {@link getSecondDirection} or {@link getFirstDirection}.
*
* @param firstDirection The {@link Direction} to set the
* {@link getFirstDirection} to.
* @param secondDirection The {@link Direction} to set the
* {@link getSecondDirection} to.
*/
public void setDirections(Direction firstDirection, Direction secondDirection) {
this.firstDirection = firstDirection;
this.secondDirection = secondDirection;
if (secondDirection != Direction.NONE) {
mostRecentDirection = secondDirection;
} else if (firstDirection != Direction.NONE) {
mostRecentDirection = firstDirection;
}
}
/**
* Gets the current {@link Action} of this {@link Mob}.
*
* @return The current {@link Action}. Returns null if there is currently no
* {@link Action}.
* @see startAction(Action)
* @see stopAction()
*/
public Action<? extends Mob> getAction() {
return action;
}
public HeadIcon getHeadIcon() {
return headIcon;
}
public void setHeadIcon(HeadIcon headIcon) {
this.headIcon = headIcon;
}
/**
* Gets the current {@link Animation} of this {@link Mob}.
*
* @return The current {@link Animation}. Returns null if there is currently
* no {@link Animation}.
* @see playAnimation(Animation)
* @see isAnimationUpdated()
*/
public Animation getAnimation() {
return animation;
}
/**
* Gets the current {@link SpotAnimation} of this {@link Mob}.
*
* @return The current {@link SpotAnimation}. Returns null if there is
* currently no {@link SpotAnimation}.
* @see playSpotAnimation(SpotAnimation}
* @see isSpotAnimationUpdated()
*/
public SpotAnimation getSpotAnimation() {
return spotAnimation;
}
/**
* Gets whether the current {@link Animation} is updated.
*
* @return Whether {@link getAnimation()} isn't null.
*/
public boolean isAnimationUpdated() {
return animation != null;
}
/**
* Gets whether the current {@link SpotAnimation} is updated.
*
* @return Whether {@link getSpotAnimation()} isn't null.
*/
public boolean isSpotAnimationUpdated() {
return spotAnimation != null;
}
/**
* Play the provided {@link Animation}. {@link getAnimation()} will be equal
* to animation.
*
* @param animation The {@link Animation} to play.
*/
public void playAnimation(Animation animation) {
this.animation = animation;
}
/**
* Cancels the {@link getAnimation()}. {@link getAnimation()} will be equal
* to {@link CANCEL_ANIMATION}.
*/
public void cancelAnimation() {
animation = CANCEL_ANIMATION;
}
/**
* Play the provided {@link SpotAnimation}. {@link getAnimation()} will be
* equal to animation.
*
* @param spotAnimation The {@link SpotAnimation} to play.
*/
public void playSpotAnimation(SpotAnimation spotAnimation) {
this.spotAnimation = spotAnimation;
}
/**
* Turn to the provided turnToPosition. {@link getTurnToPosition()} will be
* equal to turnToPosition.
*
* @param turnToPosition The {@link Position} to turn to.
*/
public void turnToPosition(Position turnToPosition) {
this.turnToPosition = turnToPosition;
}
/**
* Gets whether the {@link getTurnToPosition()} is updated.
*
* @return Whether {@link getTurnToPosition()} isn't null.
*/
public boolean isTurnToPositionUpdated() {
return turnToPosition != null;
}
/**
* Gets the {@link Position} to turn to.
*
* @return The {@link Position} to turn to.
* @see turnToPosition(Position)
*/
public Position getTurnToPosition() {
return turnToPosition;
}
/**
* Turn to the specified target. {@link getTurnToTarget()} = target
* <b>and</b> {@link getTurnToTargetId()} = {@link getTargetId(Mob)}
*
* @param target The {@link Mob} to turn to.
*/
public void turnToTarget(Mob target) {
if (currentTarget == target) {
return;
}
turnToTargetId = getTargetId(target);
currentTarget = target;
}
/**
* Gets the {@link Mob} target to turn to.
*
* @return The target to turn to.
* @see getTurnToTargetId()
*/
public Mob getTurnToTarget() {
return currentTarget;
}
/**
* Reset the target to turn to. {@link getTurnToTargetId()} = 65535
* <b>and</b> {@link getTurnToTarget()} = null
*/
public void resetTurnToTarget() {
turnToTargetId = 65535;
currentTarget = null;
}
/**
* Gets whether there is a target to turn to.
*
* @return Whether {@link getTurnToTarget()} isn't equal to null.
*/
public boolean isTurnToTargetSet() {
return currentTarget != null;
}
/**
* Gets whether the first hit is updated.
*
* @return The result of {@code getHits().updated(1)}
* @see Hits#updated(int)
*/
public boolean isHitOneUpdated() {
return hits.updated(1);
}
/**
* Gets whether the second hit is updated.
*
* @return The result of {@code getHits().updated(2)}
* @see Hits#updated(int)
*/
public boolean isHitTwoUpdated() {
return hits.updated(2);
}
/**
* Gets the instance of our {@link Hits} on this {@link Mob}.
*
* @return The hits.
*/
public Hits getHits() {
return hits;
}
/**
* Set this {@link Mob}'s hidden status to the argument.
*
* @param hidden Whether the {@link Mob} should be hidden.
* @see isHidden()
*/
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
/**
* Gets whether this {@link Mob} is hidden.
*
* @return Whether this {@link Mob} is hidden.
*/
public boolean isHidden() {
return hidden;
}
/**
* Gets whether this {@link Mob} is frozen.
*
* @return Whether this {@link Mob} appears to be frozen.
*/
public boolean frozen() {
return frozen > 0;
}
/**
* Gets whether the {@link getTurnToTargetId()} is updated.
*
* @return Whether {@link getTurnToTargetId()} isn't -1.
*/
public boolean isTurnToTargetUpdated() {
return turnToTargetId != -1;
}
/**
* Gets the id of the target to turn to.
*
* @return The id of the target.
* @see getTurnToTarget()
*/
public int getTurnToTargetId() {
return turnToTargetId;
}
/**
* Walk the {@link Path} specified. The {@link getWalkingQueue()} will be
* {@link WalkingQueue#reset()} and contain {@link Path#getPoints()}.
*
* @param path The {@link Path} to walk.
*/
public void walkPath(Path path) {
if (!path.isEmpty()) {
walkingQueue.reset();
for (Position point : path.getPoints()) {
walkingQueue.addPoint(point);
}
}
}
/**
*
* @param object The object to check for equality.
* @return True if the {@link Mob#getTargetId()} match, else false.
*/
@Override
public boolean equals(Object object) {
if (object == null) {
return false;
}
if (!(object instanceof Mob)) {
return false;
}
return getTargetId((Mob) object) == getTargetId(this);
}
/**
*
* @return hashCode depending on getTargetId(this).
*/
@Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + getTargetId(this);
return hash;
}
/**
* Gets whether this {@link Mob} can traverse in the specified
* {@link Direction}.
*
* @param direction The {@link Direction} to check if traversable.
* @return {@code Direction.isTraversable(getPosition(), direction, getSize())}
*/
public boolean canTraverse(Direction direction) {
return Direction.isTraversable(position, direction, size);
}
/**
* Gets the current amount of hit points this {@link Mob} has.
*
* @return The current amount of hit points.
*/
public abstract int getCurrentHitpoints();
/**
* Gets the current amount of hit points this {@link Mob} has.
*
* @return The current amount of hit points.
*/
public abstract int getMaximumHitpoints();
/**
* Reduce the hit points of this {@link Mob}.
*
* @param amount number of hitpoints to reduce
*/
protected abstract void reduceHp(int amount);
/**
* Increase the hit points of this {@link Mob}.
*
* @param amount number of hitpoints to heal
*/
protected abstract void heal(int amount);
/**
* Executes what has to be done when this {@link Mob} dies.
*/
protected abstract void onDeath();
/**
* Gets whether this {@link Mob} is alive.
*
* @return {@code getCurrentHitpoints() > 0}
*/
public boolean alive() {
return getCurrentHitpoints() > 0;
}
/**
* Process the hit of a certain {@link Mob} source. This includes starting
* possible retaliation, {@link Hits#addHit(Mob, HitType, int)}, {@link
* reduceHp(int)} and possible {@link onDeath()}.
*
* @param source The source of the damage.
* @param damage The damage to process.
*/
public void processHit(Mob source, int damage) {
if (!alive()) {
System.out.println("Processing hit while dead?");
}
if (damage > getCurrentHitpoints()) {
System.out.println("Damage greater than current HP");
}
if (combatHandler.shouldRetaliate()) {
startAction(new CombatAction(this, source));
}
hits.addHit(source, damage > 0 ? HitType.NORMAL : HitType.ZERO, damage);
reduceHp(damage);
if (!alive()) {
onDeath();
}
}
public CombatBonuses getCombatBonuses() {
return combatBonuses;
}
/**
* Reset this {@link Mob}. {@link getAnimation()} = {@link
* getSpotAnimation()} = {@link getTurnToPosition()} = null. <b>and</b>
* {@link isTeleporting()} = {@link WalkingQueue#isMinimapFlagReset()} =
* false. <b>and</b> {@link getTurnToTargetId()} = -1.
*
* @see Hits#reset()
*/
public void reset() {
animation = null;
spotAnimation = null;
turnToPosition = null;
teleporting = false;
forcedChat = null;
turnToTargetId = -1;
hits.reset();
walkingQueue.setMinimapFlagReset(false);
}
/**
* Get the target id for this mob.
*
* @param mob The {@link Mob} for which the target id has to be given.
* @return The id of mob, and when it is a Player, 0x8000 is added.
*/
private static int getTargetId(Mob mob) {
int val = mob.getId();
if (mob instanceof Player) {
val += 0x8000;
}
return val;
}
/**
* Gets the {@link CombatHandler} of this {@link Mob}.
*
* @return The {@link CombatHandler}.
*/
public CombatHandler<? extends Mob> getCombatHandler() {
return combatHandler;
}
/**
* Gets whether this {@link mob} is clipped.
*
* @return whether it is clipped.
*/
public boolean isClipped() {
return clipped;
}
public Area getBounds() {
if (size == 1) {
return new PositionArea(position);
}
return new QuadArea(position.getX(), position.getY(), position.getX() + size - 1, position.getY() + size - 1);
}
/**
* Gets whether this {@link Mob} is running.
*
* @return Whether this {@link Mob} is running.
*/
public abstract boolean isRunning();
/**
* Gets the health regen per tick of this {@link Mob}. 100 = One hitpoint
* per tick
*
* @return the health regen value of this {@link Mob}
*/
public abstract int getHealthRegen();
}