Compare commits
25
Commits
subdate2
...
cache_parser
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b56d4ee243 | ||
|
|
0fb1c078ad | ||
|
|
c14231afb9 | ||
|
|
0d48080e38 | ||
|
|
777f7ad89d | ||
|
|
40799f96f6 | ||
|
|
5262a602da | ||
|
|
5b80614376 | ||
|
|
b60f2754f1 | ||
|
|
89cdf5655d | ||
|
|
c9208000bb | ||
|
|
531a2d028b | ||
|
|
ca33c5a14c | ||
|
|
5868530414 | ||
|
|
66a731005e | ||
|
|
330db50985 | ||
|
|
12c08c940a | ||
|
|
b21ccdbb4c | ||
|
|
a708a1f0b2 | ||
|
|
b525a7a3ab | ||
|
|
b690195233 | ||
|
|
9aa44d4e9a | ||
|
|
ab1c3f12d3 | ||
|
|
6017641896 | ||
|
|
9866632ad5 |
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;
|
||||
|
||||
+22
-20
@@ -824,6 +824,7 @@ public class mbEnums {
|
||||
ACID,
|
||||
DISEASE,
|
||||
UNKNOWN,
|
||||
ANTISIEGE,
|
||||
// these added for immunities
|
||||
ATTACK,
|
||||
POWERS,
|
||||
@@ -842,34 +843,26 @@ public class mbEnums {
|
||||
|
||||
public static DamageType getDamageType(String modName) {
|
||||
|
||||
String name;
|
||||
|
||||
if (modName.isEmpty())
|
||||
return DamageType.NONE;
|
||||
|
||||
if(modName.toLowerCase().equals("blind"))
|
||||
modName = "BLINDNESS";
|
||||
// Client and DB strings vary in case and punctuation ("Fire", "Anti-Siege")
|
||||
|
||||
if(modName.toLowerCase().equals("powerblock"))
|
||||
modName = "POWERINHIBITOR";
|
||||
name = modName.replaceAll("[\\s,-]", "").toUpperCase();
|
||||
|
||||
//validity check for not looking up damage type that doesn't exist
|
||||
boolean valid = false;
|
||||
for(DamageType type : DamageType.values()){
|
||||
if(type.name().equals(modName))
|
||||
valid = true;
|
||||
}
|
||||
if (name.equals("BLIND"))
|
||||
name = "BLINDNESS";
|
||||
|
||||
if(!valid)
|
||||
return DamageType.NONE;
|
||||
if (name.equals("POWERBLOCK"))
|
||||
name = "POWERINHIBITOR";
|
||||
|
||||
DamageType damageType;
|
||||
for (DamageType type : DamageType.values())
|
||||
if (type.name().equals(name))
|
||||
return type;
|
||||
|
||||
try {
|
||||
damageType = DamageType.valueOf(modName.replace(",", "").toUpperCase());
|
||||
} catch (Exception e) {
|
||||
Logger.error(e);
|
||||
return DamageType.NONE;
|
||||
}
|
||||
return damageType;
|
||||
return DamageType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1509,6 +1502,15 @@ public class mbEnums {
|
||||
}
|
||||
}
|
||||
|
||||
// rune_type values from the client cache data (COBJECTS/RUNE)
|
||||
|
||||
public enum RuneType {
|
||||
CLASS,
|
||||
DISCIPLINE,
|
||||
RACE,
|
||||
TALENT
|
||||
}
|
||||
|
||||
public enum ShrineType {
|
||||
|
||||
Aelfborn(1701900, -75506007, true),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,14 @@ public class ItemTemplate {
|
||||
public final EnumSet<mbEnums.DisciplineType> item_disc_res = EnumSet.noneOf(mbEnums.DisciplineType.class);
|
||||
public int item_level_req;
|
||||
public mbEnums.SexType item_sex_req;
|
||||
// Lossy: some runes list the same power several times with different
|
||||
// arguments (a step table, e.g. PET_DISC). Only the last entry is kept.
|
||||
// engine.parsers.objects.RuneTemplate keeps the full list.
|
||||
public final HashMap<String, int[]> item_user_power_action = new HashMap<>();
|
||||
public final HashMap<String, Integer> item_power_grant = new HashMap<>();
|
||||
// Lossy: some runes carry up to 35 power_actions entries per power
|
||||
// (a step table, e.g. HNT-031). Only the first entry is kept.
|
||||
// engine.parsers.objects.RuneTemplate keeps the full list.
|
||||
public final HashMap<String, int[]> item_power_action = new HashMap<>();
|
||||
public final HashMap<mbEnums.ResourceType, Integer> item_resource_cost = new HashMap<>();
|
||||
public int modTable;
|
||||
@@ -138,6 +144,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 +565,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);
|
||||
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.cache;
|
||||
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.parsers.objects.PowerGrant;
|
||||
import engine.parsers.objects.UserPowerAction;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
// Shared JSON helpers for the COBJECTS cache parsers.
|
||||
// Only generic reading lives here; each parser owns its own field mapping.
|
||||
|
||||
public class CacheParserUtil {
|
||||
|
||||
// Files are named <template id>.json
|
||||
|
||||
public static File[] listJsonFiles(String folderPath) {
|
||||
|
||||
File folder = new File(folderPath);
|
||||
File[] jsonFiles = folder.listFiles((dir, name) -> name.endsWith(".json"));
|
||||
|
||||
if (jsonFiles == null) {
|
||||
Logger.error("Cache folder not found: " + folder.getAbsolutePath());
|
||||
return new File[0];
|
||||
}
|
||||
|
||||
return jsonFiles;
|
||||
}
|
||||
|
||||
public static int templateIdFromFile(File jsonFile) {
|
||||
|
||||
String fileName = jsonFile.getName();
|
||||
|
||||
return Integer.parseInt(fileName.substring(0, fileName.length() - ".json".length()));
|
||||
}
|
||||
|
||||
public static JSONObject readJsonFile(File jsonFile) throws IOException {
|
||||
|
||||
byte[] fileData = Files.readAllBytes(jsonFile.toPath());
|
||||
|
||||
return new JSONObject(new String(fileData, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// [x, y, z] -> vector. Returns null for an empty array.
|
||||
|
||||
public static Vector3fImmutable readVector3(JSONArray vectorJson) {
|
||||
|
||||
if (vectorJson.length() < 3)
|
||||
return null;
|
||||
|
||||
return new Vector3fImmutable(vectorJson.getFloat(0), vectorJson.getFloat(1), vectorJson.getFloat(2));
|
||||
}
|
||||
|
||||
// Client strings carry spaces, hyphens and commas that enum constants
|
||||
// can't ("Half-Giant", "Battle Magus").
|
||||
|
||||
public static String enumName(String value) {
|
||||
|
||||
String name = value;
|
||||
|
||||
name = name.replaceAll("\\s", "");
|
||||
name = name.replaceAll("-", "");
|
||||
name = name.replaceAll(",", "");
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
// Exact match on the cleaned name first, then upper case (ResourceType).
|
||||
// Unknown values are logged and returned as null so one bad value
|
||||
// doesn't drop the whole template.
|
||||
|
||||
public static <E extends Enum<E>> E readEnum(Class<E> enumClass, String value, int templateId, String fieldName) {
|
||||
|
||||
String name = enumName(value);
|
||||
|
||||
try {
|
||||
return Enum.valueOf(enumClass, name);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// fall through to upper case
|
||||
}
|
||||
|
||||
try {
|
||||
return Enum.valueOf(enumClass, name.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
Logger.error(templateId + ": " + fieldName + " has no " + enumClass.getSimpleName() + " for '" + value + "'");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends Enum<E>> EnumSet<E> readEnumSet(Class<E> enumClass, JSONArray valuesJson, int templateId, String fieldName) {
|
||||
|
||||
EnumSet<E> enumSet = EnumSet.noneOf(enumClass);
|
||||
E enumValue;
|
||||
|
||||
for (int i = 0; i < valuesJson.length(); i++) {
|
||||
enumValue = readEnum(enumClass, valuesJson.getString(i), templateId, fieldName);
|
||||
|
||||
if (enumValue != null)
|
||||
enumSet.add(enumValue);
|
||||
}
|
||||
|
||||
return enumSet;
|
||||
}
|
||||
|
||||
// [{keyName: "Strength", valueName: 40}, ...] -> enum map
|
||||
// Negative values are stored unsigned in the JSON (-5 is 4294967291),
|
||||
// so values are read as long and wrapped to int.
|
||||
|
||||
public static <E extends Enum<E>> EnumMap<E, Integer> readEnumValueMap(Class<E> enumClass, JSONArray entriesJson,
|
||||
String keyName, String valueName,
|
||||
int templateId, String fieldName) {
|
||||
|
||||
EnumMap<E, Integer> enumMap = new EnumMap<>(enumClass);
|
||||
JSONObject entry;
|
||||
E enumValue;
|
||||
|
||||
for (int i = 0; i < entriesJson.length(); i++) {
|
||||
entry = entriesJson.getJSONObject(i);
|
||||
enumValue = readEnum(enumClass, entry.getString(keyName), templateId, fieldName);
|
||||
|
||||
if (enumValue != null)
|
||||
enumMap.put(enumValue, (int) entry.getLong(valueName));
|
||||
}
|
||||
|
||||
return enumMap;
|
||||
}
|
||||
|
||||
// [{keyName: "Sword", valueName: 20}, ...] -> map in file order
|
||||
|
||||
public static LinkedHashMap<String, Integer> readStringValueMap(JSONArray entriesJson, String keyName, String valueName) {
|
||||
|
||||
LinkedHashMap<String, Integer> valueMap = new LinkedHashMap<>();
|
||||
JSONObject entry;
|
||||
|
||||
for (int i = 0; i < entriesJson.length(); i++) {
|
||||
entry = entriesJson.getJSONObject(i);
|
||||
valueMap.put(entry.getString(keyName), (int) entry.getLong(valueName));
|
||||
}
|
||||
|
||||
return valueMap;
|
||||
}
|
||||
|
||||
// {"SLASHING": 0.0, ...} -> enum map
|
||||
|
||||
public static <E extends Enum<E>> EnumMap<E, Float> readEnumFloatMap(Class<E> enumClass, JSONObject mapJson,
|
||||
int templateId, String fieldName) {
|
||||
|
||||
EnumMap<E, Float> enumMap = new EnumMap<>(enumClass);
|
||||
E enumValue;
|
||||
|
||||
for (String key : mapJson.keySet()) {
|
||||
enumValue = readEnum(enumClass, key, templateId, fieldName);
|
||||
|
||||
if (enumValue != null)
|
||||
enumMap.put(enumValue, mapJson.getFloat(key));
|
||||
}
|
||||
|
||||
return enumMap;
|
||||
}
|
||||
|
||||
// Values are read as long and wrapped to int so unsigned 32-bit hashes survive.
|
||||
|
||||
public static int[] readIntArray(JSONArray valuesJson) {
|
||||
|
||||
int[] values = new int[valuesJson.length()];
|
||||
|
||||
for (int i = 0; i < valuesJson.length(); i++)
|
||||
values[i] = (int) valuesJson.getLong(i);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public static float[] readFloatArray(JSONArray valuesJson) {
|
||||
|
||||
float[] values = new float[valuesJson.length()];
|
||||
|
||||
for (int i = 0; i < valuesJson.length(); i++)
|
||||
values[i] = valuesJson.getFloat(i);
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
public static ArrayList<Integer> readIntList(JSONArray valuesJson) {
|
||||
|
||||
ArrayList<Integer> values = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < valuesJson.length(); i++)
|
||||
values.add((int) valuesJson.getLong(i));
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
// [[a, b], [a, b], ...] -> list of int[]
|
||||
|
||||
public static ArrayList<int[]> readIntArrays(JSONArray arraysJson) {
|
||||
|
||||
ArrayList<int[]> arrays = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < arraysJson.length(); i++)
|
||||
arrays.add(readIntArray(arraysJson.getJSONArray(i)));
|
||||
|
||||
return arrays;
|
||||
}
|
||||
|
||||
// Sparse data values are mixed bool/int/string; stored as strings.
|
||||
|
||||
public static HashMap<String, String> readSparseData(JSONObject sparseJson) {
|
||||
|
||||
HashMap<String, String> sparseData = new HashMap<>();
|
||||
|
||||
for (String key : sparseJson.keySet())
|
||||
sparseData.put(key, sparseJson.get(key).toString());
|
||||
|
||||
return sparseData;
|
||||
}
|
||||
|
||||
// Some fields are a name when set and the number 0 when not
|
||||
// (obj_sound_table, item_skill_used). The number becomes "".
|
||||
|
||||
public static String readLabel(JSONObject json, String key) {
|
||||
|
||||
Object value = json.get(key);
|
||||
|
||||
if (value instanceof String)
|
||||
return (String) value;
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static ArrayList<UserPowerAction> readUserPowerActions(JSONArray actionsJson) {
|
||||
|
||||
ArrayList<UserPowerAction> userPowerActions = new ArrayList<>();
|
||||
JSONObject entry;
|
||||
|
||||
for (int i = 0; i < actionsJson.length(); i++) {
|
||||
entry = actionsJson.getJSONObject(i);
|
||||
userPowerActions.add(new UserPowerAction(entry.getString("power"), readIntArray(entry.getJSONArray("arguments"))));
|
||||
}
|
||||
|
||||
return userPowerActions;
|
||||
}
|
||||
|
||||
public static ArrayList<PowerGrant> readPowerGrants(JSONArray grantsJson) {
|
||||
|
||||
ArrayList<PowerGrant> powerGrants = new ArrayList<>();
|
||||
JSONObject entry;
|
||||
PowerGrant powerGrant;
|
||||
|
||||
for (int i = 0; i < grantsJson.length(); i++) {
|
||||
entry = grantsJson.getJSONObject(i);
|
||||
powerGrant = new PowerGrant();
|
||||
|
||||
powerGrant.power_type = entry.getString("power_type");
|
||||
powerGrant.power_value = entry.getInt("power_value");
|
||||
powerGrant.power_granted_attrs = entry.getJSONArray("power_granted_attrs");
|
||||
powerGrant.power_granted_skills.addAll(readIntArrays(entry.getJSONArray("power_granted_skills")));
|
||||
powerGrant.power_granted_powers.addAll(readIntArrays(entry.getJSONArray("power_granted_powers")));
|
||||
powerGrant.power_monster_types = entry.getJSONArray("power_monster_types");
|
||||
|
||||
powerGrants.add(powerGrant);
|
||||
}
|
||||
|
||||
return powerGrants;
|
||||
}
|
||||
|
||||
// [{power_type: "X", power_actions: [{power_arguments: [a, b]}, ...]}, ...]
|
||||
// -> power_type -> list of argument arrays (one per power_actions entry)
|
||||
|
||||
public static LinkedHashMap<String, ArrayList<int[]>> readPowerActions(JSONArray actionsJson) {
|
||||
|
||||
LinkedHashMap<String, ArrayList<int[]>> powerActions = new LinkedHashMap<>();
|
||||
JSONObject entry;
|
||||
JSONArray argumentsJson;
|
||||
ArrayList<int[]> arguments;
|
||||
|
||||
for (int i = 0; i < actionsJson.length(); i++) {
|
||||
entry = actionsJson.getJSONObject(i);
|
||||
argumentsJson = entry.getJSONArray("power_actions");
|
||||
arguments = new ArrayList<>();
|
||||
|
||||
for (int j = 0; j < argumentsJson.length(); j++)
|
||||
arguments.add(readIntArray(argumentsJson.getJSONObject(j).getJSONArray("power_arguments")));
|
||||
|
||||
powerActions.put(entry.getString("power_type"), arguments);
|
||||
}
|
||||
|
||||
return powerActions;
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.cache;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.mbEnums;
|
||||
import engine.parsers.objects.DeedTemplate;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
// Loads DEED templates from the client cache JSON (COBJECTS/DEED/<id>.json).
|
||||
// Not yet wired into server startup.
|
||||
|
||||
public class DeedParser {
|
||||
|
||||
private static final String deedPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/DEED/";
|
||||
|
||||
public static HashMap<Integer, DeedTemplate> parseDeedFolder() {
|
||||
|
||||
HashMap<Integer, DeedTemplate> deedTemplates = new HashMap<>();
|
||||
File[] deedFiles = CacheParserUtil.listJsonFiles(deedPath);
|
||||
int templateId;
|
||||
JSONObject deedJson;
|
||||
|
||||
for (File deedFile : deedFiles) {
|
||||
try {
|
||||
templateId = CacheParserUtil.templateIdFromFile(deedFile);
|
||||
deedJson = CacheParserUtil.readJsonFile(deedFile);
|
||||
deedTemplates.put(templateId, parseDeedEntry(templateId, deedJson));
|
||||
} catch (Exception e) {
|
||||
Logger.error(deedFile.getName() + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Loaded " + deedTemplates.size() + " of " + deedFiles.length + " deed templates");
|
||||
|
||||
return deedTemplates;
|
||||
}
|
||||
|
||||
private static DeedTemplate parseDeedEntry(int templateId, JSONObject deedJson) {
|
||||
|
||||
DeedTemplate deed = new DeedTemplate();
|
||||
JSONObject raceReq;
|
||||
JSONObject classReq;
|
||||
JSONObject discReq;
|
||||
JSONArray itemFlags;
|
||||
String flag;
|
||||
mbEnums.ItemFlags itemFlag;
|
||||
|
||||
deed.template_id = templateId;
|
||||
|
||||
// obj_
|
||||
|
||||
deed.obj_name = deedJson.getString("obj_name");
|
||||
deed.obj_pickable = deedJson.getBoolean("obj_pickable");
|
||||
deed.obj_gravity = deedJson.getFloat("obj_gravity");
|
||||
deed.obj_cull_distance = deedJson.getFloat("obj_cull_distance");
|
||||
deed.obj_scale = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_scale"));
|
||||
deed.obj_render_object = deedJson.getInt("obj_render_object");
|
||||
deed.obj_double_fusion = deedJson.getBoolean("obj_double_fusion");
|
||||
deed.obj_forward_vector = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_forward_vector"));
|
||||
deed.obj_tracking_name = deedJson.getString("obj_tracking_name");
|
||||
deed.obj_max_tracking_distance = deedJson.getFloat("obj_max_tracking_distance");
|
||||
deed.obj_icon = deedJson.getInt("obj_icon");
|
||||
deed.obj_gravity_f = deedJson.getFloat("obj_gravity_f");
|
||||
deed.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
|
||||
deed.obj_arc_hardpoint_list = deedJson.getJSONArray("obj_arc_hardpoint_list");
|
||||
deed.obj_sparse_data.putAll(CacheParserUtil.readSparseData(deedJson.getJSONObject("obj_sparse_data")));
|
||||
deed.obj_render_object_low_detail = deedJson.getInt("obj_render_object_low_detail");
|
||||
deed.obj_default_alignment = CacheParserUtil.readVector3(deedJson.getJSONArray("obj_default_alignment"));
|
||||
deed.obj_sound_table = CacheParserUtil.readLabel(deedJson, "obj_sound_table");
|
||||
|
||||
// combat_
|
||||
|
||||
deed.combat_health_current = deedJson.getFloat("combat_health_current");
|
||||
deed.combat_health_full = deedJson.getFloat("combat_health_full");
|
||||
deed.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, deedJson.getJSONObject("combat_attack_resist"),
|
||||
templateId, "combat_attack_resist"));
|
||||
deed.combat_powers = deedJson.getJSONArray("combat_powers");
|
||||
|
||||
// item_
|
||||
|
||||
deed.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, deedJson.getString("item_type"), templateId, "item_type");
|
||||
deed.item_eq_slots_value = deedJson.getInt("item_eq_slots_value");
|
||||
deed.item_eq_slots_type = deedJson.getBoolean("item_eq_slots_type");
|
||||
deed.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
|
||||
deed.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, deedJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
|
||||
deed.item_takeable = deedJson.getBoolean("item_takeable");
|
||||
deed.item_value = deedJson.getInt("item_value");
|
||||
deed.item_wt = deedJson.getInt("item_wt");
|
||||
deed.item_passive_defense_mod = deedJson.getFloat("item_passive_defense_mod");
|
||||
deed.item_base_name = deedJson.getString("item_base_name");
|
||||
deed.item_dsc = deedJson.getString("item_dsc");
|
||||
deed.item_render_object_female = deedJson.getInt("item_render_object_female");
|
||||
deed.item_health_full = deedJson.getFloat("item_health_full");
|
||||
deed.item_skill_used = CacheParserUtil.readLabel(deedJson, "item_skill_used");
|
||||
deed.item_skill_mastery_used = CacheParserUtil.readLabel(deedJson, "item_skill_mastery_used");
|
||||
deed.item_parry_anim_id = deedJson.getInt("item_parry_anim_id");
|
||||
deed.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_info"),
|
||||
"offering_type", "offering_value", templateId, "item_offering_info"));
|
||||
|
||||
// "None" is the client's empty marker, not a flag
|
||||
|
||||
itemFlags = deedJson.getJSONArray("item_flags");
|
||||
|
||||
for (int i = 0; i < itemFlags.length(); i++) {
|
||||
flag = itemFlags.getString(i);
|
||||
|
||||
if (flag.equals("None"))
|
||||
continue;
|
||||
|
||||
itemFlag = CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags");
|
||||
|
||||
if (itemFlag != null)
|
||||
deed.item_flags.add(itemFlag);
|
||||
}
|
||||
|
||||
deed.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, deedJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
|
||||
deed.item_post_item_id = deedJson.getInt("item_post_item_id");
|
||||
deed.item_initial_charges = deedJson.getInt("item_initial_charges");
|
||||
deed.item_book_arcana = deedJson.getInt("item_book_arcana");
|
||||
deed.item_skill_req.putAll(CacheParserUtil.readStringValueMap(deedJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
|
||||
|
||||
raceReq = deedJson.getJSONObject("item_race_req");
|
||||
deed.item_race_req_restrict = raceReq.getBoolean("restrict");
|
||||
deed.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
|
||||
|
||||
classReq = deedJson.getJSONObject("item_class_req");
|
||||
deed.item_class_req_restrict = classReq.getBoolean("restrict");
|
||||
deed.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
|
||||
|
||||
discReq = deedJson.getJSONObject("item_disc_req");
|
||||
deed.item_disc_req_restrict = discReq.getBoolean("restrict");
|
||||
deed.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
|
||||
|
||||
deed.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, deedJson.getJSONArray("item_attr_req"),
|
||||
"attr_type", "attr_value", templateId, "item_attr_req"));
|
||||
deed.item_level_req = deedJson.getInt("item_level_req");
|
||||
deed.item_rank_req = deedJson.getInt("item_rank_req");
|
||||
deed.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, deedJson.getString("item_sex_req"), templateId, "item_sex_req");
|
||||
deed.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(deedJson.getJSONArray("item_user_power_action")));
|
||||
deed.item_power_grant.addAll(CacheParserUtil.readPowerGrants(deedJson.getJSONArray("item_power_grant")));
|
||||
deed.item_power_action.putAll(CacheParserUtil.readPowerActions(deedJson.getJSONArray("item_power_action")));
|
||||
deed.item_sheathable = deedJson.getBoolean("item_sheathable");
|
||||
deed.item_has_stub = deedJson.getBoolean("item_has_stub");
|
||||
deed.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, deedJson.getJSONArray("item_offering_adjustments"),
|
||||
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
|
||||
deed.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, deedJson.getJSONArray("item_resource_costs"),
|
||||
"resource_type", "resource_value", templateId, "item_resource_costs"));
|
||||
deed.item_bane_rank = deedJson.getInt("item_bane_rank");
|
||||
deed.item_ignore_saved_actions = deedJson.getBoolean("item_ignore_saved_actions");
|
||||
|
||||
// deed_
|
||||
|
||||
deed.deed_type = deedJson.getInt("deed_type");
|
||||
deed.deed_furniture_id = deedJson.getInt("deed_furniture_id");
|
||||
deed.deed_target_id = deedJson.getInt("deed_target_id");
|
||||
deed.deed_employment = deedJson.getInt("deed_employment");
|
||||
deed.deed_start_rank = deedJson.getInt("deed_start_rank");
|
||||
deed.deed_name_lookup = deedJson.getInt("deed_name_lookup");
|
||||
deed.deed_indoors = deedJson.getBoolean("deed_indoors");
|
||||
deed.deed_is_fortress = deedJson.getBoolean("deed_is_fortress");
|
||||
deed.deed_namelookup_val = deedJson.getFloat("deed_namelookup_val");
|
||||
deed.deed_custom_city = deedJson.getBoolean("deed_custom_city");
|
||||
deed.deed_structure_id = deedJson.getInt("deed_structure_id");
|
||||
|
||||
return deed;
|
||||
}
|
||||
}
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.cache;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.mbEnums;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
// Loads RUNE templates from the client cache JSON (COBJECTS/RUNE/<id>.json).
|
||||
// Not yet wired into server startup.
|
||||
|
||||
public class RuneParser {
|
||||
|
||||
private static final String runePath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/RUNE/";
|
||||
|
||||
public static HashMap<Integer, RuneTemplate> parseRuneFolder() {
|
||||
|
||||
HashMap<Integer, RuneTemplate> runeTemplates = new HashMap<>();
|
||||
File[] runeFiles = CacheParserUtil.listJsonFiles(runePath);
|
||||
int templateId;
|
||||
JSONObject runeJson;
|
||||
|
||||
for (File runeFile : runeFiles) {
|
||||
try {
|
||||
templateId = CacheParserUtil.templateIdFromFile(runeFile);
|
||||
runeJson = CacheParserUtil.readJsonFile(runeFile);
|
||||
runeTemplates.put(templateId, parseRuneEntry(templateId, runeJson));
|
||||
} catch (Exception e) {
|
||||
Logger.error(runeFile.getName() + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Loaded " + runeTemplates.size() + " of " + runeFiles.length + " rune templates");
|
||||
|
||||
return runeTemplates;
|
||||
}
|
||||
|
||||
private static RuneTemplate parseRuneEntry(int templateId, JSONObject runeJson) {
|
||||
|
||||
RuneTemplate rune = new RuneTemplate();
|
||||
JSONObject raceReq;
|
||||
JSONObject classReq;
|
||||
JSONObject discReq;
|
||||
JSONArray itemFlags;
|
||||
String flag;
|
||||
mbEnums.ItemFlags itemFlag;
|
||||
Object subType;
|
||||
JSONObject speedJson;
|
||||
mbEnums.MovementType movementType;
|
||||
JSONObject groupJson;
|
||||
JSONArray skillGrantsJson;
|
||||
JSONObject skillGrantJson;
|
||||
RuneTemplate.SkillGrant skillGrant;
|
||||
JSONArray skillAdjJson;
|
||||
JSONObject skillAdjEntry;
|
||||
JSONArray naturalAttacksJson;
|
||||
JSONArray inventoryJson;
|
||||
JSONObject inventoryEntryJson;
|
||||
RuneTemplate.InventoryEntry inventoryEntry;
|
||||
JSONArray bodyPartsJson;
|
||||
JSONObject bodyPartJson;
|
||||
RuneTemplate.BodyPart bodyPart;
|
||||
|
||||
rune.template_id = templateId;
|
||||
|
||||
// obj_
|
||||
|
||||
rune.obj_name = runeJson.getString("obj_name");
|
||||
rune.obj_pickable = runeJson.getBoolean("obj_pickable");
|
||||
rune.obj_gravity = runeJson.getFloat("obj_gravity");
|
||||
rune.obj_cull_distance = runeJson.getFloat("obj_cull_distance");
|
||||
rune.obj_scale = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_scale"));
|
||||
rune.obj_render_object = runeJson.getInt("obj_render_object");
|
||||
rune.obj_double_fusion = runeJson.getBoolean("obj_double_fusion");
|
||||
rune.obj_forward_vector = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_forward_vector"));
|
||||
rune.obj_tracking_name = runeJson.getString("obj_tracking_name");
|
||||
rune.obj_max_tracking_distance = runeJson.getFloat("obj_max_tracking_distance");
|
||||
rune.obj_icon = runeJson.getInt("obj_icon");
|
||||
rune.obj_gravity_f = runeJson.getFloat("obj_gravity_f");
|
||||
rune.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
|
||||
rune.obj_arc_hardpoint_list = runeJson.getJSONArray("obj_arc_hardpoint_list");
|
||||
rune.obj_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("obj_sparse_data")));
|
||||
rune.obj_render_object_low_detail = runeJson.getInt("obj_render_object_low_detail");
|
||||
rune.obj_default_alignment = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_default_alignment"));
|
||||
rune.obj_sound_table = CacheParserUtil.readLabel(runeJson, "obj_sound_table");
|
||||
|
||||
// combat_
|
||||
|
||||
rune.combat_health_current = runeJson.getFloat("combat_health_current");
|
||||
rune.combat_health_full = runeJson.getFloat("combat_health_full");
|
||||
rune.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, runeJson.getJSONObject("combat_attack_resist"),
|
||||
templateId, "combat_attack_resist"));
|
||||
rune.combat_powers = runeJson.getJSONArray("combat_powers");
|
||||
|
||||
// item_
|
||||
|
||||
rune.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, runeJson.getString("item_type"), templateId, "item_type");
|
||||
rune.item_eq_slots_value = runeJson.getInt("item_eq_slots_value");
|
||||
rune.item_eq_slots_type = runeJson.getBoolean("item_eq_slots_type");
|
||||
rune.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
|
||||
rune.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
|
||||
rune.item_takeable = runeJson.getBoolean("item_takeable");
|
||||
rune.item_value = runeJson.getInt("item_value");
|
||||
rune.item_wt = runeJson.getInt("item_wt");
|
||||
rune.item_passive_defense_mod = runeJson.getFloat("item_passive_defense_mod");
|
||||
rune.item_base_name = runeJson.getString("item_base_name");
|
||||
rune.item_dsc = runeJson.getString("item_dsc");
|
||||
rune.item_render_object_female = runeJson.getInt("item_render_object_female");
|
||||
rune.item_health_full = runeJson.getFloat("item_health_full");
|
||||
rune.item_skill_used = CacheParserUtil.readLabel(runeJson, "item_skill_used");
|
||||
rune.item_skill_mastery_used = CacheParserUtil.readLabel(runeJson, "item_skill_mastery_used");
|
||||
rune.item_parry_anim_id = runeJson.getInt("item_parry_anim_id");
|
||||
rune.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_info"),
|
||||
"offering_type", "offering_value", templateId, "item_offering_info"));
|
||||
|
||||
// "None" is the client's empty marker, not a flag
|
||||
|
||||
itemFlags = runeJson.getJSONArray("item_flags");
|
||||
|
||||
for (int i = 0; i < itemFlags.length(); i++) {
|
||||
flag = itemFlags.getString(i);
|
||||
|
||||
if (flag.equals("None"))
|
||||
continue;
|
||||
|
||||
itemFlag = CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags");
|
||||
|
||||
if (itemFlag != null)
|
||||
rune.item_flags.add(itemFlag);
|
||||
}
|
||||
|
||||
rune.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, runeJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
|
||||
rune.item_post_item_id = runeJson.getInt("item_post_item_id");
|
||||
rune.item_initial_charges = runeJson.getInt("item_initial_charges");
|
||||
rune.item_book_arcana = runeJson.getInt("item_book_arcana");
|
||||
rune.item_skill_req.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
|
||||
|
||||
raceReq = runeJson.getJSONObject("item_race_req");
|
||||
rune.item_race_req_restrict = raceReq.getBoolean("restrict");
|
||||
rune.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
|
||||
|
||||
classReq = runeJson.getJSONObject("item_class_req");
|
||||
rune.item_class_req_restrict = classReq.getBoolean("restrict");
|
||||
rune.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
|
||||
|
||||
discReq = runeJson.getJSONObject("item_disc_req");
|
||||
rune.item_disc_req_restrict = discReq.getBoolean("restrict");
|
||||
rune.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
|
||||
|
||||
rune.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("item_attr_req"),
|
||||
"attr_type", "attr_value", templateId, "item_attr_req"));
|
||||
rune.item_level_req = runeJson.getInt("item_level_req");
|
||||
rune.item_rank_req = runeJson.getInt("item_rank_req");
|
||||
rune.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("item_sex_req"), templateId, "item_sex_req");
|
||||
rune.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(runeJson.getJSONArray("item_user_power_action")));
|
||||
rune.item_power_grant.addAll(CacheParserUtil.readPowerGrants(runeJson.getJSONArray("item_power_grant")));
|
||||
rune.item_power_action.putAll(CacheParserUtil.readPowerActions(runeJson.getJSONArray("item_power_action")));
|
||||
rune.item_sheathable = runeJson.getBoolean("item_sheathable");
|
||||
rune.item_has_stub = runeJson.getBoolean("item_has_stub");
|
||||
rune.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_adjustments"),
|
||||
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
|
||||
rune.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, runeJson.getJSONArray("item_resource_costs"),
|
||||
"resource_type", "resource_value", templateId, "item_resource_costs"));
|
||||
rune.item_bane_rank = runeJson.getInt("item_bane_rank");
|
||||
rune.item_ignore_saved_actions = runeJson.getBoolean("item_ignore_saved_actions");
|
||||
|
||||
// rune_
|
||||
|
||||
rune.rune_type = CacheParserUtil.readEnum(mbEnums.RuneType.class, runeJson.getString("rune_type"), templateId, "rune_type");
|
||||
|
||||
subType = runeJson.get("rune_sub_type");
|
||||
|
||||
if (subType instanceof String) {
|
||||
rune.rune_sub_type = (String) subType;
|
||||
} else {
|
||||
rune.rune_sub_type = "";
|
||||
rune.rune_sub_type_hash = (int) runeJson.getLong("rune_sub_type");
|
||||
}
|
||||
|
||||
rune.rune_is_standard_character_creation = runeJson.getBoolean("rune_is_standard_character_creation");
|
||||
rune.rune_creation_cost = runeJson.getInt("rune_creation_cost");
|
||||
rune.rune_rank = runeJson.getInt("rune_rank");
|
||||
rune.rune_pracs_per_level = runeJson.getInt("rune_pracs_per_level");
|
||||
rune.rune_exp_req_to_level = runeJson.getFloat("rune_exp_req_to_level");
|
||||
rune.rune_sex = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("rune_sex"), templateId, "rune_sex");
|
||||
rune.rune_class_icon = runeJson.getInt("rune_class_icon");
|
||||
rune.rune_health = runeJson.getInt("rune_health");
|
||||
rune.rune_mana = runeJson.getInt("rune_mana");
|
||||
rune.rune_stamina = runeJson.getInt("rune_stamina");
|
||||
rune.rune_min_damage = runeJson.getFloat("rune_min_damage");
|
||||
rune.rune_max_damage = runeJson.getFloat("rune_max_damage");
|
||||
rune.rune_attack = runeJson.getInt("rune_attack");
|
||||
rune.rune_defense = runeJson.getInt("rune_defense");
|
||||
rune.rune_level = runeJson.getInt("rune_level");
|
||||
|
||||
speedJson = runeJson.getJSONObject("rune_speed");
|
||||
|
||||
for (String speedKey : speedJson.keySet()) {
|
||||
movementType = CacheParserUtil.readEnum(mbEnums.MovementType.class, speedKey, templateId, "rune_speed");
|
||||
|
||||
if (movementType != null)
|
||||
rune.rune_speed.put(movementType, speedJson.getFloat(speedKey));
|
||||
}
|
||||
|
||||
groupJson = runeJson.getJSONObject("rune_group");
|
||||
rune.rune_group_type = groupJson.getInt("group_type");
|
||||
rune.rune_group_is_faction = groupJson.getBoolean("group_is_faction");
|
||||
rune.rune_group_is_guild = groupJson.getBoolean("group_is_guild");
|
||||
|
||||
rune.rune_dsc = runeJson.getString("rune_dsc");
|
||||
rune.rune_fx_txt = runeJson.getString("rune_fx_txt");
|
||||
rune.rune_group_tactics = runeJson.getLong("rune_group_tactics");
|
||||
rune.rune_group_role_set = runeJson.getLong("rune_group_role_set");
|
||||
rune.rune_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_enemy_monster_types"),
|
||||
templateId, "rune_enemy_monster_types"));
|
||||
rune.rune_groupee_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_groupee_monster_types")));
|
||||
rune.rune_helper_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_helper_monster_types")));
|
||||
rune.rune_not_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_not_enemy_monster_types"),
|
||||
templateId, "rune_not_enemy_monster_types"));
|
||||
rune.rune_enemy_gender = runeJson.getJSONArray("rune_enemy_gender");
|
||||
|
||||
skillGrantsJson = runeJson.getJSONArray("rune_skill_grant");
|
||||
|
||||
for (int i = 0; i < skillGrantsJson.length(); i++) {
|
||||
skillGrantJson = skillGrantsJson.getJSONObject(i);
|
||||
skillGrant = new RuneTemplate.SkillGrant();
|
||||
|
||||
skillGrant.skill_type = skillGrantJson.getString("skill_type");
|
||||
skillGrant.skill_value = skillGrantJson.getInt("skill_value");
|
||||
skillGrant.skill_granted_attrs = skillGrantJson.getJSONArray("skill_granted_attrs");
|
||||
skillGrant.skill_granted_skills.addAll(CacheParserUtil.readIntArrays(skillGrantJson.getJSONArray("skill_granted_skills")));
|
||||
skillGrant.skill_granted_powers = skillGrantJson.getJSONArray("skill_granted_powers");
|
||||
skillGrant.skill_monster_types = skillGrantJson.getJSONArray("skill_monster_types");
|
||||
|
||||
rune.rune_skill_grant.add(skillGrant);
|
||||
}
|
||||
|
||||
skillAdjJson = runeJson.getJSONArray("rune_skill_adj");
|
||||
|
||||
for (int i = 0; i < skillAdjJson.length(); i++) {
|
||||
skillAdjEntry = skillAdjJson.getJSONObject(i);
|
||||
rune.rune_skill_adj.put(skillAdjEntry.getString("skill_type"),
|
||||
CacheParserUtil.readIntArrays(skillAdjEntry.getJSONArray("skill_adjusts")));
|
||||
}
|
||||
|
||||
rune.rune_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_attr_adj"),
|
||||
"attr_type", "attr_value", templateId, "rune_attr_adj"));
|
||||
rune.rune_max_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_max_attr_adj"),
|
||||
"attr_type", "attr_value", templateId, "rune_max_attr_adj"));
|
||||
|
||||
naturalAttacksJson = runeJson.getJSONArray("rune_naturalattacks");
|
||||
|
||||
for (int i = 0; i < naturalAttacksJson.length(); i++)
|
||||
rune.rune_naturalattacks.add(CacheParserUtil.readFloatArray(naturalAttacksJson.getJSONArray(i)));
|
||||
|
||||
rune.rune_enchantment_type = runeJson.getJSONArray("rune_enchantment_type");
|
||||
|
||||
inventoryJson = runeJson.getJSONArray("rune_inventory_contents");
|
||||
|
||||
for (int i = 0; i < inventoryJson.length(); i++) {
|
||||
inventoryEntryJson = inventoryJson.getJSONObject(i);
|
||||
inventoryEntry = new RuneTemplate.InventoryEntry();
|
||||
inventoryEntry.type = inventoryEntryJson.getString("type");
|
||||
|
||||
if (inventoryEntryJson.has("gold"))
|
||||
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("gold"));
|
||||
else if (inventoryEntryJson.has("table"))
|
||||
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("table"));
|
||||
else
|
||||
Logger.error(templateId + ": rune_inventory_contents entry of type " + inventoryEntry.type + " has no values");
|
||||
|
||||
rune.rune_inventory_contents.add(inventoryEntry);
|
||||
}
|
||||
|
||||
rune.rune_renderable = runeJson.getBoolean("rune_renderable");
|
||||
|
||||
// Render fields are absent on some runes
|
||||
|
||||
if (runeJson.has("rune_scale_factor"))
|
||||
rune.rune_scale_factor = CacheParserUtil.readVector3(runeJson.getJSONArray("rune_scale_factor"));
|
||||
|
||||
if (runeJson.has("rune_skeleton"))
|
||||
rune.rune_skeleton = runeJson.getInt("rune_skeleton");
|
||||
|
||||
if (runeJson.has("rune_slope_hugger"))
|
||||
rune.rune_slope_hugger = runeJson.getBoolean("rune_slope_hugger");
|
||||
|
||||
if (runeJson.has("rune_can_fly"))
|
||||
rune.rune_can_fly = runeJson.getBoolean("rune_can_fly");
|
||||
|
||||
if (runeJson.has("rune_death_effect"))
|
||||
rune.rune_death_effect = runeJson.getInt("rune_death_effect");
|
||||
|
||||
if (runeJson.has("rune_tombstone_id"))
|
||||
rune.rune_tombstone_id = runeJson.getInt("rune_tombstone_id");
|
||||
|
||||
if (runeJson.has("rune_body_parts")) {
|
||||
|
||||
bodyPartsJson = runeJson.getJSONArray("rune_body_parts");
|
||||
|
||||
for (int i = 0; i < bodyPartsJson.length(); i++) {
|
||||
bodyPartJson = bodyPartsJson.getJSONObject(i);
|
||||
bodyPart = new RuneTemplate.BodyPart();
|
||||
bodyPart.body_part_render = bodyPartJson.getInt("body_part_render");
|
||||
bodyPart.body_part_position = CacheParserUtil.readVector3(bodyPartJson.getJSONArray("body_part_position"));
|
||||
rune.rune_body_parts.add(bodyPart);
|
||||
}
|
||||
}
|
||||
|
||||
if (runeJson.has("rune_hair"))
|
||||
rune.rune_hair.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_hair")));
|
||||
|
||||
if (runeJson.has("rune_beard"))
|
||||
rune.rune_beard.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_beard")));
|
||||
|
||||
rune.rune_natural_power_attack = runeJson.getInt("rune_natural_power_attack");
|
||||
rune.rune_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("rune_sparse_data")));
|
||||
|
||||
return rune;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
// Deed template as defined by the client's cache data
|
||||
// (MBDATA/data/COBJECTS/DEED). Field names match the JSON keys.
|
||||
//
|
||||
// Values are kept as the client defines them: no req/res splitting,
|
||||
// no defaults substituted. Strings with a matching enum in mbEnums
|
||||
// are stored as that enum; everything else stays a String.
|
||||
//
|
||||
// Read only once loaded.
|
||||
|
||||
public class DeedTemplate {
|
||||
|
||||
public int template_id;
|
||||
|
||||
// obj_
|
||||
|
||||
public String obj_name;
|
||||
public boolean obj_pickable;
|
||||
public float obj_gravity;
|
||||
public float obj_cull_distance;
|
||||
public Vector3fImmutable obj_scale;
|
||||
public int obj_render_object;
|
||||
public boolean obj_double_fusion;
|
||||
public Vector3fImmutable obj_forward_vector;
|
||||
public String obj_tracking_name;
|
||||
public float obj_max_tracking_distance;
|
||||
public int obj_icon;
|
||||
public float obj_gravity_f;
|
||||
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
|
||||
public JSONArray obj_arc_hardpoint_list;
|
||||
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
|
||||
public int obj_render_object_low_detail;
|
||||
public Vector3fImmutable obj_default_alignment;
|
||||
public String obj_sound_table;
|
||||
|
||||
// combat_
|
||||
|
||||
public float combat_health_current;
|
||||
public float combat_health_full;
|
||||
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
|
||||
public JSONArray combat_powers;
|
||||
|
||||
// item_
|
||||
|
||||
public mbEnums.ItemType item_type;
|
||||
public int item_eq_slots_value;
|
||||
public boolean item_eq_slots_type;
|
||||
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
|
||||
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
|
||||
public boolean item_takeable;
|
||||
public int item_value;
|
||||
public int item_wt;
|
||||
public float item_passive_defense_mod;
|
||||
public String item_base_name;
|
||||
public String item_dsc;
|
||||
public int item_render_object_female;
|
||||
public float item_health_full;
|
||||
public String item_skill_used;
|
||||
public String item_skill_mastery_used;
|
||||
public int item_parry_anim_id;
|
||||
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
|
||||
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
|
||||
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
|
||||
public int item_post_item_id;
|
||||
public int item_initial_charges;
|
||||
public int item_book_arcana;
|
||||
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
|
||||
public boolean item_race_req_restrict;
|
||||
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||
public boolean item_class_req_restrict;
|
||||
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
|
||||
public boolean item_disc_req_restrict;
|
||||
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
|
||||
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
|
||||
public int item_level_req;
|
||||
public int item_rank_req;
|
||||
public mbEnums.SexType item_sex_req;
|
||||
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
|
||||
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
|
||||
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
|
||||
public boolean item_sheathable;
|
||||
public boolean item_has_stub;
|
||||
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
|
||||
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
|
||||
public int item_bane_rank;
|
||||
public boolean item_ignore_saved_actions;
|
||||
|
||||
// deed_
|
||||
|
||||
public int deed_type;
|
||||
public int deed_furniture_id;
|
||||
public int deed_target_id;
|
||||
public int deed_employment;
|
||||
public int deed_start_rank;
|
||||
public int deed_name_lookup;
|
||||
public boolean deed_indoors;
|
||||
public boolean deed_is_fortress;
|
||||
public float deed_namelookup_val;
|
||||
public boolean deed_custom_city;
|
||||
public int deed_structure_id;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
// One entry of item_power_grant.
|
||||
//
|
||||
// power_granted_skills and power_granted_powers entries look like
|
||||
// [hash, value] pairs (first element is a 32-bit string hash, some
|
||||
// exceed Integer.MAX_VALUE in the JSON and are stored wrapped to int).
|
||||
// This reading is from the data only and has not been confirmed in the client.
|
||||
//
|
||||
// power_granted_attrs and power_monster_types are empty in every DEED and
|
||||
// RUNE file, so their shape is unknown and they are kept as raw JSON.
|
||||
|
||||
public class PowerGrant {
|
||||
|
||||
public String power_type;
|
||||
public int power_value;
|
||||
public JSONArray power_granted_attrs;
|
||||
public final ArrayList<int[]> power_granted_skills = new ArrayList<>();
|
||||
public final ArrayList<int[]> power_granted_powers = new ArrayList<>();
|
||||
public JSONArray power_monster_types;
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
// Rune template as defined by the client's cache data
|
||||
// (MBDATA/data/COBJECTS/RUNE). Field names match the JSON keys.
|
||||
//
|
||||
// Covers every rune the client knows about: player races, classes,
|
||||
// disciplines, talents and mob/pet/npc runes alike.
|
||||
//
|
||||
// Values are kept as the client defines them: no req/res splitting,
|
||||
// no defaults substituted. Strings with a matching enum in mbEnums
|
||||
// are stored as that enum; everything else stays a String.
|
||||
//
|
||||
// Read only once loaded.
|
||||
|
||||
public class RuneTemplate {
|
||||
|
||||
public int template_id;
|
||||
|
||||
// obj_
|
||||
|
||||
public String obj_name;
|
||||
public boolean obj_pickable;
|
||||
public float obj_gravity;
|
||||
public float obj_cull_distance;
|
||||
public Vector3fImmutable obj_scale;
|
||||
public int obj_render_object;
|
||||
public boolean obj_double_fusion;
|
||||
public Vector3fImmutable obj_forward_vector;
|
||||
public String obj_tracking_name;
|
||||
public float obj_max_tracking_distance;
|
||||
public int obj_icon;
|
||||
public float obj_gravity_f;
|
||||
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
|
||||
public JSONArray obj_arc_hardpoint_list;
|
||||
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
|
||||
public int obj_render_object_low_detail;
|
||||
public Vector3fImmutable obj_default_alignment;
|
||||
public String obj_sound_table;
|
||||
|
||||
// combat_
|
||||
|
||||
public float combat_health_current;
|
||||
public float combat_health_full;
|
||||
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
|
||||
public JSONArray combat_powers;
|
||||
|
||||
// item_
|
||||
|
||||
public mbEnums.ItemType item_type;
|
||||
public int item_eq_slots_value;
|
||||
public boolean item_eq_slots_type;
|
||||
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
|
||||
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
|
||||
public boolean item_takeable;
|
||||
public int item_value;
|
||||
public int item_wt;
|
||||
public float item_passive_defense_mod;
|
||||
public String item_base_name;
|
||||
public String item_dsc;
|
||||
public int item_render_object_female;
|
||||
public float item_health_full;
|
||||
public String item_skill_used;
|
||||
public String item_skill_mastery_used;
|
||||
public int item_parry_anim_id;
|
||||
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
|
||||
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
|
||||
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
|
||||
public int item_post_item_id;
|
||||
public int item_initial_charges;
|
||||
public int item_book_arcana;
|
||||
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
|
||||
public boolean item_race_req_restrict;
|
||||
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||
public boolean item_class_req_restrict;
|
||||
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
|
||||
public boolean item_disc_req_restrict;
|
||||
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
|
||||
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
|
||||
public int item_level_req;
|
||||
public int item_rank_req;
|
||||
public mbEnums.SexType item_sex_req;
|
||||
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
|
||||
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
|
||||
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
|
||||
public boolean item_sheathable;
|
||||
public boolean item_has_stub;
|
||||
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
|
||||
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
|
||||
public int item_bane_rank;
|
||||
public boolean item_ignore_saved_actions;
|
||||
|
||||
// rune_
|
||||
|
||||
public mbEnums.RuneType rune_type;
|
||||
public String rune_sub_type;
|
||||
|
||||
// A handful of runes carry a number instead of a name in rune_sub_type.
|
||||
// These look like string hashes the cache converter could not resolve.
|
||||
// Stored here (wrapped to int) with rune_sub_type left empty.
|
||||
|
||||
public int rune_sub_type_hash;
|
||||
public boolean rune_is_standard_character_creation;
|
||||
public int rune_creation_cost;
|
||||
public int rune_rank;
|
||||
public int rune_pracs_per_level;
|
||||
public float rune_exp_req_to_level;
|
||||
public mbEnums.SexType rune_sex;
|
||||
public int rune_class_icon;
|
||||
public int rune_health;
|
||||
public int rune_mana;
|
||||
public int rune_stamina;
|
||||
public float rune_min_damage;
|
||||
public float rune_max_damage;
|
||||
public int rune_attack;
|
||||
public int rune_defense;
|
||||
public int rune_level;
|
||||
public final EnumMap<mbEnums.MovementType, Float> rune_speed = new EnumMap<>(mbEnums.MovementType.class);
|
||||
public int rune_group_type;
|
||||
public boolean rune_group_is_faction;
|
||||
public boolean rune_group_is_guild;
|
||||
public String rune_dsc;
|
||||
public String rune_fx_txt;
|
||||
|
||||
// Bit vectors. The existing ItemTemplate decodes these as MobBehaviourType.
|
||||
|
||||
public long rune_group_tactics;
|
||||
public long rune_group_role_set;
|
||||
public final EnumSet<mbEnums.MonsterType> rune_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||
public final ArrayList<Integer> rune_groupee_monster_types = new ArrayList<>();
|
||||
public final ArrayList<Integer> rune_helper_monster_types = new ArrayList<>();
|
||||
public final EnumSet<mbEnums.MonsterType> rune_not_enemy_monster_types = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||
|
||||
// Empty in every RUNE file; shape unknown.
|
||||
|
||||
public JSONArray rune_enemy_gender;
|
||||
public final ArrayList<SkillGrant> rune_skill_grant = new ArrayList<>();
|
||||
|
||||
// skill_type -> list of [value, value] pairs, in file order.
|
||||
|
||||
public final LinkedHashMap<String, ArrayList<int[]>> rune_skill_adj = new LinkedHashMap<>();
|
||||
public final EnumMap<mbEnums.AttributeType, Integer> rune_attr_adj = new EnumMap<>(mbEnums.AttributeType.class);
|
||||
public final EnumMap<mbEnums.AttributeType, Integer> rune_max_attr_adj = new EnumMap<>(mbEnums.AttributeType.class);
|
||||
|
||||
// One float[10] per natural attack. Same "attack info" struct as a weapon
|
||||
// (see MBDATA/docs/client-map.md): [0] attack speed, [1] range, [2] unknown,
|
||||
// [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_naturalattacks = new ArrayList<>();
|
||||
|
||||
// Empty in every RUNE file; shape unknown.
|
||||
|
||||
public JSONArray rune_enchantment_type;
|
||||
public final ArrayList<InventoryEntry> rune_inventory_contents = new ArrayList<>();
|
||||
public boolean rune_renderable;
|
||||
|
||||
// The following fields are only present when the rune is renderable
|
||||
// (missing from 315 of 2695 files). They keep their defaults otherwise.
|
||||
|
||||
public Vector3fImmutable rune_scale_factor;
|
||||
public int rune_skeleton;
|
||||
public boolean rune_slope_hugger;
|
||||
public boolean rune_can_fly;
|
||||
public int rune_death_effect;
|
||||
public int rune_tombstone_id;
|
||||
public final ArrayList<BodyPart> rune_body_parts = new ArrayList<>();
|
||||
public final ArrayList<Integer> rune_hair = new ArrayList<>();
|
||||
public final ArrayList<Integer> rune_beard = new ArrayList<>();
|
||||
|
||||
public int rune_natural_power_attack;
|
||||
public final HashMap<String, String> rune_sparse_data = new HashMap<>();
|
||||
|
||||
// One entry of rune_skill_grant.
|
||||
// skill_granted_skills entries look like [hash, value] pairs; not confirmed in the client.
|
||||
// skill_granted_attrs, skill_granted_powers and skill_monster_types are empty
|
||||
// in every RUNE file, so they are kept as raw JSON.
|
||||
|
||||
public static class SkillGrant {
|
||||
|
||||
public String skill_type;
|
||||
public int skill_value;
|
||||
public JSONArray skill_granted_attrs;
|
||||
public final ArrayList<int[]> skill_granted_skills = new ArrayList<>();
|
||||
public JSONArray skill_granted_powers;
|
||||
public JSONArray skill_monster_types;
|
||||
}
|
||||
|
||||
// One entry of rune_body_parts.
|
||||
|
||||
public static class BodyPart {
|
||||
|
||||
public int body_part_render;
|
||||
public Vector3fImmutable body_part_position;
|
||||
}
|
||||
|
||||
// One entry of rune_inventory_contents.
|
||||
// type is GOLD or BOOTYTABLE.
|
||||
// GOLD carries "gold": three floats (meaning not yet confirmed).
|
||||
// BOOTYTABLE carries "table": two numbers (meaning not yet confirmed).
|
||||
|
||||
public static class InventoryEntry {
|
||||
|
||||
public String type;
|
||||
public float[] values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
// One entry of item_user_power_action.
|
||||
// The same power can appear more than once on a single template,
|
||||
// so these are kept in a list rather than a map keyed by power.
|
||||
|
||||
public class UserPowerAction {
|
||||
|
||||
public String power;
|
||||
public int[] arguments;
|
||||
|
||||
public UserPowerAction(String power, int[] arguments) {
|
||||
this.power = power;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
// Parsers for the client's .wpak configuration files (powers, effects, etc).
|
||||
|
||||
package engine.parsers.wpak;
|
||||
@@ -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