Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40799f96f6 | ||
|
|
5262a602da | ||
|
|
5b80614376 | ||
|
|
b60f2754f1 | ||
|
|
89cdf5655d | ||
|
|
c9208000bb | ||
|
|
531a2d028b | ||
|
|
ca33c5a14c | ||
|
|
5868530414 | ||
|
|
66a731005e | ||
|
|
330db50985 | ||
|
|
12c08c940a | ||
|
|
b21ccdbb4c | ||
|
|
a708a1f0b2 | ||
|
|
b525a7a3ab | ||
|
|
b690195233 | ||
|
|
9aa44d4e9a | ||
|
|
ab1c3f12d3 | ||
|
|
6017641896 |
No files matched your search
@@ -468,7 +468,12 @@ public enum InterestManager implements Runnable {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
awonpc.playerAgroMap.put(player.getObjectUUID(), 0f);
|
awonpc.playerAgroMap.put(player.getObjectUUID(), 0f);
|
||||||
|
|
||||||
|
// siege engines keep firing at their target when a new player loads them
|
||||||
|
|
||||||
|
if (!awonpc.isSiege())
|
||||||
((Mob) awonpc).setCombatTarget(null);
|
((Mob) awonpc).setCombatTarget(null);
|
||||||
|
|
||||||
lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
|
lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
|
||||||
|
|
||||||
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) {
|
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import engine.jobs.AttackJob;
|
|||||||
import engine.jobs.DeferredPowerJob;
|
import engine.jobs.DeferredPowerJob;
|
||||||
import engine.mbEnums;
|
import engine.mbEnums;
|
||||||
import engine.net.client.ClientConnection;
|
import engine.net.client.ClientConnection;
|
||||||
|
import engine.net.client.msg.PowerProjectileMsg;
|
||||||
import engine.net.client.msg.TargetedActionMsg;
|
import engine.net.client.msg.TargetedActionMsg;
|
||||||
import engine.net.client.msg.UpdateStateMsg;
|
import engine.net.client.msg.UpdateStateMsg;
|
||||||
import engine.objects.*;
|
import engine.objects.*;
|
||||||
@@ -146,11 +147,19 @@ public enum CombatManager {
|
|||||||
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
|
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
|
||||||
|
|
||||||
attackRange += weapon.template.item_weapon_max_range * rangeMod;
|
attackRange += weapon.template.item_weapon_max_range * rangeMod;
|
||||||
|
} else if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob)) {
|
||||||
|
|
||||||
|
//unarmed mob, add the natural attack range from its rune (0 for most melee mobs)
|
||||||
|
|
||||||
|
if (bonus != null)
|
||||||
|
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
|
||||||
|
|
||||||
|
attackRange += getNaturalAttackRange(attacker, slot) * rangeMod;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
|
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
|
||||||
if (((Mob) attacker).isSiege())
|
if (((Mob) attacker).isSiege())
|
||||||
attackRange = 300;
|
attackRange = getSiegeRange((Mob) attacker);
|
||||||
|
|
||||||
float distanceSquared = attacker.loc.distanceSquared(target.loc);
|
float distanceSquared = attacker.loc.distanceSquared(target.loc);
|
||||||
|
|
||||||
@@ -214,6 +223,22 @@ public enum CombatManager {
|
|||||||
wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects.
|
wepSpeed = 10; //Old was 10, but it can be reached lower with legit buffs,effects.
|
||||||
|
|
||||||
delay = (long)wepSpeed * 100L;
|
delay = (long)wepSpeed * 100L;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//unarmed, use the natural attack speed from the mob or race rune
|
||||||
|
|
||||||
|
float naturalSpeed = getNaturalAttackSpeed(attacker, slot);
|
||||||
|
|
||||||
|
if (naturalSpeed > 0) {
|
||||||
|
|
||||||
|
if (attacker.getBonuses() != null && attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.SourceType.None) != 0f) //add effects speed bonus
|
||||||
|
naturalSpeed *= (1 + attacker.getBonuses().getFloatPercentAll(mbEnums.ModType.AttackDelay, mbEnums.SourceType.None));
|
||||||
|
|
||||||
|
if (naturalSpeed < 10)
|
||||||
|
naturalSpeed = 10;
|
||||||
|
|
||||||
|
delay = (long) naturalSpeed * 100L;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
|
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob))
|
||||||
@@ -232,7 +257,8 @@ public enum CombatManager {
|
|||||||
|
|
||||||
|
|
||||||
//handle retaliate
|
//handle retaliate
|
||||||
if (AbstractCharacter.IsAbstractCharacter(target)) {
|
//siege engines only retaliate on damage, see AbstractCharacter.modifyHealth
|
||||||
|
if (AbstractCharacter.IsAbstractCharacter(target) && !(target.getObjectType().equals(mbEnums.GameObjectType.Mob) && ((Mob) target).isSiege())) {
|
||||||
if (((AbstractCharacter) target).combatTarget == null || !((AbstractCharacter) target).combatTarget.isAlive()) {
|
if (((AbstractCharacter) target).combatTarget == null || !((AbstractCharacter) target).combatTarget.isAlive()) {
|
||||||
((AbstractCharacter) target).combatTarget = attacker;
|
((AbstractCharacter) target).combatTarget = attacker;
|
||||||
if (target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && ((AbstractCharacter) target).isCombat())
|
if (target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && ((AbstractCharacter) target).isCombat())
|
||||||
@@ -319,7 +345,7 @@ public enum CombatManager {
|
|||||||
DispatchManager.sendToAllInRange(attacker, msg);
|
DispatchManager.sendToAllInRange(attacker, msg);
|
||||||
|
|
||||||
//we need to send the animation even if the attacker misses
|
//we need to send the animation even if the attacker misses
|
||||||
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
|
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot));
|
||||||
DispatchManager.sendToAllInRange(target, cmm);
|
DispatchManager.sendToAllInRange(target, cmm);
|
||||||
|
|
||||||
//set auto attack job
|
//set auto attack job
|
||||||
@@ -359,7 +385,7 @@ public enum CombatManager {
|
|||||||
DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
|
DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
|
||||||
|
|
||||||
//we need to send the animation even if the attacker misses
|
//we need to send the animation even if the attacker misses
|
||||||
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
|
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot));
|
||||||
DispatchManager.sendToAllInRange(target, cmm);
|
DispatchManager.sendToAllInRange(target, cmm);
|
||||||
//set auto attack job
|
//set auto attack job
|
||||||
setAutoAttackJob(attacker, slot, delay);
|
setAutoAttackJob(attacker, slot, delay);
|
||||||
@@ -447,7 +473,7 @@ public enum CombatManager {
|
|||||||
if (resists.immuneTo(damageType)) {
|
if (resists.immuneTo(damageType)) {
|
||||||
//set auto attack job
|
//set auto attack job
|
||||||
//we need to send the animation even if the attacker misses
|
//we need to send the animation even if the attacker misses
|
||||||
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon.template, null, slot));
|
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) 0, getSwingAnimation(weapon == null ? null : weapon.template, null, slot));
|
||||||
DispatchManager.sendToAllInRange(target, cmm);
|
DispatchManager.sendToAllInRange(target, cmm);
|
||||||
setAutoAttackJob(attacker, slot, delay);
|
setAutoAttackJob(attacker, slot, delay);
|
||||||
return;
|
return;
|
||||||
@@ -479,6 +505,15 @@ public enum CombatManager {
|
|||||||
}
|
}
|
||||||
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) damage, attackAnim);
|
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) damage, attackAnim);
|
||||||
DispatchManager.sendToAllInRange(target, cmm);
|
DispatchManager.sendToAllInRange(target, cmm);
|
||||||
|
|
||||||
|
//siege projectile is sent once per hit, not per AI pulse
|
||||||
|
|
||||||
|
if (attacker.getObjectType().equals(mbEnums.GameObjectType.Mob) && ((Mob) attacker).isSiege()
|
||||||
|
&& target.getObjectType().equals(mbEnums.GameObjectType.Building)) {
|
||||||
|
PowerProjectileMsg ppm = new PowerProjectileMsg(attacker, target);
|
||||||
|
ppm.setRange(50);
|
||||||
|
DispatchManager.dispatchMsgToInterestArea(attacker, ppm, mbEnums.DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,6 +671,76 @@ public enum CombatManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static float getNaturalAttackSpeed(AbstractCharacter attacker, mbEnums.EquipSlotType slot) {
|
||||||
|
|
||||||
|
// Returns the rune natural attack speed (x100 ms) for this hand, or 0 if the rune has none.
|
||||||
|
|
||||||
|
float[] attack = getNaturalAttack(attacker, slot);
|
||||||
|
|
||||||
|
if (attack == null || attack.length < 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return attack[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getNaturalAttackRange(AbstractCharacter attacker, mbEnums.EquipSlotType slot) {
|
||||||
|
|
||||||
|
// Returns the rune natural attack range for this hand, or 0 if the rune has none.
|
||||||
|
|
||||||
|
float[] attack = getNaturalAttack(attacker, slot);
|
||||||
|
|
||||||
|
if (attack == null || attack.length < 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return attack[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float getSiegeRange(Mob siegeEngine) {
|
||||||
|
|
||||||
|
// Siege engines use the range from their rune natural attack (350 for all three), 300 if the rune has none.
|
||||||
|
|
||||||
|
float range = getNaturalAttackRange(siegeEngine, mbEnums.EquipSlotType.RHELD);
|
||||||
|
|
||||||
|
if (range <= 0)
|
||||||
|
return 300;
|
||||||
|
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float[] getNaturalAttack(AbstractCharacter attacker, mbEnums.EquipSlotType slot) {
|
||||||
|
|
||||||
|
// Returns the rune natural attack entry for this hand, or null if the rune has none.
|
||||||
|
// Mobs use their mobbase rune, players use their race rune.
|
||||||
|
|
||||||
|
int runeID;
|
||||||
|
ItemTemplate rune;
|
||||||
|
int hand = slot.equals(mbEnums.EquipSlotType.LHELD) ? 2 : 1;
|
||||||
|
|
||||||
|
switch (attacker.getObjectType()) {
|
||||||
|
case Mob:
|
||||||
|
runeID = ((Mob) attacker).loadID;
|
||||||
|
break;
|
||||||
|
case PlayerCharacter:
|
||||||
|
runeID = ((PlayerCharacter) attacker).getRaceID();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
rune = ItemTemplate.templates.get(runeID);
|
||||||
|
|
||||||
|
if (rune == null || rune.rune_natural_attacks.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (float[] attack : rune.rune_natural_attacks)
|
||||||
|
if (attack.length > 3 && (int) attack[3] == hand)
|
||||||
|
return attack;
|
||||||
|
|
||||||
|
// No entry for this hand, fall back to the first natural attack
|
||||||
|
|
||||||
|
return rune.rune_natural_attacks.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
public static int calculatePetDamage(AbstractCharacter agent) {
|
public static int calculatePetDamage(AbstractCharacter agent) {
|
||||||
//damage calc for pet
|
//damage calc for pet
|
||||||
float range;
|
float range;
|
||||||
|
|||||||
@@ -14,12 +14,10 @@ import engine.gameManager.*;
|
|||||||
import engine.math.Vector3f;
|
import engine.math.Vector3f;
|
||||||
import engine.math.Vector3fImmutable;
|
import engine.math.Vector3fImmutable;
|
||||||
import engine.mbEnums;
|
import engine.mbEnums;
|
||||||
import engine.mbEnums.DispatchChannel;
|
|
||||||
import engine.mobileAI.Threads.MobAIThread;
|
import engine.mobileAI.Threads.MobAIThread;
|
||||||
import engine.mobileAI.Threads.ReSpawner;
|
import engine.mobileAI.Threads.ReSpawner;
|
||||||
import engine.mobileAI.utilities.MovementUtilities;
|
import engine.mobileAI.utilities.MovementUtilities;
|
||||||
import engine.net.client.msg.PerformActionMsg;
|
import engine.net.client.msg.PerformActionMsg;
|
||||||
import engine.net.client.msg.PowerProjectileMsg;
|
|
||||||
import engine.objects.*;
|
import engine.objects.*;
|
||||||
import engine.powers.ActionsBase;
|
import engine.powers.ActionsBase;
|
||||||
import engine.powers.PowersBase;
|
import engine.powers.PowersBase;
|
||||||
@@ -46,6 +44,11 @@ public class MobAI {
|
|||||||
//
|
//
|
||||||
// CombatManager.class implements shared combat routines for all avatars.
|
// CombatManager.class implements shared combat routines for all avatars.
|
||||||
|
|
||||||
|
// Pet aggro scan in checkForAggro stays off until it checks rune enemy lists,
|
||||||
|
// filters its targets and keeps pet targets (MBDATA/docs/plan-mask-pet.md steps 2-4).
|
||||||
|
|
||||||
|
private static final boolean PET_AGGRO_ENABLED = false;
|
||||||
|
|
||||||
private static void attackTarget(Mob mob, AbstractWorldObject target) {
|
private static void attackTarget(Mob mob, AbstractWorldObject target) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -93,7 +96,7 @@ public class MobAI {
|
|||||||
mob.updateLocation();
|
mob.updateLocation();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +115,9 @@ public class MobAI {
|
|||||||
if (mob.behaviourType.callsForHelp)
|
if (mob.behaviourType.callsForHelp)
|
||||||
mobCallForHelp(mob);
|
mobCallForHelp(mob);
|
||||||
|
|
||||||
if (!MovementUtilities.inRangeDropAggro(mob, target)) {
|
// siege engines drop targets on their own attack range in determineAction
|
||||||
|
|
||||||
|
if (!mob.isSiege() && !MovementUtilities.inRangeDropAggro(mob, target)) {
|
||||||
mob.setCombatTarget(null);
|
mob.setCombatTarget(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -133,7 +138,7 @@ public class MobAI {
|
|||||||
target.getPet().setCombatTarget(mob);
|
target.getPet().setCombatTarget(mob);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackPlayer" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackPlayer");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -155,20 +160,10 @@ public class MobAI {
|
|||||||
if (guard.getCombatTarget() == null && !guard.getGuild().equals(mob.getGuild()))
|
if (guard.getCombatTarget() == null && !guard.getGuild().equals(mob.getGuild()))
|
||||||
guard.setCombatTarget(mob);
|
guard.setCombatTarget(mob);
|
||||||
|
|
||||||
if (mob.isSiege())
|
|
||||||
MovementManager.sendRWSSMsg(mob);
|
|
||||||
|
|
||||||
|
|
||||||
CombatManager.combatCycle(mob, target);
|
CombatManager.combatCycle(mob, target);
|
||||||
|
|
||||||
if (mob.isSiege()) {
|
|
||||||
PowerProjectileMsg ppm = new PowerProjectileMsg(mob, target);
|
|
||||||
ppm.setRange(50);
|
|
||||||
DispatchManager.dispatchMsgToInterestArea(mob, ppm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackBuilding" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackBuilding");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +181,7 @@ public class MobAI {
|
|||||||
CombatManager.combatCycle(mob, target);
|
CombatManager.combatCycle(mob, target);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackMob" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackMob");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +243,7 @@ public class MobAI {
|
|||||||
MovementUtilities.aiMove(mob, mob.destination, true);
|
MovementUtilities.aiMove(mob, mob.destination, true);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: AttackTarget" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: Patrol");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,7 +285,7 @@ public class MobAI {
|
|||||||
return mob.nextCastTime <= System.currentTimeMillis();
|
return mob.nextCastTime <= System.currentTimeMillis();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: canCast" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: canCast");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -387,7 +382,7 @@ public class MobAI {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCast");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -452,7 +447,7 @@ public class MobAI {
|
|||||||
mob.nextCallForHelp = System.currentTimeMillis() + 60000;
|
mob.nextCallForHelp = System.currentTimeMillis() + 60000;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCallForHelp" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: MobCallForHelp");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +513,16 @@ public class MobAI {
|
|||||||
boolean bypassLoadedPlayerCheck = false;
|
boolean bypassLoadedPlayerCheck = false;
|
||||||
if (mob.isPlayerGuard() || mob.isSiege()) {
|
if (mob.isPlayerGuard() || mob.isSiege()) {
|
||||||
bypassLoadedPlayerCheck = true;
|
bypassLoadedPlayerCheck = true;
|
||||||
if (mob.combatTarget != null && mob.combatTarget.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
|
|
||||||
|
if (mob.isSiege()) {
|
||||||
|
|
||||||
|
// siege engines hold a character target until it dies or leaves their attack range
|
||||||
|
|
||||||
|
if (mob.combatTarget != null && AbstractCharacter.IsAbstractCharacter(mob.combatTarget))
|
||||||
|
if (mob.combatTarget.loc.distanceSquared(mob.loc) > mob.getRange() * mob.getRange())
|
||||||
|
mob.setCombatTarget(null);
|
||||||
|
|
||||||
|
} else if (mob.combatTarget != null && mob.combatTarget.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter))
|
||||||
if (mob.combatTarget.loc.distanceSquared(mob.loc) > 10000)
|
if (mob.combatTarget.loc.distanceSquared(mob.loc) > 10000)
|
||||||
mob.setCombatTarget(null);
|
mob.setCombatTarget(null);
|
||||||
}
|
}
|
||||||
@@ -544,7 +548,7 @@ public class MobAI {
|
|||||||
|
|
||||||
PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget();
|
PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget();
|
||||||
|
|
||||||
if (!mob.playerAgroMap.containsKey(target.getObjectUUID())) {
|
if (!mob.isSiege() && !mob.playerAgroMap.containsKey(target.getObjectUUID())) {
|
||||||
mob.setCombatTarget(null);
|
mob.setCombatTarget(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -575,7 +579,7 @@ public class MobAI {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DetermineAction" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: DetermineAction");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,7 +634,7 @@ public class MobAI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aiAgent.getCombatTarget() == null) {
|
if (PET_AGGRO_ENABLED && aiAgent.getCombatTarget() == null) {
|
||||||
|
|
||||||
//look for pets to aggro if no players found to aggro
|
//look for pets to aggro if no players found to aggro
|
||||||
|
|
||||||
@@ -649,7 +653,7 @@ public class MobAI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForAggro" + " " + e.getMessage());
|
Logger.error(e, aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForAggro");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,7 +706,7 @@ public class MobAI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckMobMovement" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckMobMovement");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,7 +750,7 @@ public class MobAI {
|
|||||||
ReSpawner.respawnQueue.put(aiAgent);
|
ReSpawner.respawnQueue.put(aiAgent);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn" + " " + e.getMessage());
|
Logger.error(e, aiAgent.getObjectUUID() + " " + aiAgent.getName() + " Failed At: CheckForRespawn");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,7 +765,7 @@ public class MobAI {
|
|||||||
if (mob.getCombatTarget() == null)
|
if (mob.getCombatTarget() == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (mob.getCombatTarget().getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && !MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && !mob.agentType.equals(mbEnums.AIAgentType.PET)) {
|
if (mob.getCombatTarget().getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && !MovementUtilities.inRangeDropAggro(mob, (PlayerCharacter) mob.getCombatTarget()) && !mob.agentType.equals(mbEnums.AIAgentType.PET) && !mob.isSiege()) {
|
||||||
|
|
||||||
mob.setCombatTarget(null);
|
mob.setCombatTarget(null);
|
||||||
return;
|
return;
|
||||||
@@ -769,7 +773,7 @@ public class MobAI {
|
|||||||
attackTarget(mob, mob.getCombatTarget());
|
attackTarget(mob, mob.getCombatTarget());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForAttack" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForAttack");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,7 +816,7 @@ public class MobAI {
|
|||||||
mob.playerAgroMap.replaceAll((e, v) -> 0f);
|
mob.playerAgroMap.replaceAll((e, v) -> 0f);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckToSendMobHome" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckToSendMobHome");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -851,7 +855,7 @@ public class MobAI {
|
|||||||
mob.updateMovementState();
|
mob.updateMovementState();
|
||||||
mob.updateLocation();
|
mob.updateLocation();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: chaseTarget" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: chaseTarget");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -877,13 +881,17 @@ public class MobAI {
|
|||||||
if (aggroMob.agentType.equals(mbEnums.AIAgentType.PET))
|
if (aggroMob.agentType.equals(mbEnums.AIAgentType.PET))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
//don't attack siege engines
|
||||||
|
if (aggroMob.isSiege())
|
||||||
|
continue;
|
||||||
|
|
||||||
if (mob.getLoc().distanceSquared2D(aggroMob.getLoc()) > sqr(50))
|
if (mob.getLoc().distanceSquared2D(aggroMob.getLoc()) > sqr(50))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
mob.setCombatTarget(aggroMob);
|
mob.setCombatTarget(aggroMob);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: SafeGuardAggro" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: SafeGuardAggro");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,7 +921,7 @@ public class MobAI {
|
|||||||
checkForAttack(mob); //only check to attack if combat target is not null
|
checkForAttack(mob); //only check to attack if combat target is not null
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardLogic" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardLogic");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,7 +954,7 @@ public class MobAI {
|
|||||||
mob.setHealth(mob.getHealthMax());
|
mob.setHealth(mob.getHealthMax());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: PetLogic" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: PetLogic");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,7 +978,7 @@ public class MobAI {
|
|||||||
}
|
}
|
||||||
checkForAttack(mob);
|
checkForAttack(mob);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: HamletGuardLogic" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: HamletGuardLogic");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,7 +1051,7 @@ public class MobAI {
|
|||||||
checkForAttack(mob);
|
checkForAttack(mob);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: DefaultLogic" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: DefaultLogic");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1099,25 +1107,11 @@ public class MobAI {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mob.getCombatTarget() == null) {
|
|
||||||
|
|
||||||
//look for siege equipment to aggro if no players found to aggro
|
// guards never auto-aggro siege engines; they only respond when one damages a city building
|
||||||
|
|
||||||
HashSet<AbstractWorldObject> awoList = WorldGrid.getObjectsInRangePartial(mob, MobAIThread.AI_BASE_AGGRO_RANGE, MBServerStatics.MASK_SIEGE);
|
|
||||||
|
|
||||||
for (AbstractWorldObject awoMob : awoList) {
|
|
||||||
|
|
||||||
|
|
||||||
Mob aggroMob = (Mob) awoMob;
|
|
||||||
if (guardCanAggro(mob, aggroMob)) {
|
|
||||||
mob.setCombatTarget(aggroMob);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro" + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1180,7 +1174,7 @@ public class MobAI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1232,7 +1226,7 @@ public class MobAI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints" + " " + e);
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,7 +1261,7 @@ public class MobAI {
|
|||||||
}
|
}
|
||||||
return mostHatedTarget;
|
return mostHatedTarget;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: ChangeTargetFromMostHated" + " " + e.getMessage());
|
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: ChangeTargetFromMostHated");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -656,12 +656,30 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
|||||||
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD));
|
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD));
|
||||||
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
|
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
|
||||||
Mob mob = (Mob) abstractCharacter;
|
Mob mob = (Mob) abstractCharacter;
|
||||||
abstractCharacter.minDamageHandOne = (int) mob.mobBase.getDamageMin();
|
ItemTemplate rune = ItemTemplate.templates.get(mob.loadID);
|
||||||
abstractCharacter.minDamageHandTwo = (int) mob.mobBase.getDamageMin();
|
float minDamage = mob.mobBase.getDamageMin();
|
||||||
abstractCharacter.maxDamageHandOne = (int) mob.mobBase.getDamageMax();
|
float maxDamage = mob.mobBase.getDamageMax();
|
||||||
abstractCharacter.maxDamageHandTwo = (int) mob.mobBase.getDamageMax();
|
int attackRating = mob.mobBase.getAttackRating();
|
||||||
abstractCharacter.atrHandOne = mob.mobBase.getAttackRating();
|
|
||||||
abstractCharacter.atrHandTwo = mob.mobBase.getAttackRating();
|
// The client (ArcMobile, 0x46D200) takes mob damage straight from the rune MINDAMAGE/MAXDAMAGE,
|
||||||
|
// with no stat scaling. Use the rune when it has damage, otherwise keep the mobbase value.
|
||||||
|
|
||||||
|
if (rune != null && rune.rune_max_damage > 0) {
|
||||||
|
minDamage = rune.rune_min_damage;
|
||||||
|
maxDamage = rune.rune_max_damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Likewise ATR comes straight from the rune ATTACK for mobiles (0x466570).
|
||||||
|
|
||||||
|
if (rune != null && rune.rune_attack > 0)
|
||||||
|
attackRating = rune.rune_attack;
|
||||||
|
|
||||||
|
abstractCharacter.minDamageHandOne = (int) minDamage;
|
||||||
|
abstractCharacter.minDamageHandTwo = (int) minDamage;
|
||||||
|
abstractCharacter.maxDamageHandOne = (int) maxDamage;
|
||||||
|
abstractCharacter.maxDamageHandTwo = (int) maxDamage;
|
||||||
|
abstractCharacter.atrHandOne = attackRating;
|
||||||
|
abstractCharacter.atrHandTwo = attackRating;
|
||||||
abstractCharacter.defenseRating = mob.mobBase.getDefenseRating();
|
abstractCharacter.defenseRating = mob.mobBase.getDefenseRating();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -717,9 +735,18 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
|||||||
mastDam = CharacterSkill.getQuickMastery(abstractCharacter, "Unarmed Combat Mastery");
|
mastDam = CharacterSkill.getQuickMastery(abstractCharacter, "Unarmed Combat Mastery");
|
||||||
else
|
else
|
||||||
mastDam = masteryPercentage;
|
mastDam = masteryPercentage;
|
||||||
// TODO Correct these
|
// Base fist damage from the race rune natural attack (1-4 for all races), scaled below like a weapon.
|
||||||
|
// Matches the client's unarmed damage calc (0x46D200).
|
||||||
|
|
||||||
|
float[] naturalAttack = CombatManager.getNaturalAttack(abstractCharacter, mainHand ? EquipSlotType.RHELD : EquipSlotType.LHELD);
|
||||||
|
|
||||||
|
if (naturalAttack != null && naturalAttack.length > 9) {
|
||||||
|
min = (int) naturalAttack[8];
|
||||||
|
max = (int) naturalAttack[9];
|
||||||
|
} else {
|
||||||
min = 1;
|
min = 1;
|
||||||
max = 3;
|
max = 3;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (mainHand)
|
if (mainHand)
|
||||||
@@ -1476,13 +1503,22 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
|||||||
Mob mob = (Mob) this;
|
Mob mob = (Mob) this;
|
||||||
|
|
||||||
if (mob.isSiege()) {
|
if (mob.isSiege()) {
|
||||||
return 300;
|
return CombatManager.getSiegeRange(mob);
|
||||||
}
|
}
|
||||||
float range = 8;
|
float range = 8;
|
||||||
if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) {
|
if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) {
|
||||||
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).template.item_weapon_max_range;
|
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).template.item_weapon_max_range;
|
||||||
} else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) {
|
} else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) {
|
||||||
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD).template.item_weapon_max_range;
|
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD).template.item_weapon_max_range;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Unarmed: use the rune natural attack range when it has one (e.g. Gasser 150), not clamped.
|
||||||
|
// Most melee runes have 0, so they keep the default of 8.
|
||||||
|
|
||||||
|
float naturalRange = CombatManager.getNaturalAttackRange(mob, EquipSlotType.RHELD);
|
||||||
|
|
||||||
|
if (naturalRange > 0)
|
||||||
|
return naturalRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Is this clamp from live?
|
// TODO Is this clamp from live?
|
||||||
@@ -1827,6 +1863,15 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
|||||||
if (this.getObjectType().equals(GameObjectType.Mob)) {
|
if (this.getObjectType().equals(GameObjectType.Mob)) {
|
||||||
//handle hate value addition
|
//handle hate value addition
|
||||||
Mob target = (Mob) this;
|
Mob target = (Mob) this;
|
||||||
|
|
||||||
|
// Siege engines switch to whoever damages them, players and pets alike.
|
||||||
|
// Misses and non-damaging powers do not pull them.
|
||||||
|
|
||||||
|
if (target.isSiege() && value < 0f && attacker != null && !attacker.equals(target)) {
|
||||||
|
target.setCombatTarget(attacker);
|
||||||
|
flagSiegeAttackerHostile(target, attacker);
|
||||||
|
}
|
||||||
|
|
||||||
if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) {
|
if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) {
|
||||||
|
|
||||||
if (target.playerAgroMap.containsKey(attacker.getObjectUUID()))
|
if (target.playerAgroMap.containsKey(attacker.getObjectUUID()))
|
||||||
@@ -1860,6 +1905,41 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void flagSiegeAttackerHostile(Mob siegeEngine, AbstractCharacter attacker) {
|
||||||
|
|
||||||
|
// Damaging a siege engine on a city grid flags the player hostile to that
|
||||||
|
// city, unless they belong to the city's nation or an allied nation.
|
||||||
|
// Pets flag their owner.
|
||||||
|
|
||||||
|
City city;
|
||||||
|
AbstractCharacter flagged;
|
||||||
|
Guild cityNation;
|
||||||
|
|
||||||
|
city = ZoneManager.getCityAtLocation(siegeEngine.loc);
|
||||||
|
|
||||||
|
if (city == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
flagged = attacker;
|
||||||
|
|
||||||
|
if (attacker.getObjectType().equals(GameObjectType.Mob))
|
||||||
|
flagged = ((Mob) attacker).guardCaptain;
|
||||||
|
|
||||||
|
if (flagged == null || !flagged.getObjectType().equals(GameObjectType.PlayerCharacter))
|
||||||
|
return;
|
||||||
|
|
||||||
|
cityNation = city.getGuild().getNation();
|
||||||
|
|
||||||
|
if (flagged.getGuild().getNation().equals(cityNation))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (cityNation.getAllyList().contains(flagged.getGuild().getNation()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!city.cityOutlaws.contains(flagged.getObjectUUID()))
|
||||||
|
city.cityOutlaws.add(flagged.getObjectUUID());
|
||||||
|
}
|
||||||
|
|
||||||
public float getCurrentHitpoints() {
|
public float getCurrentHitpoints() {
|
||||||
return this.health.get();
|
return this.health.get();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,9 +148,20 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
|
|||||||
|
|
||||||
public void dismiss() {
|
public void dismiss() {
|
||||||
|
|
||||||
|
boolean isSiegeEngine;
|
||||||
|
|
||||||
|
isSiegeEngine = this.getObjectType() == GameObjectType.Mob && ((Mob) this).isSiege();
|
||||||
|
|
||||||
if (this.isPet()) {
|
if (this.isPet()) {
|
||||||
|
|
||||||
if ((this.agentType.equals(mbEnums.AIAgentType.PET))) { //delete summoned pet
|
if (isSiegeEngine) {
|
||||||
|
|
||||||
|
// Released siege engines stay in place, alive, and keep firing at
|
||||||
|
// their current target until a Rod of Command takes them again.
|
||||||
|
|
||||||
|
this.agentType = mbEnums.AIAgentType.MOBILE;
|
||||||
|
|
||||||
|
} else if ((this.agentType.equals(mbEnums.AIAgentType.PET))) { //delete summoned pet
|
||||||
|
|
||||||
WorldGrid.RemoveWorldObject(this);
|
WorldGrid.RemoveWorldObject(this);
|
||||||
|
|
||||||
@@ -167,6 +178,7 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
|
|||||||
|
|
||||||
// clear owner and set not alive
|
// clear owner and set not alive
|
||||||
|
|
||||||
|
if (!isSiegeEngine)
|
||||||
this.isAlive.set(false);
|
this.isAlive.set(false);
|
||||||
|
|
||||||
PlayerCharacter owner = (PlayerCharacter) this.guardCaptain;
|
PlayerCharacter owner = (PlayerCharacter) this.guardCaptain;
|
||||||
@@ -188,6 +200,8 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
|
|||||||
this.guardCaptain = null;
|
this.guardCaptain = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isSiegeEngine && this.isAlive())
|
||||||
|
WorldGrid.updateObject(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -395,8 +395,10 @@ public class Building extends AbstractWorldObject {
|
|||||||
//friendly building has been attacked, add attacker to city outlaw list
|
//friendly building has been attacked, add attacker to city outlaw list
|
||||||
if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()) && attacker.getObjectType().equals(GameObjectType.PlayerCharacter))
|
if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()) && attacker.getObjectType().equals(GameObjectType.PlayerCharacter))
|
||||||
playerCity.cityOutlaws.add(attacker.getObjectUUID());
|
playerCity.cityOutlaws.add(attacker.getObjectUUID());
|
||||||
|
//only city guards respond; siege engines and pets in the zone are left alone
|
||||||
|
|
||||||
for (Mob guard : playerCity.getParent().zoneMobSet)
|
for (Mob guard : playerCity.getParent().zoneMobSet)
|
||||||
if (guard.combatTarget == null)
|
if (guard.isPlayerGuard() && guard.combatTarget == null)
|
||||||
guard.setCombatTarget(attacker);
|
guard.setCombatTarget(attacker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,11 @@ public class ItemTemplate {
|
|||||||
public final EnumSet<mbEnums.MobBehaviourType> rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
|
public final EnumSet<mbEnums.MobBehaviourType> rune_group_role_set = EnumSet.noneOf(mbEnums.MobBehaviourType.class);
|
||||||
public boolean rune_renderable = false;
|
public boolean rune_renderable = false;
|
||||||
public int rune_natural_power_attack;
|
public int rune_natural_power_attack;
|
||||||
|
// One float[] per rune_naturalattacks entry. Same "attack info" struct as a weapon (verified in IDA, see
|
||||||
|
// MBDATA/docs/client-map.md): [0] attack speed (WEPSPEED), [1] range (MAXRANGE), [2] unknown int,
|
||||||
|
// [3] equip slot (1 = RHELD, 2 = LHELD), [4] projectile template id, [5] projectile speed, [6] swing anim,
|
||||||
|
// [7] damage type, [8] min damage, [9] max damage
|
||||||
|
public final ArrayList<float[]> rune_natural_attacks = new ArrayList<>();
|
||||||
public final HashMap<String, String> rune_sparse_data = new HashMap<>();
|
public final HashMap<String, String> rune_sparse_data = new HashMap<>();
|
||||||
public final HashMap<String, int[]> rune_skill_adj = new HashMap<>();
|
public final HashMap<String, int[]> rune_skill_adj = new HashMap<>();
|
||||||
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
|
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||||
@@ -554,6 +559,21 @@ public class ItemTemplate {
|
|||||||
rune_renderable = jsonObject.getBoolean("rune_renderable");
|
rune_renderable = jsonObject.getBoolean("rune_renderable");
|
||||||
rune_natural_power_attack = jsonObject.getInt("rune_natural_power_attack");
|
rune_natural_power_attack = jsonObject.getInt("rune_natural_power_attack");
|
||||||
|
|
||||||
|
if (jsonObject.has("rune_naturalattacks")) {
|
||||||
|
|
||||||
|
JSONArray natural_attacks_json = jsonObject.getJSONArray("rune_naturalattacks");
|
||||||
|
|
||||||
|
for (Object attackEntry : natural_attacks_json) {
|
||||||
|
JSONArray attackArray = (JSONArray) attackEntry;
|
||||||
|
float[] attack = new float[attackArray.length()];
|
||||||
|
|
||||||
|
for (int i = 0; i < attackArray.length(); i++)
|
||||||
|
attack[i] = attackArray.getFloat(i);
|
||||||
|
|
||||||
|
rune_natural_attacks.add(attack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JSONObject rune_sparse_json = jsonObject.getJSONObject("rune_sparse_data");
|
JSONObject rune_sparse_json = jsonObject.getJSONObject("rune_sparse_data");
|
||||||
|
|
||||||
for (String sparseType : rune_sparse_json.keySet()) {
|
for (String sparseType : rune_sparse_json.keySet()) {
|
||||||
|
|||||||
@@ -552,6 +552,12 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
|||||||
|
|
||||||
petMinion.despawned = false;
|
petMinion.despawned = false;
|
||||||
petMinion.runAfterLoad();
|
petMinion.runAfterLoad();
|
||||||
|
|
||||||
|
// Summoned pets carry MASK_PET. Necro pets keep their own NecroPet handling.
|
||||||
|
|
||||||
|
if (!petMinion.isNecroPet())
|
||||||
|
petMinion.setObjectTypeMask(petMinion.getObjectTypeMask() | MBServerStatics.MASK_PET);
|
||||||
|
|
||||||
DbManager.addToCache(petMinion);
|
DbManager.addToCache(petMinion);
|
||||||
petMinion.setLoc(petMinion.bindLoc);
|
petMinion.setLoc(petMinion.bindLoc);
|
||||||
|
|
||||||
@@ -1318,6 +1324,11 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
|||||||
if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter))
|
if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
|
||||||
|
|
||||||
|
if (this.isSiege())
|
||||||
|
return;
|
||||||
|
|
||||||
if (this.getCombatTarget() == null)
|
if (this.getCombatTarget() == null)
|
||||||
this.setCombatTarget(ac);
|
this.setCombatTarget(ac);
|
||||||
|
|
||||||
|
|||||||
@@ -4184,8 +4184,9 @@ public class PlayerCharacter extends AbstractCharacter {
|
|||||||
} else if (currentPet.isSiege()) {
|
} else if (currentPet.isSiege()) {
|
||||||
currentPet.agentType = AIAgentType.MOBILE;
|
currentPet.agentType = AIAgentType.MOBILE;
|
||||||
|
|
||||||
|
// released siege engine keeps its current target
|
||||||
|
|
||||||
currentPet.guardCaptain = null;
|
currentPet.guardCaptain = null;
|
||||||
currentPet.setCombatTarget(null);
|
|
||||||
if (currentPet.isAlive())
|
if (currentPet.isAlive())
|
||||||
WorldGrid.updateObject(currentPet);
|
WorldGrid.updateObject(currentPet);
|
||||||
}
|
}
|
||||||
@@ -4193,7 +4194,6 @@ public class PlayerCharacter extends AbstractCharacter {
|
|||||||
|
|
||||||
toCommand.setPet(this, false);
|
toCommand.setPet(this, false);
|
||||||
this.setPet(toCommand);
|
this.setPet(toCommand);
|
||||||
toCommand.setCombatTarget(null);
|
|
||||||
PetMsg petMsg = new PetMsg(6, toCommand);
|
PetMsg petMsg = new PetMsg(6, toCommand);
|
||||||
Dispatch dispatch = Dispatch.borrow(this, petMsg);
|
Dispatch dispatch = Dispatch.borrow(this, petMsg);
|
||||||
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
|
||||||
|
|||||||
@@ -246,7 +246,9 @@ public class ApplyEffectPowerAction extends AbstractPowerAction {
|
|||||||
|
|
||||||
if (this.effectID.equals("TAUNT")) {
|
if (this.effectID.equals("TAUNT")) {
|
||||||
|
|
||||||
if (awo != null && awo.getObjectType() == GameObjectType.Mob) {
|
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
|
||||||
|
|
||||||
|
if (awo != null && awo.getObjectType() == GameObjectType.Mob && !((Mob) awo).isSiege()) {
|
||||||
((Mob) awo).setCombatTarget(source);
|
((Mob) awo).setCombatTarget(source);
|
||||||
ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
|
ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
|
||||||
DispatchManager.sendToAllInRange(source, msg);
|
DispatchManager.sendToAllInRange(source, msg);
|
||||||
|
|||||||
Reference in new issue
Block a user