1package com.osroyale.game.world.entity.combat;
3import com.osroyale.content.bot.PlayerBot;
4import com.osroyale.game.task.Task;
5import com.osroyale.game.world.World;
6import com.osroyale.game.world.entity.combat.attack.FightStyle;
7import com.osroyale.game.world.entity.combat.attack.FightType;
8import com.osroyale.game.world.entity.combat.attack.listener.CombatListener;
9import com.osroyale.game.world.entity.combat.formula.CombatFormula;
10import com.osroyale.game.world.entity.combat.hit.CombatData;
11import com.osroyale.game.world.entity.combat.hit.CombatHit;
12import com.osroyale.game.world.entity.combat.hit.Hit;
13import com.osroyale.game.world.entity.combat.strategy.CombatStrategy;
14import com.osroyale.game.world.entity.mob.Mob;
15import com.osroyale.game.world.entity.mob.player.Player;
16import com.osroyale.game.world.position.Position;
17import com.osroyale.game.world.region.Region;
18import com.osroyale.net.packet.out.SendEntityFeed;
19import com.osroyale.util.Stopwatch;
20import com.osroyale.util.Utility;
22import java.util.ArrayDeque;
23import java.util.Deque;
24import java.util.Iterator;
25import java.util.concurrent.TimeUnit;
27public class Combat<T
extends Mob> {
28 private final T attacker;
30 private CombatTarget target;
31 private Mob lastAggressor, lastVictim;
33 private final Stopwatch lastAttacked = Stopwatch.start();
34 public final Stopwatch lastBlocked = Stopwatch.start();
36 private FightType fightType;
38 private CombatType combatType;
39 private FightStyle fightStyle;
41 private final CombatFormula<T> formula =
new CombatFormula<>();
42 private final Deque<CombatListener<? super T>> listeners =
new ArrayDeque<>();
43 private final Deque<CombatListener<? super T>> pendingAddition =
new ArrayDeque<>();
44 private final Deque<CombatListener<? super T>> pendingRemoval =
new ArrayDeque<>();
46 private final CombatDamage damageCache =
new CombatDamage();
47 private final Deque<CombatData<T>> combatQueue =
new ArrayDeque<>();
48 private final Deque<Hit> damageQueue =
new ArrayDeque<>();
50 private final int[] hitsplatCooldowns =
new int[4];
53 public Combat(T attacker) {
54 this.attacker = attacker;
55 this.target =
new CombatTarget(attacker);
56 fightType = FightType.UNARMED_PUNCH;
59 public boolean attack(Mob defender) {
60 if (attacker.isPlayer()) {
61 Player player = attacker.getPlayer();
62 if (!player.interfaceManager.isMainClear() || !player.interfaceManager.isDialogueClear()) {
63 player.interfaceManager.close();
67 if (!canAttack(defender, attacker.getStrategy())) {
68 attacker.face(defender.getPosition());
69 attacker.movement.reset();
74 target.setTarget(defender);
75 attacker.attack(defender);
82 performChecks(target.getTarget());
84 if (!checkDistances(target.getTarget()))
91 if (target.getTarget() !=
null) {
92 attacker.interact(target.getTarget());
95 CombatStrategy<? super T> strategy = attacker.getStrategy();
96 submitStrategy(target.getTarget(), strategy);
101 while (!combatQueue.isEmpty()) {
102 CombatData<T> data = combatQueue.poll();
103 World.schedule(hitTask(data));
106 for (
int index = 0, sent = 0; index < hitsplatCooldowns.length; index++) {
107 if (hitsplatCooldowns[index] > 0) {
108 hitsplatCooldowns[index]--;
109 }
else if (sent < 2 && sendNextHitsplat()) {
110 hitsplatCooldowns[index] = 2;
115 final Mob targetMob = target.getTarget();
116 if (targetMob !=
null && attacker.isPlayer() && isAttacking(targetMob)) {
117 attacker.getPlayer().send(
118 new SendEntityFeed(targetMob));
122 private boolean checkDistances(Mob defender) {
123 return defender !=
null && Utility.withinDistance(attacker, defender, Region.VIEW_DISTANCE);
126 private boolean sendNextHitsplat() {
127 if (damageQueue.isEmpty()) {
131 if (attacker.getCurrentHealth() <= 0) {
136 Hit hit = damageQueue.poll();
137 attacker.writeDamage(hit);
141 private void updateListeners() {
142 if (!pendingAddition.isEmpty()) {
143 for (Iterator<CombatListener<? super T>> iterator = pendingAddition.iterator(); iterator.hasNext(); ) {
144 CombatListener<? super T> next = iterator.next();
151 if (!pendingRemoval.isEmpty()) {
152 for (Iterator<CombatListener<? super T>> iterator = pendingRemoval.iterator(); iterator.hasNext(); ) {
153 CombatListener<? super T> next = iterator.next();
154 removeModifier(next);
155 listeners.remove(next);
161 public void performChecks(
final Mob defender) {
162 if (defender ==
null)
return;
164 for (
final CombatListener<? super T> listener : listeners) {
165 listener.performChecks(attacker, defender);
167 final CombatStrategy<? super T> strategy = attacker.getStrategy();
168 if (strategy !=
null) {
169 strategy.performChecks(attacker, defender);
173 public void submitStrategy(Mob defender, CombatStrategy<? super T> strategy) {
174 if (!canAttack(defender, strategy)) {
178 if (!checkWithin(attacker, defender, strategy)) {
182 attacker.interact(target.getTarget());
184 formula.add(strategy);
185 init(defender, strategy);
186 cooldown(strategy.getAttackDelay(attacker, defender, fightType));
187 submitHits(defender, strategy, strategy.getHits(attacker, defender));
188 formula.remove(strategy);
190 if (attacker.isPlayer() && defender.isPlayer()) {
191 attacker.getPlayer().skulling.checkForSkulling(defender.getPlayer());
195 public void submitHits(Mob defender, CombatHit... hits) {
196 CombatStrategy<? super T> strategy = attacker.getStrategy();
197 addModifier(strategy);
198 init(defender, strategy);
199 submitHits(defender, strategy, hits);
200 removeModifier(strategy);
203 private void submitHits(Mob defender, CombatStrategy<? super T> strategy, CombatHit... hits) {
204 start(defender, strategy, hits);
205 for (
int index = 0; index < hits.length; index++) {
206 boolean last = index == hits.length - 1;
207 CombatHit hit = hits[index];
208 CombatData<T> data =
new CombatData<>(attacker, defender, hit, strategy, last);
209 attack(defender, hit, strategy);
210 combatQueue.add(data);
214 public void queueDamage(Hit hit) {
215 if (attacker.getCurrentHealth() <= 0) {
219 if (damageQueue.size() > 10) {
220 attacker.decrementHealth(hit);
224 damageQueue.add(hit);
227 public void clearDamageQueue() {
231 public void cooldown(
int delay) {
232 if (cooldown < delay)
236 public void setCooldown(
int delay) {
240 public boolean canAttack(Mob defender) {
241 if (!CombatUtil.validate(attacker, defender)) {
244 if (!CombatUtil.canBasicAttack(attacker, defender)) {
247 if (!attacker.getStrategy().canAttack(attacker, defender)) {
250 for (CombatListener<? super T> listener : listeners) {
251 if (!listener.canAttack(attacker, defender)) {
255 return defender.getCombat().canOtherAttack(attacker);
258 private boolean canAttack(Mob defender, CombatStrategy<? super T> strategy) {
259 if (!CombatUtil.validateMobs(attacker, defender)) {
262 if (!CombatUtil.canAttack(attacker, defender)) {
265 if (!strategy.canAttack(attacker, defender)) {
268 for (CombatListener<? super T> listener : listeners) {
269 if (!listener.canAttack(attacker, defender)) {
273 return defender.getCombat().canOtherAttack(attacker);
276 private boolean canOtherAttack(Mob attacker) {
277 T defender = this.attacker;
278 for (CombatListener<? super T> listener : listeners) {
279 if (!listener.canOtherAttack(attacker, defender)) {
283 return defender.getStrategy().canOtherAttack(attacker, defender);
286 private void init(Mob defender, CombatStrategy<? super T> strategy) {
287 strategy.init(attacker, defender);
288 listeners.forEach(listener -> listener.init(attacker, defender));
291 private void start(Mob defender, CombatStrategy<? super T> strategy, Hit... hits) {
292 if (!CombatUtil.validateMobs(attacker, defender)) {
293 combatQueue.removeIf(_hit -> _hit.getDefender() == defender);
294 if (defender.inTeleport)
295 defender.getCombat().damageQueue.clear();
300 defender.getCombat().lastBlocked.reset();
301 defender.getCombat().lastAggressor = attacker;
302 strategy.start(attacker, defender, hits);
303 Hit[] finalHits = hits;
304 listeners.forEach(listener -> listener.start(attacker, defender, finalHits));
307 private void attack(Mob defender, Hit hit, CombatStrategy<? super T> strategy) {
308 if (!CombatUtil.validateMobs(attacker, defender)) {
309 combatQueue.removeIf(_hit -> _hit.getDefender() == defender);
310 if (defender.inTeleport)
311 defender.getCombat().damageQueue.clear();
316 lastVictim = defender;
317 lastAttacked.reset();
318 attacker.action.reset();
320 strategy.attack(attacker, defender, hit);
321 listeners.forEach(listener -> listener.attack(attacker, defender, hit));
324 private void block(Mob attacker, Hit hit, CombatType combatType) {
325 T defender = this.attacker;
327 lastAggressor = attacker;
328 defender.action.reset();
329 listeners.forEach(listener -> listener.block(attacker, defender, hit, combatType));
330 defender.getStrategy().block(attacker, defender, hit, combatType);
331 defender.getCombat().retaliate(attacker);
334 private void hit(Mob defender, Hit hit, CombatStrategy<? super T> strategy) {
335 if (!CombatUtil.validateMobs(attacker, defender)) {
336 combatQueue.removeIf(_hit -> _hit.getDefender() == defender);
337 if (defender.inTeleport)
338 defender.getCombat().damageQueue.clear();
343 strategy.hit(attacker, defender, hit);
344 listeners.forEach(listener -> listener.hit(attacker, defender, hit));
346 if (!strategy.getCombatType().equals(CombatType.MAGIC) && hit.getDamage() > -1 && defender.id != 8060) {
347 defender.animate(CombatUtil.getBlockAnimation(defender));
350 if (defender.getCurrentHealth() - hit.getDamage() > 0) {
351 defender.getCombat().block(attacker, hit, strategy.getCombatType());
355 private void hitsplat(Mob defender, Hit hit, CombatStrategy<? super T> strategy) {
356 if (!CombatUtil.validateMobs(attacker, defender)) {
357 combatQueue.removeIf(_hit -> _hit.getDefender() == defender);
358 if (defender.inTeleport)
359 defender.getCombat().damageQueue.clear();
364 strategy.hitsplat(attacker, defender, hit);
365 listeners.forEach(listener -> listener.hitsplat(attacker, defender, hit));
367 if (!strategy.getCombatType().equals(CombatType.MAGIC) || hit.isAccurate()) {
368 if (defender.getCurrentHealth() > 0 && hit.getDamage() >= 0) {
369 defender.getCombat().queueDamage(hit);
370 defender.getCombat().damageCache.add(attacker, hit);
375 private void retaliate(Mob attacker) {
376 T defender = this.attacker;
378 if (attacker.isPlayer()) {
379 Player player = attacker.getPlayer();
381 if (!player.interfaceManager.isMainClear() || !player.interfaceManager.isDialogueClear()) {
382 player.interfaceManager.close();
385 if (defender.isPlayer() && defender.getPlayer().isBot) {
386 ((PlayerBot) defender.getPlayer()).retaliate(player);
390 if (defender.isPlayer()) {
391 Player player = defender.getPlayer();
393 if (!player.damageImmunity.finished()) {
397 if (!player.interfaceManager.isMainClear() || !player.interfaceManager.isDialogueClear()) {
398 player.interfaceManager.close();
402 if (target.getTarget() !=
null && !target.isTarget(attacker)) {
406 if (!CombatUtil.canAttack(attacker, defender)) {
410 if (defender.isAutoRetaliate() && (defender.isNpc() || defender.movement.isMovementDone())) {
415 public void preDeath(Mob attacker, Hit hit) {
416 if (attacker !=
null) {
417 T defender = this.attacker;
418 defender.getStrategy().preDeath(attacker, defender, hit);
419 listeners.forEach(listener -> listener.preDeath(attacker, defender, hit));
424 public void preKill(Mob defender, Hit hit) {
425 if (attacker !=
null) {
426 defender.getCombat().listeners.forEach(listener -> listener.preKill(attacker, defender, hit));
430 public void onDamage(Hit hit) {
431 if (attacker !=
null) {
432 listeners.forEach(listener -> listener.onDamage(attacker, hit));
436 public void onKill(Mob defender, Hit hit) {
437 if (attacker !=
null) {
438 if (attacker.isPlayer() && attacker.getPlayer().getCombatSpecial() !=
null) {
439 attacker.getPlayer().getCombatSpecial().getStrategy().onKill(attacker.getPlayer(), defender, hit);
441 listeners.forEach(listener -> listener.onKill(attacker, defender, hit));
445 public void onDeath(Mob attacker, Hit hit) {
446 if (attacker !=
null) {
447 T defender = this.attacker;
448 listeners.forEach(listener -> listener.onDeath(attacker, defender, hit));
449 defender.movement.reset();
450 defender.resetWaypoint();
455 private void finishOutgoing(Mob defender, CombatStrategy<? super T> strategy) {
456 strategy.finishOutgoing(attacker, defender);
457 listeners.forEach(listener -> listener.finishOutgoing(attacker, defender));
458 defender.getCombat().finishIncoming(attacker);
461 private void finishIncoming(Mob attacker) {
462 T defender = this.attacker;
463 defender.getStrategy().finishIncoming(attacker, defender);
464 listeners.forEach(listener -> listener.finishIncoming(attacker, defender));
467 public void reset(
boolean force) {
468 if (force || target.getTarget() !=
null) {
469 target.resetTarget();
470 attacker.resetFace();
471 attacker.movement.reset();
472 attacker.resetWaypoint();
476 public void reset() {
480 public void addModifier(FormulaModifier<? super T> modifier) {
481 formula.add(modifier);
484 public void removeModifier(FormulaModifier<? super T> modifier) {
485 formula.remove(modifier);
488 public void addFirst(FormulaModifier<? super T> modifier) {
489 formula.addFirst(modifier);
492 public void removeFirst() {
493 formula.removeFirst();
496 public void addListener(CombatListener<? super T> listener) {
497 if (listeners.contains(listener) || pendingAddition.contains(listener)) {
501 pendingAddition.add(listener);
504 public void removeListener(CombatListener<? super T> listener) {
505 if (pendingRemoval.contains(listener)) {
509 pendingRemoval.add(listener);
512 public void clearIncoming() {
516 public boolean inCombat() {
517 return isAttacking() || isUnderAttack();
520 public boolean inCombatWith(Mob mob) {
521 return isAttacking(mob) || isUnderAttackBy(mob);
524 public boolean isAttacking() {
525 return target.getTarget() !=
null && !hasPassed(lastAttacked, CombatConstants.COMBAT_TIMER_COOLDOWN);
528 public boolean isUnderAttack() {
529 return lastAggressor !=
null && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
532 public boolean isAttacking(Mob defender) {
533 return defender !=
null && defender.equals(lastVictim) && !hasPassed(lastAttacked, CombatConstants.COMBAT_TIMER_COOLDOWN);
536 public boolean isUnderAttackBy(Mob attacker) {
537 return attacker !=
null && attacker.equals(lastAggressor) && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
540 public boolean isUnderAttackByPlayer() {
541 return lastAggressor !=
null && lastAggressor.isPlayer() && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
544 public boolean isUnderAttackByNpc() {
545 return lastAggressor !=
null && lastAggressor.isNpc() && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
548 public int getCooldown() {
552 public FightType getFightType() {
556 public CombatType getCombatType() {
560 public void setFightType(FightType type) {
561 this.fightType = type;
562 this.fightStyle = type.getStyle();
565 public void setCombatType(CombatType type) {
566 this.combatType = type;
569 public FightStyle getFightStyle() {
573 public Mob getDefender() {
574 return target.getTarget();
577 public CombatDamage getDamageCache() {
581 public void compare(Mob mob,
int level, Position spawn) {
582 target.compare(mob, level, spawn);
585 public void checkAggression(Position spawn) {
586 target.checkAggression(spawn);
589 public boolean isLastAggressor(Mob mob) {
590 return mob.equals(lastAggressor);
593 public Mob getLastVictim() {
597 public Mob getLastAggressor() {
598 return lastAggressor;
601 private Task hitTask(CombatData<T> data) {
602 return new Task(data.getHitDelay()) {
604 public void execute() {
605 hit(data.getDefender(), data.getHit(), attacker.getStrategy());
606 World.schedule(hitsplatTask(data));
612 private Task hitsplatTask(CombatData<T> data) {
613 return new Task(data.getHitsplatDelay()) {
615 public void execute() {
616 hitsplat(data.getDefender(), data.getHit(), data.getStrategy());
617 if (data.isLastHit())
618 finishOutgoing(data.getDefender(), data.getStrategy());
624 private boolean hasPassed(Stopwatch timer,
int delay) {
625 return timer.elapsed(delay, TimeUnit.MILLISECONDS);
628 public boolean hasPassed(
int delay) {
629 return elapsedTime() - delay >= 0;
632 public long elapsedTime() {
633 long attacked = lastAttacked.elapsedTime();
634 long blocked = lastBlocked.elapsedTime();
635 return Math.max(attacked, blocked);
638 public void resetTimers(
int millis) {
639 lastAttacked.reset(millis, TimeUnit.MILLISECONDS);
640 lastBlocked.reset(millis, TimeUnit.MILLISECONDS);
643 public boolean checkWithin(T attacker, Mob defender, CombatStrategy<? super T> strategy) {
644 if (strategy ==
null || Utility.inside(attacker, defender)) {
647 if (!strategy.withinDistance(attacker, defender)) {
650 for (CombatListener<? super T> listener : listeners) {
651 if (!listener.withinDistance(attacker, defender))
657 public int modifyAttackLevel(Mob defender,
int level) {
658 return formula.modifyAttackLevel(attacker, defender, level);
661 public int modifyStrengthLevel(Mob defender,
int level) {
662 return formula.modifyStrengthLevel(attacker, defender, level);
665 public int modifyDefenceLevel(Mob attacker,
int level) {
666 return formula.modifyDefenceLevel(attacker, this.attacker, level);
669 public int modifyRangedLevel(Mob defender,
int level) {
670 return formula.modifyRangedLevel(attacker, defender, level);
673 public int modifyMagicLevel(Mob defender,
int level) {
674 return formula.modifyMagicLevel(attacker, defender, level);
677 public int modifyAccuracy(Mob defender,
int roll) {
678 return formula.modifyAccuracy(attacker, defender, roll);
681 public int modifyDefensive(Mob attacker,
int roll) {
682 return formula.modifyDefensive(attacker, this.attacker, roll);
685 public int modifyDamage(Mob defender,
int damage) {
686 return formula.modifyDamage(attacker, defender, damage);
689 public int modifyOffensiveBonus(Mob defender,
int bonus) {
690 return formula.modifyOffensiveBonus(attacker, defender, bonus);
693 public int modifyAggresiveBonus(Mob defender,
int bonus) {
694 return formula.modifyAggressiveBonus(attacker, defender, bonus);
697 public int modifyDefensiveBonus(Mob attacker,
int bonus) {
698 return formula.modifyDefensiveBonus(attacker, this.attacker, bonus);