103public abstract class Mob extends Entity {
105 public boolean pathfinderProjectiles =
false;
106 private int listIndex;
108 private int transformId;
109 private boolean dead;
110 public boolean regionChange;
111 public boolean positionChange;
112 public boolean forceWalking;
113 public boolean teleporting;
114 public boolean inTeleport;
115 public boolean teleportRegion;
116 public boolean blockFace;
117 public boolean blockInteract;
118 public String forceChat;
119 public Mob interactingWith;
121 public Hit secondHit;
126 private Optional<Animation> animation = Optional.empty();
127 public transient long nextAnimation;
128 private Optional<Graphic> graphic = Optional.empty();
129 public List<Mob> followers =
new LinkedList<>();
131 public final EnumSet<UpdateFlag> updateFlags = EnumSet.noneOf(
UpdateFlag.class);
137 public ActionManager action =
new ActionManager();
140 private int[] bonuses = EMPTY_BONUSES;
141 private final MutableNumber poisonDamage =
new MutableNumber();
142 private final MutableNumber venomDamage =
new MutableNumber();
153 this.lastPosition = position.copy();
157 super(position, visible);
158 this.lastPosition = position.copy();
164 public void speak(String forceChat) {
167 this.forceChat = forceChat;
171 public void animate(
int animation) {
172 animate(animation,
false);
175 public void animate(
int animation,
boolean override) {
176 animate(
new Animation(animation), override);
183 animate(animation,
false);
186 public void graphic(
int graphic) {
194 graphic(graphic,
false);
201 final long now = System.currentTimeMillis();
202 if (!override && (nextAnimation > now || updateFlags.contains(
UpdateFlag.ANIMATION))) {
206 final Optional<Animation> result = Optional.ofNullable(animation);
207 animation = result.orElse(
Animation.RESET);
210 animation == Animation.RESET
213 if (definition !=
null) {
214 nextAnimation = now + definition.durationTime + 600;
216 this.animation = result;
224 Optional<Graphic> result = Optional.ofNullable(graphic);
225 graphic = result.orElse(
Graphic.RESET);
227 if (!this.graphic.isPresent() || override ||
this.graphic.get().compareTo(graphic) > 0) {
228 this.graphic = result;
233 public static boolean pathfinderProjectiles(
Mob source) {
234 final String name = source.
getName().toLowerCase();
235 return "imp".equals(name) || name.contains(
"impling");
238 public void transform(
int transformId) {
239 transform(transformId,
false);
245 public void transform(
int transformId,
boolean reload) {
246 this.transformId = transformId;
247 this.id = transformId;
253 final Npc npc = getNpc();
254 npc.definition = definition;
255 npc.pathfinderProjectiles =
Mob.pathfinderProjectiles(npc);
256 setWidth(definition.
getSize());
257 setLength(definition.
getSize());
259 mobAnimation.setNpcAnimations(definition);
262 Combat<Npc> combat = getNpc().getCombat();
265 if (listener !=
null) {
266 combat.addListener(listener);
269 getNpc().npcAssistant.reloadSkills();
270 getNpc().setStrategy(
NpcUtility.STRATEGIES.getOrDefault(getNpc().
id, () ->
NpcAssistant.loadStrategy(getNpc()).orElse(NpcMeleeStrategy.get())).get());
287 if (cachedWaypoint !=
null && cachedWaypoint.isRunning()) {
288 cachedWaypoint.cancel();
292 public void forceMove(
int animation,
int x,
int y) {
296 public void forceMove(
int delay,
int animation,
int startSpeed,
int endSpeed,
Position offset, Direction direction) {
297 forceMove(delay, 0, animation, startSpeed, endSpeed, offset, direction);
300 public void forceMove(
int delay,
int delay2,
int animation,
int startSpeed,
int endSpeed,
Position offset, Direction direction) {
301 forceMove(delay, delay2, animation, 0, startSpeed, endSpeed, offset, direction);
307 public void forceMove(
int delay,
int delay2,
int animation,
int animationDelay,
int startSpeed,
int endSpeed,
Position offset,
Direction direction) {
319 this.interactingWith = mob;
329 if (
object ==
null ||
object.
getPosition().equals(facePosition))
331 this.facePosition =
object.getPosition();
332 this.updateFlags.add(
UpdateFlag.FACE_COORDINATE);
336 face(mob.getPosition());
345 if (!position.equals(facePosition)) {
346 this.facePosition = position;
347 this.updateFlags.add(
UpdateFlag.FACE_COORDINATE);
358 if (!position.equals(facePosition)) {
359 this.facePosition = position;
360 this.updateFlags.add(
UpdateFlag.FACE_COORDINATE);
368 if (blockFace || interactingWith ==
null)
370 interactingWith =
null;
380 if (
isPlayer() && !getPlayer().interfaceManager.isClear())
381 getPlayer().interfaceManager.close(
false);
382 setPosition(position);
383 if (
Utility.isRegionChange(position, lastPosition)) {
386 positionChange =
true;
388 teleportRegion =
true;
391 locking.lock(599, TimeUnit.MILLISECONDS,
LockType.MASTER);
395 public void walk(
Position position) {
396 walk(position,
false);
399 public void walk(
Position destination,
boolean ignoreClip) {
401 movement.
walk(destination);
407 public void runTo(
Position destination) {
411 public void walkTo(Position position) {
413 walkTo(position, () -> { });
416 public void walkTo(Position position, Runnable onDestination) {
417 Interactable interactable = Interactable.create(position);
418 walkTo(interactable, onDestination);
421 public void walkExactlyTo(Position position) {
422 walkExactlyTo(position, () -> {
426 public void walkExactlyTo(Position position, Runnable onDestination) {
427 Interactable interactable = Interactable.create(position, 0, 0);
428 walkTo(interactable, onDestination);
431 public void walkTo(Interactable target, Runnable onDestination) {
432 walkTo(target,
true, onDestination);
435 public void walkTo(Interactable target,
boolean clearAction, Runnable onDestination) {
436 Waypoint waypoint =
new WalkToWaypoint(
this, target, onDestination);
438 if (cachedWaypoint ==
null || (!cachedWaypoint.isRunning() || !waypoint.equals(cachedWaypoint))) {
444 action.clearNonWalkableActions();
447 World.schedule(cachedWaypoint = waypoint);
451 public void follow(
Mob target) {
452 Waypoint waypoint =
new FollowWaypoint(
this, target);
453 if (cachedWaypoint ==
null || (!cachedWaypoint.isRunning() || !waypoint.equals(cachedWaypoint))) {
456 action.clearNonWalkableActions();
457 World.schedule(cachedWaypoint = waypoint);
461 public void attack(
Mob target) {
462 Waypoint waypoint =
new CombatWaypoint(
this, target);
463 if (cachedWaypoint ==
null || (!cachedWaypoint.isRunning() || !waypoint.equals(cachedWaypoint))) {
466 action.clearNonWalkableActions();
467 World.schedule(cachedWaypoint = waypoint);
471 protected void setWaypoint(Waypoint waypoint) {
472 if (cachedWaypoint ==
null || (!cachedWaypoint.isRunning() || !waypoint.equals(cachedWaypoint))) {
475 action.clearNonWalkableActions();
476 World.schedule(cachedWaypoint = waypoint);
480 public void damage(Hit... hits) {
485 public void writeFakeDamage(Hit hit) {
486 if (!updateFlags.contains(UpdateFlag.FIRST_HIT)) {
488 updateFlags.add(UpdateFlag.FIRST_HIT);
491 updateFlags.add(UpdateFlag.SECOND_HIT);
495 public void writeDamage(Hit hit) {
496 if (isDead() || getCurrentHealth() < 1) {
500 if (!damageImmunity.finished()) {
506 if (!updateFlags.contains(UpdateFlag.FIRST_HIT)) {
507 firstHit = decrementHealth(hit);
508 updateFlags.add(UpdateFlag.FIRST_HIT);
510 secondHit = decrementHealth(hit);
511 updateFlags.add(UpdateFlag.SECOND_HIT);
515 public Hit decrementHealth(Hit hit) {
516 if (getCurrentHealth() - hit.getDamage() < 0)
517 hit.modifyDamage(damage -> getCurrentHealth());
518 skills.modifyLevel(level -> level - hit.getDamage(), Skill.HITPOINTS, 0, getCurrentHealth());
519 skills.refresh(Skill.HITPOINTS);
520 if (getCurrentHealth() < 1)
526 public void heal(
int amount) {
527 int health = getCurrentHealth();
528 if (health >= getMaximumHealth())
530 skills.modifyLevel(hp -> health + amount, Skill.HITPOINTS, 0, getMaximumHealth());
531 skills.refresh(Skill.HITPOINTS);
550 this.forceMovement = forceMovement;
551 if (forceMovement !=
null)
552 this.updateFlags.add(
UpdateFlag.FORCE_MOVEMENT);
555 public boolean inActivity() {
556 return activity !=
null;
559 public boolean inActivity(ActivityType type) {
560 return inActivity() && activity.getType() == type;
563 public void setActivity(Activity activity) {
564 if (this.activity !=
null) {
565 this.activity.cleanup();
567 this.activity = activity;
574 this.teleportTarget =
null;
581 return !updateFlags.isEmpty();
594 public final boolean isNpc(BooleanInterface<Npc> condition) {
595 return getType() == EntityType.NPC && condition.activated(getNpc());
605 public final Npc getNpc() {
612 public final boolean isPlayer(Function<Player, Boolean> condition) {
613 return getType() == EntityType.PLAYER && condition.apply(getPlayer());
616 public void takeStep() {
620 walkTo = walkTo.west();
621 }
else if (TraversalMap.isTraversable(
getPosition(), Direction.EAST,
width())) {
622 walkTo = walkTo.east();
623 }
else if (TraversalMap.isTraversable(
getPosition(), Direction.SOUTH,
width())) {
624 walkTo = walkTo.north();
625 }
else if (TraversalMap.isTraversable(
getPosition(), Direction.NORTH,
width())) {
626 walkTo = walkTo.south();
634 public ForceMovement getForceMovement() {
635 return forceMovement;
638 public void unpoison() {
642 if (
this instanceof Player) {
643 Player player = (Player)
this;
644 player.send(
new SendPoison(SendPoison.PoisonType.NO_POISON));
648 public void unvenom() {
651 if (
this instanceof Player) {
652 Player player = (Player)
this;
653 player.send(
new SendPoison(SendPoison.PoisonType.NO_POISON));
657 public final boolean isPoisoned() {
658 return poisonDamage.get() > 0;
661 public final boolean isVenomed() {
662 return venomDamage.get() > 0;
665 public final MutableNumber getPoisonDamage() {
669 public MutableNumber getVenomDamage() {
673 public PoisonType getPoisonType() {
677 public boolean isDead() {
681 public void setDead(
boolean dead) {
685 public final Player getPlayer() {
686 return (Player)
this;
689 public int getCurrentHealth() {
691 Npc npc = (Npc)
this;
693 case Wintertodt.PYROMANCER, Wintertodt.INCAPACITATED_PYROMANCER -> {
694 return npc.pyroHealth;
698 return skills.getLevel(Skill.HITPOINTS);
701 public int getMaximumHealth() {
703 Npc npc = (Npc)
this;
705 case Wintertodt.PYROMANCER, Wintertodt.INCAPACITATED_PYROMANCER -> {
710 return skills.getMaxLevel(Skill.HITPOINTS);
713 public int[] getBonuses() {
717 public int getBonus(
int index) {
718 return bonuses[index];
721 public void setBonuses(
int[] bonuses) {
722 this.bonuses = bonuses;
725 public void appendBonus(
int index,
int amount) {
726 if (bonuses == EMPTY_BONUSES)
727 bonuses =
new int[EMPTY_BONUSES.length];
728 bonuses[index] += amount;
731 public void setBonus(
int equipSlot,
int bonus) {
732 if (bonuses == EMPTY_BONUSES)
733 bonuses =
new int[EMPTY_BONUSES.length];
734 bonuses[equipSlot] = bonus;
737 public int getListIndex() {
741 public void setListIndex(
int listIndex) {
742 this.listIndex = listIndex;
745 public Optional<Animation> getAnimation() {
749 public Optional<Graphic> getGraphic() {
753 public void resetAnimation() {
754 this.animation = Optional.empty();
757 public void resetGraphic() {
758 this.graphic = Optional.empty();
779 public abstract <T extends Mob> CombatStrategy<? super T>
getStrategy();
786 private boolean fixingInside;
788 public boolean isFixingInside() {
792 public void setFixingInside(
boolean fixingInside) {
793 this.fixingInside = fixingInside;
796 public int getPriorityIndex() {
797 return getListIndex();
800 public boolean hasPriorityIndex(
Mob other) {
801 return getPriorityIndex() < other.getPriorityIndex();
808 public int getTransformId() {