RuneHive-Tarnish
Neural OSRS Enhancement Framework
Loading...
Searching...
No Matches
Combat.java
1package com.osroyale.game.world.entity.combat;
2
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;
21
22import java.util.ArrayDeque;
23import java.util.Deque;
24import java.util.Iterator;
25import java.util.concurrent.TimeUnit;
26
27public class Combat<T extends Mob> {
28 private final T attacker;
29
30 private CombatTarget target;
31 private Mob lastAggressor, lastVictim;
32
33 private final Stopwatch lastAttacked = Stopwatch.start();
34 public final Stopwatch lastBlocked = Stopwatch.start();
35
36 private FightType fightType;
37
38 private CombatType combatType;
39 private FightStyle fightStyle;
40
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<>();
45
46 private final CombatDamage damageCache = new CombatDamage();
47 private final Deque<CombatData<T>> combatQueue = new ArrayDeque<>();
48 private final Deque<Hit> damageQueue = new ArrayDeque<>();
49
50 private final int[] hitsplatCooldowns = new int[4];
51 private int cooldown;
52
53 public Combat(T attacker) {
54 this.attacker = attacker;
55 this.target = new CombatTarget(attacker);
56 fightType = FightType.UNARMED_PUNCH;
57 }
58
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();
64 }
65 }
66
67 if (!canAttack(defender, attacker.getStrategy())) {
68 attacker.face(defender.getPosition());
69 attacker.movement.reset();
70 target.resetTarget();
71 return false;
72 }
73
74 target.setTarget(defender);
75 attacker.attack(defender);
76 return true;
77 }
78
79 public void tick() {
80 updateListeners();
81
82 performChecks(target.getTarget());
83
84 if (!checkDistances(target.getTarget()))
85 reset();
86
87 if (cooldown > 0) {
88 cooldown--;
89 }
90
91 if (target.getTarget() != null) {
92 attacker.interact(target.getTarget());
93
94 if (cooldown == 0) {
95 CombatStrategy<? super T> strategy = attacker.getStrategy();
96 submitStrategy(target.getTarget(), strategy);
97 }
98 }
99
100
101 while (!combatQueue.isEmpty()) {
102 CombatData<T> data = combatQueue.poll();
103 World.schedule(hitTask(data));
104 }
105
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;
111 sent++;
112 }
113 }
114
115 final Mob targetMob = target.getTarget();
116 if (targetMob != null && attacker.isPlayer() && isAttacking(targetMob)) {
117 attacker.getPlayer().send(
118 new SendEntityFeed(targetMob));
119 }
120 }
121
122 private boolean checkDistances(Mob defender) {
123 return defender != null && Utility.withinDistance(attacker, defender, Region.VIEW_DISTANCE);
124 }
125
126 private boolean sendNextHitsplat() {
127 if (damageQueue.isEmpty()) {
128 return false;
129 }
130
131 if (attacker.getCurrentHealth() <= 0) {
132 damageQueue.clear();
133 return false;
134 }
135
136 Hit hit = damageQueue.poll();
137 attacker.writeDamage(hit);
138 return true;
139 }
140
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();
145 addModifier(next);
146 listeners.add(next);
147 iterator.remove();
148 }
149 }
150
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);
156 iterator.remove();
157 }
158 }
159 }
160
161 public void performChecks(final Mob defender) {
162 if (defender == null) return;
163
164 for (final CombatListener<? super T> listener : listeners) {
165 listener.performChecks(attacker, defender);
166 }
167 final CombatStrategy<? super T> strategy = attacker.getStrategy();
168 if (strategy != null) {
169 strategy.performChecks(attacker, defender);
170 }
171 }
172
173 public void submitStrategy(Mob defender, CombatStrategy<? super T> strategy) {
174 if (!canAttack(defender, strategy)) {
175 return;
176 }
177
178 if (!checkWithin(attacker, defender, strategy)) {
179 return;
180 }
181
182 attacker.interact(target.getTarget());
183
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);
189
190 if (attacker.isPlayer() && defender.isPlayer()) {
191 attacker.getPlayer().skulling.checkForSkulling(defender.getPlayer());
192 }
193 }
194
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);
201 }
202
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);
211 }
212 }
213
214 public void queueDamage(Hit hit) {
215 if (attacker.getCurrentHealth() <= 0) {
216 return;
217 }
218
219 if (damageQueue.size() > 10) {
220 attacker.decrementHealth(hit);
221 return;
222 }
223
224 damageQueue.add(hit);
225 }
226
227 public void clearDamageQueue() {
228 damageQueue.clear();
229 }
230
231 public void cooldown(int delay) {
232 if (cooldown < delay)
233 cooldown = delay;
234 }
235
236 public void setCooldown(int delay) {
237 cooldown = delay;
238 }
239
240 public boolean canAttack(Mob defender) {
241 if (!CombatUtil.validate(attacker, defender)) {
242 return false;
243 }
244 if (!CombatUtil.canBasicAttack(attacker, defender)) {
245 return false;
246 }
247 if (!attacker.getStrategy().canAttack(attacker, defender)) {
248 return false;
249 }
250 for (CombatListener<? super T> listener : listeners) {
251 if (!listener.canAttack(attacker, defender)) {
252 return false;
253 }
254 }
255 return defender.getCombat().canOtherAttack(attacker);
256 }
257
258 private boolean canAttack(Mob defender, CombatStrategy<? super T> strategy) {
259 if (!CombatUtil.validateMobs(attacker, defender)) {
260 return false;
261 }
262 if (!CombatUtil.canAttack(attacker, defender)) {
263 return false;
264 }
265 if (!strategy.canAttack(attacker, defender)) {
266 return false;
267 }
268 for (CombatListener<? super T> listener : listeners) {
269 if (!listener.canAttack(attacker, defender)) {
270 return false;
271 }
272 }
273 return defender.getCombat().canOtherAttack(attacker);
274 }
275
276 private boolean canOtherAttack(Mob attacker) {
277 T defender = this.attacker;
278 for (CombatListener<? super T> listener : listeners) {
279 if (!listener.canOtherAttack(attacker, defender)) {
280 return false;
281 }
282 }
283 return defender.getStrategy().canOtherAttack(attacker, defender);
284 }
285
286 private void init(Mob defender, CombatStrategy<? super T> strategy) {
287 strategy.init(attacker, defender);
288 listeners.forEach(listener -> listener.init(attacker, defender));
289 }
290
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();
296 reset();
297 return;
298 }
299
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));
305 }
306
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();
312 reset();
313 return;
314 }
315
316 lastVictim = defender;
317 lastAttacked.reset();
318 attacker.action.reset();
319
320 strategy.attack(attacker, defender, hit);
321 listeners.forEach(listener -> listener.attack(attacker, defender, hit));
322 }
323
324 private void block(Mob attacker, Hit hit, CombatType combatType) {
325 T defender = this.attacker;
326 lastBlocked.reset();
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);
332 }
333
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();
339 reset();
340 return;
341 }
342
343 strategy.hit(attacker, defender, hit);
344 listeners.forEach(listener -> listener.hit(attacker, defender, hit));
345
346 if (!strategy.getCombatType().equals(CombatType.MAGIC) && hit.getDamage() > -1 && defender.id != 8060) {
347 defender.animate(CombatUtil.getBlockAnimation(defender));
348 }
349
350 if (defender.getCurrentHealth() - hit.getDamage() > 0) {
351 defender.getCombat().block(attacker, hit, strategy.getCombatType());
352 }
353 }
354
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();
360 reset();
361 return;
362 }
363
364 strategy.hitsplat(attacker, defender, hit);
365 listeners.forEach(listener -> listener.hitsplat(attacker, defender, hit));
366
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);
371 }
372 }
373 }
374
375 private void retaliate(Mob attacker) {
376 T defender = this.attacker;
377
378 if (attacker.isPlayer()) {
379 Player player = attacker.getPlayer();
380
381 if (!player.interfaceManager.isMainClear() || !player.interfaceManager.isDialogueClear()) {
382 player.interfaceManager.close();
383 }
384
385 if (defender.isPlayer() && defender.getPlayer().isBot) {
386 ((PlayerBot) defender.getPlayer()).retaliate(player);
387 }
388 }
389
390 if (defender.isPlayer()) {
391 Player player = defender.getPlayer();
392
393 if (!player.damageImmunity.finished()) {
394 return;
395 }
396
397 if (!player.interfaceManager.isMainClear() || !player.interfaceManager.isDialogueClear()) {
398 player.interfaceManager.close();
399 }
400 }
401
402 if (target.getTarget() != null && !target.isTarget(attacker)) {
403 return;
404 }
405
406 if (!CombatUtil.canAttack(attacker, defender)) {
407 return;
408 }
409
410 if (defender.isAutoRetaliate() && (defender.isNpc() || defender.movement.isMovementDone())) {
411 attack(attacker);
412 }
413 }
414
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));
420 reset();
421 }
422 }
423
424 public void preKill(Mob defender, Hit hit) {
425 if (attacker != null) {
426 defender.getCombat().listeners.forEach(listener -> listener.preKill(attacker, defender, hit));
427 }
428 }
429
430 public void onDamage(Hit hit) {
431 if (attacker != null) {
432 listeners.forEach(listener -> listener.onDamage(attacker, hit));
433 }
434 }
435
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);
440 }
441 listeners.forEach(listener -> listener.onKill(attacker, defender, hit));
442 }
443 }
444
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();
451 reset();
452 }
453 }
454
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);
459 }
460
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));
465 }
466
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();
473 }
474 }
475
476 public void reset() {
477 reset(false);
478 }
479
480 public void addModifier(FormulaModifier<? super T> modifier) {
481 formula.add(modifier);
482 }
483
484 public void removeModifier(FormulaModifier<? super T> modifier) {
485 formula.remove(modifier);
486 }
487
488 public void addFirst(FormulaModifier<? super T> modifier) {
489 formula.addFirst(modifier);
490 }
491
492 public void removeFirst() {
493 formula.removeFirst();
494 }
495
496 public void addListener(CombatListener<? super T> listener) {
497 if (listeners.contains(listener) || pendingAddition.contains(listener)) {
498 return;
499 }
500// System.out.println("[@Combat] added listener: " + listener.getClass().getSimpleName());
501 pendingAddition.add(listener);
502 }
503
504 public void removeListener(CombatListener<? super T> listener) {
505 if (pendingRemoval.contains(listener)) {
506 return;
507 }
508// System.out.println("[@Combat] removed listener: " + listener.getClass().getSimpleName());
509 pendingRemoval.add(listener);
510 }
511
512 public void clearIncoming() {
513 combatQueue.clear();
514 }
515
516 public boolean inCombat() {
517 return isAttacking() || isUnderAttack();
518 }
519
520 public boolean inCombatWith(Mob mob) {
521 return isAttacking(mob) || isUnderAttackBy(mob);
522 }
523
524 public boolean isAttacking() {
525 return target.getTarget() != null && !hasPassed(lastAttacked, CombatConstants.COMBAT_TIMER_COOLDOWN);
526 }
527
528 public boolean isUnderAttack() {
529 return lastAggressor != null && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
530 }
531
532 public boolean isAttacking(Mob defender) {
533 return defender != null && defender.equals(lastVictim) && !hasPassed(lastAttacked, CombatConstants.COMBAT_TIMER_COOLDOWN);
534 }
535
536 public boolean isUnderAttackBy(Mob attacker) {
537 return attacker != null && attacker.equals(lastAggressor) && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
538 }
539
540 public boolean isUnderAttackByPlayer() {
541 return lastAggressor != null && lastAggressor.isPlayer() && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
542 }
543
544 public boolean isUnderAttackByNpc() {
545 return lastAggressor != null && lastAggressor.isNpc() && !hasPassed(lastBlocked, CombatConstants.COMBAT_TIMER_COOLDOWN);
546 }
547
548 public int getCooldown() {
549 return cooldown;
550 }
551
552 public FightType getFightType() {
553 return fightType;
554 }
555
556 public CombatType getCombatType() {
557 return combatType;
558 }
559
560 public void setFightType(FightType type) {
561 this.fightType = type;
562 this.fightStyle = type.getStyle();
563 }
564
565 public void setCombatType(CombatType type) {
566 this.combatType = type;
567 }
568
569 public FightStyle getFightStyle() {
570 return fightStyle;
571 }
572
573 public Mob getDefender() {
574 return target.getTarget();
575 }
576
577 public CombatDamage getDamageCache() {
578 return damageCache;
579 }
580
581 public void compare(Mob mob, int level, Position spawn) {
582 target.compare(mob, level, spawn);
583 }
584
585 public void checkAggression(Position spawn) {
586 target.checkAggression(spawn);
587 }
588
589 public boolean isLastAggressor(Mob mob) {
590 return mob.equals(lastAggressor);
591 }
592
593 public Mob getLastVictim() {
594 return lastVictim;
595 }
596
597 public Mob getLastAggressor() {
598 return lastAggressor;
599 }
600
601 private Task hitTask(CombatData<T> data) {
602 return new Task(data.getHitDelay()) {
603 @Override
604 public void execute() {
605 hit(data.getDefender(), data.getHit(), attacker.getStrategy()/*data.getStrategy()*/);
606 World.schedule(hitsplatTask(data));
607 cancel();
608 }
609 };
610 }
611
612 private Task hitsplatTask(CombatData<T> data) {
613 return new Task(data.getHitsplatDelay()) {
614 @Override
615 public void execute() {
616 hitsplat(data.getDefender(), data.getHit(), data.getStrategy());
617 if (data.isLastHit())
618 finishOutgoing(data.getDefender(), data.getStrategy());
619 cancel();
620 }
621 };
622 }
623
624 private boolean hasPassed(Stopwatch timer, int delay) {
625 return timer.elapsed(delay, TimeUnit.MILLISECONDS);
626 }
627
628 public boolean hasPassed(int delay) {
629 return elapsedTime() - delay >= 0;
630 }
631
632 public long elapsedTime() {
633 long attacked = lastAttacked.elapsedTime();
634 long blocked = lastBlocked.elapsedTime();
635 return Math.max(attacked, blocked);
636 }
637
638 public void resetTimers(int millis) {
639 lastAttacked.reset(millis, TimeUnit.MILLISECONDS);
640 lastBlocked.reset(millis, TimeUnit.MILLISECONDS);
641 }
642
643 public boolean checkWithin(T attacker, Mob defender, CombatStrategy<? super T> strategy) {
644 if (strategy == null || Utility.inside(attacker, defender)) {
645 return false;
646 }
647 if (!strategy.withinDistance(attacker, defender)) {
648 return false;
649 }
650 for (CombatListener<? super T> listener : listeners) {
651 if (!listener.withinDistance(attacker, defender))
652 return false;
653 }
654 return true;
655 }
656
657 public int modifyAttackLevel(Mob defender, int level) {
658 return formula.modifyAttackLevel(attacker, defender, level);
659 }
660
661 public int modifyStrengthLevel(Mob defender, int level) {
662 return formula.modifyStrengthLevel(attacker, defender, level);
663 }
664
665 public int modifyDefenceLevel(Mob attacker, int level) {
666 return formula.modifyDefenceLevel(attacker, this.attacker, level);
667 }
668
669 public int modifyRangedLevel(Mob defender, int level) {
670 return formula.modifyRangedLevel(attacker, defender, level);
671 }
672
673 public int modifyMagicLevel(Mob defender, int level) {
674 return formula.modifyMagicLevel(attacker, defender, level);
675 }
676
677 public int modifyAccuracy(Mob defender, int roll) {
678 return formula.modifyAccuracy(attacker, defender, roll);
679 }
680
681 public int modifyDefensive(Mob attacker, int roll) {
682 return formula.modifyDefensive(attacker, this.attacker, roll);
683 }
684
685 public int modifyDamage(Mob defender, int damage) {
686 return formula.modifyDamage(attacker, defender, damage);
687 }
688
689 public int modifyOffensiveBonus(Mob defender, int bonus) {
690 return formula.modifyOffensiveBonus(attacker, defender, bonus);
691 }
692
693 public int modifyAggresiveBonus(Mob defender, int bonus) {
694 return formula.modifyAggressiveBonus(attacker, defender, bonus);
695 }
696
697 public int modifyDefensiveBonus(Mob attacker, int bonus) {
698 return formula.modifyDefensiveBonus(attacker, this.attacker, bonus);
699 }
700
701}