Compare commits
19
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;
|
||||
|
||||
awonpc.playerAgroMap.put(player.getObjectUUID(), 0f);
|
||||
((Mob) awonpc).setCombatTarget(null);
|
||||
|
||||
// siege engines keep firing at their target when a new player loads them
|
||||
|
||||
if (!awonpc.isSiege())
|
||||
((Mob) awonpc).setCombatTarget(null);
|
||||
|
||||
lcm = new LoadCharacterMsg(awonpc, PlayerCharacter.hideNonAscii());
|
||||
|
||||
} else if ((awo.getObjectTypeMask() & MBServerStatics.MASK_NPC) != 0) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import engine.jobs.AttackJob;
|
||||
import engine.jobs.DeferredPowerJob;
|
||||
import engine.mbEnums;
|
||||
import engine.net.client.ClientConnection;
|
||||
import engine.net.client.msg.PowerProjectileMsg;
|
||||
import engine.net.client.msg.TargetedActionMsg;
|
||||
import engine.net.client.msg.UpdateStateMsg;
|
||||
import engine.objects.*;
|
||||
@@ -146,11 +147,19 @@ public enum CombatManager {
|
||||
rangeMod += bonus.getFloatPercentAll(mbEnums.ModType.WeaponRange, mbEnums.SourceType.None);
|
||||
|
||||
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 (((Mob) attacker).isSiege())
|
||||
attackRange = 300;
|
||||
attackRange = getSiegeRange((Mob) attacker);
|
||||
|
||||
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.
|
||||
|
||||
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))
|
||||
@@ -232,7 +257,8 @@ public enum CombatManager {
|
||||
|
||||
|
||||
//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()) {
|
||||
((AbstractCharacter) target).combatTarget = attacker;
|
||||
if (target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter) && ((AbstractCharacter) target).isCombat())
|
||||
@@ -319,7 +345,7 @@ public enum CombatManager {
|
||||
DispatchManager.sendToAllInRange(attacker, msg);
|
||||
|
||||
//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);
|
||||
|
||||
//set auto attack job
|
||||
@@ -359,7 +385,7 @@ public enum CombatManager {
|
||||
DispatchManager.dispatchMsgToInterestArea(target, msg, mbEnums.DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
|
||||
|
||||
//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);
|
||||
//set auto attack job
|
||||
setAutoAttackJob(attacker, slot, delay);
|
||||
@@ -447,7 +473,7 @@ public enum CombatManager {
|
||||
if (resists.immuneTo(damageType)) {
|
||||
//set auto attack job
|
||||
//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);
|
||||
setAutoAttackJob(attacker, slot, delay);
|
||||
return;
|
||||
@@ -479,6 +505,15 @@ public enum CombatManager {
|
||||
}
|
||||
TargetedActionMsg cmm = new TargetedActionMsg(attacker, target, (float) damage, attackAnim);
|
||||
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) {
|
||||
//damage calc for pet
|
||||
float range;
|
||||
|
||||
@@ -14,12 +14,10 @@ import engine.gameManager.*;
|
||||
import engine.math.Vector3f;
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import engine.mbEnums.DispatchChannel;
|
||||
import engine.mobileAI.Threads.MobAIThread;
|
||||
import engine.mobileAI.Threads.ReSpawner;
|
||||
import engine.mobileAI.utilities.MovementUtilities;
|
||||
import engine.net.client.msg.PerformActionMsg;
|
||||
import engine.net.client.msg.PowerProjectileMsg;
|
||||
import engine.objects.*;
|
||||
import engine.powers.ActionsBase;
|
||||
import engine.powers.PowersBase;
|
||||
@@ -46,6 +44,11 @@ public class MobAI {
|
||||
//
|
||||
// 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) {
|
||||
|
||||
try {
|
||||
@@ -93,7 +96,7 @@ public class MobAI {
|
||||
mob.updateLocation();
|
||||
|
||||
} 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)
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@@ -133,7 +138,7 @@ public class MobAI {
|
||||
target.getPet().setCombatTarget(mob);
|
||||
|
||||
} 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()))
|
||||
guard.setCombatTarget(mob);
|
||||
|
||||
if (mob.isSiege())
|
||||
MovementManager.sendRWSSMsg(mob);
|
||||
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
} 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);
|
||||
|
||||
} 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();
|
||||
|
||||
} 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;
|
||||
}
|
||||
@@ -387,7 +382,7 @@ public class MobAI {
|
||||
return true;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
@@ -452,7 +447,7 @@ public class MobAI {
|
||||
mob.nextCallForHelp = System.currentTimeMillis() + 60000;
|
||||
|
||||
} 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;
|
||||
if (mob.isPlayerGuard() || mob.isSiege()) {
|
||||
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)
|
||||
mob.setCombatTarget(null);
|
||||
}
|
||||
@@ -544,7 +548,7 @@ public class MobAI {
|
||||
|
||||
PlayerCharacter target = (PlayerCharacter) mob.getCombatTarget();
|
||||
|
||||
if (!mob.playerAgroMap.containsKey(target.getObjectUUID())) {
|
||||
if (!mob.isSiege() && !mob.playerAgroMap.containsKey(target.getObjectUUID())) {
|
||||
mob.setCombatTarget(null);
|
||||
return;
|
||||
}
|
||||
@@ -575,7 +579,7 @@ public class MobAI {
|
||||
break;
|
||||
}
|
||||
} 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
|
||||
|
||||
@@ -649,7 +653,7 @@ public class MobAI {
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
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);
|
||||
}
|
||||
} 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)
|
||||
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);
|
||||
return;
|
||||
@@ -769,7 +773,7 @@ public class MobAI {
|
||||
attackTarget(mob, mob.getCombatTarget());
|
||||
|
||||
} 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);
|
||||
}
|
||||
} 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.updateLocation();
|
||||
} 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))
|
||||
continue;
|
||||
|
||||
//don't attack siege engines
|
||||
if (aggroMob.isSiege())
|
||||
continue;
|
||||
|
||||
if (mob.getLoc().distanceSquared2D(aggroMob.getLoc()) > sqr(50))
|
||||
continue;
|
||||
|
||||
mob.setCombatTarget(aggroMob);
|
||||
}
|
||||
} 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
|
||||
|
||||
} 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());
|
||||
}
|
||||
} 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);
|
||||
} 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);
|
||||
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro" + " " + e.getMessage());
|
||||
Logger.error(e, mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1232,7 +1226,7 @@ public class MobAI {
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -656,12 +656,30 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
||||
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD));
|
||||
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
|
||||
Mob mob = (Mob) abstractCharacter;
|
||||
abstractCharacter.minDamageHandOne = (int) mob.mobBase.getDamageMin();
|
||||
abstractCharacter.minDamageHandTwo = (int) mob.mobBase.getDamageMin();
|
||||
abstractCharacter.maxDamageHandOne = (int) mob.mobBase.getDamageMax();
|
||||
abstractCharacter.maxDamageHandTwo = (int) mob.mobBase.getDamageMax();
|
||||
abstractCharacter.atrHandOne = mob.mobBase.getAttackRating();
|
||||
abstractCharacter.atrHandTwo = mob.mobBase.getAttackRating();
|
||||
ItemTemplate rune = ItemTemplate.templates.get(mob.loadID);
|
||||
float minDamage = mob.mobBase.getDamageMin();
|
||||
float maxDamage = mob.mobBase.getDamageMax();
|
||||
int attackRating = 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();
|
||||
}
|
||||
}
|
||||
@@ -717,9 +735,18 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
||||
mastDam = CharacterSkill.getQuickMastery(abstractCharacter, "Unarmed Combat Mastery");
|
||||
else
|
||||
mastDam = masteryPercentage;
|
||||
// TODO Correct these
|
||||
min = 1;
|
||||
max = 3;
|
||||
// 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;
|
||||
max = 3;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (mainHand)
|
||||
@@ -1476,13 +1503,22 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
||||
Mob mob = (Mob) this;
|
||||
|
||||
if (mob.isSiege()) {
|
||||
return 300;
|
||||
return CombatManager.getSiegeRange(mob);
|
||||
}
|
||||
float range = 8;
|
||||
if ((this).charItemManager.equipped.get(EquipSlotType.RHELD) != null) {
|
||||
range = ((Mob) this).charItemManager.equipped.get(EquipSlotType.RHELD).template.item_weapon_max_range;
|
||||
} else if (((Mob) this).charItemManager.equipped.get(EquipSlotType.LHELD) != null) {
|
||||
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?
|
||||
@@ -1827,6 +1863,15 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
||||
if (this.getObjectType().equals(GameObjectType.Mob)) {
|
||||
//handle hate value addition
|
||||
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 (target.playerAgroMap.containsKey(attacker.getObjectUUID()))
|
||||
@@ -1860,6 +1905,41 @@ public abstract class AbstractCharacter extends AbstractWorldObject {
|
||||
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() {
|
||||
return this.health.get();
|
||||
}
|
||||
|
||||
@@ -148,9 +148,20 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
|
||||
|
||||
public void dismiss() {
|
||||
|
||||
boolean isSiegeEngine;
|
||||
|
||||
isSiegeEngine = this.getObjectType() == GameObjectType.Mob && ((Mob) this).isSiege();
|
||||
|
||||
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);
|
||||
|
||||
@@ -167,7 +178,8 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
|
||||
|
||||
// clear owner and set not alive
|
||||
|
||||
this.isAlive.set(false);
|
||||
if (!isSiegeEngine)
|
||||
this.isAlive.set(false);
|
||||
|
||||
PlayerCharacter owner = (PlayerCharacter) this.guardCaptain;
|
||||
|
||||
@@ -188,6 +200,8 @@ public abstract class AbstractIntelligenceAgent extends AbstractCharacter {
|
||||
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
|
||||
if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()) && attacker.getObjectType().equals(GameObjectType.PlayerCharacter))
|
||||
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)
|
||||
if (guard.combatTarget == null)
|
||||
if (guard.isPlayerGuard() && guard.combatTarget == null)
|
||||
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 boolean rune_renderable = false;
|
||||
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, int[]> rune_skill_adj = new HashMap<>();
|
||||
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_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");
|
||||
|
||||
for (String sparseType : rune_sparse_json.keySet()) {
|
||||
|
||||
@@ -552,6 +552,12 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
|
||||
petMinion.despawned = false;
|
||||
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);
|
||||
petMinion.setLoc(petMinion.bindLoc);
|
||||
|
||||
@@ -1318,6 +1324,11 @@ public class Mob extends AbstractIntelligenceAgent implements Delayed {
|
||||
if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter))
|
||||
return;
|
||||
|
||||
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
|
||||
|
||||
if (this.isSiege())
|
||||
return;
|
||||
|
||||
if (this.getCombatTarget() == null)
|
||||
this.setCombatTarget(ac);
|
||||
|
||||
|
||||
@@ -4184,8 +4184,9 @@ public class PlayerCharacter extends AbstractCharacter {
|
||||
} else if (currentPet.isSiege()) {
|
||||
currentPet.agentType = AIAgentType.MOBILE;
|
||||
|
||||
// released siege engine keeps its current target
|
||||
|
||||
currentPet.guardCaptain = null;
|
||||
currentPet.setCombatTarget(null);
|
||||
if (currentPet.isAlive())
|
||||
WorldGrid.updateObject(currentPet);
|
||||
}
|
||||
@@ -4193,7 +4194,6 @@ public class PlayerCharacter extends AbstractCharacter {
|
||||
|
||||
toCommand.setPet(this, false);
|
||||
this.setPet(toCommand);
|
||||
toCommand.setCombatTarget(null);
|
||||
PetMsg petMsg = new PetMsg(6, toCommand);
|
||||
Dispatch dispatch = Dispatch.borrow(this, petMsg);
|
||||
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
|
||||
|
||||
@@ -246,7 +246,9 @@ public class ApplyEffectPowerAction extends AbstractPowerAction {
|
||||
|
||||
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);
|
||||
ChatSystemMsg msg = ChatManager.CombatInfo(source, awo);
|
||||
DispatchManager.sendToAllInRange(source, msg);
|
||||
|
||||
Reference in new issue
Block a user