Files
Server/src/engine/objects/Resists.java
T
MagicBotandClaude Opus 5.5 6b4e9815e0 Switch Item.template and MobLoot to CObjectTemplate
Item.template is now the cache template from CObjectTemplate.lookup
instead of the legacy DB ItemTemplate, so item.template.cacheTemplate.X
becomes item.template.X. MobLoot, LootManager, ItemManager.isTwoHanded/
isShield and CombatManager.getSwingAnimation take a CObjectTemplate.

No value changes: every getter already read the cache template through
the bridge, and the lookup resolves the same 7586 ids as
static_item_templates with the same item_type (offline parity p17.py,
0 differences).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:55:28 -04:00

471 lines
19 KiB
Java

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.objects;
import engine.gameManager.ChatManager;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.mbEnums.ModType;
import engine.mbEnums.SourceType;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public class Resists {
private static ConcurrentHashMap<Integer, Resists> mobResists = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private ConcurrentHashMap<mbEnums.DamageType, Float> resists = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private ConcurrentHashMap<mbEnums.DamageType, Boolean> immuneTo = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private mbEnums.DamageType protection;
private int protectionTrains = 0;
private boolean immuneToAll;
/**
* Generic Constructor
*/
public Resists(String type) {
switch (type) {
case "Building":
setBuildingResists();
break;
case "Mine":
setMineResists();
break;
default:
setGenericResists();
break;
}
}
public Resists(Resists r) {
for (mbEnums.DamageType dt : r.resists.keySet())
this.resists.put(dt, r.resists.get(dt));
for (mbEnums.DamageType dt : r.immuneTo.keySet())
this.immuneTo.put(dt, r.immuneTo.get(dt));
this.protection = r.protection;
this.protectionTrains = r.protectionTrains;
this.immuneToAll = r.immuneToAll;
}
/**
* Generic Constructor for player
*/
public Resists(PlayerCharacter pc) {
setGenericResists();
}
public Resists(Mob mob) {
setGenericResists();
}
/**
* Called for mobBase when getting from the db fails
*/
public Resists(MobBase mobBase) {
setGenericResists();
}
/**
* Database Constructor
*/
public Resists(ResultSet rs) throws SQLException {
this.immuneToAll = false;
this.resists.put(mbEnums.DamageType.SLASHING, rs.getFloat("slash"));
this.resists.put(mbEnums.DamageType.CRUSHING, rs.getFloat("crush"));
this.resists.put(mbEnums.DamageType.PIERCING, rs.getFloat("pierce"));
this.resists.put(mbEnums.DamageType.MAGIC, rs.getFloat("magic"));
this.resists.put(mbEnums.DamageType.BLEEDING, rs.getFloat("bleed"));
this.resists.put(mbEnums.DamageType.POISON, rs.getFloat("poison"));
this.resists.put(mbEnums.DamageType.MENTAL, rs.getFloat("mental"));
this.resists.put(mbEnums.DamageType.HOLY, rs.getFloat("holy"));
this.resists.put(mbEnums.DamageType.UNHOLY, rs.getFloat("unholy"));
this.resists.put(mbEnums.DamageType.LIGHTNING, rs.getFloat("lightning"));
this.resists.put(mbEnums.DamageType.FIRE, rs.getFloat("fire"));
this.resists.put(mbEnums.DamageType.COLD, rs.getFloat("cold"));
this.resists.put(mbEnums.DamageType.HEALING, 0f);
}
//Handle Fortitudes
private static float handleFortitude(AbstractCharacter target, mbEnums.DamageType type, float damage) {
if (target == null || !(target.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)))
return damage;
PlayerBonuses bonus = target.getBonuses();
//see if there is a fortitude
float damageCap = bonus.getFloatPercentAll(ModType.DamageCap, SourceType.None);
if (damageCap == 0f || type == mbEnums.DamageType.HEALING)
return damage;
//is fortitude, Are we under the cap?
float maxHealth = target.getHealthMax();
float capFire = maxHealth * (damageCap);
if (damage < capFire)
return damage;
//let's see if valid damagetype to apply it
boolean exclusive;
HashSet<SourceType> forts = bonus.getList(ModType.IgnoreDamageCap);
if (forts == null) {
exclusive = true;
forts = bonus.getList(ModType.ExclusiveDamageCap);
} else
exclusive = false;
if (forts == null || !isValidDamageCapType(forts, type, exclusive))
return damage;
float adjustedDamage = bonus.getFloatPercentAll(ModType.AdjustAboveDmgCap, SourceType.None);
//Adjust damage down and return new amount
float aadc = 1 + adjustedDamage;
return capFire * aadc;
}
//Test if Damagetype is valid for foritude
private static boolean isValidDamageCapType(HashSet<SourceType> forts, mbEnums.DamageType damageType, boolean exclusive) {
for (SourceType fort : forts) {
mbEnums.DamageType dt = mbEnums.DamageType.getDamageType(fort.name());
if (dt.equals(mbEnums.DamageType.NONE))
continue;
if (dt.equals(damageType)) {
return exclusive;
}
}
return !exclusive;
}
/**
* Calculate Current Resists for Player
*/
public static void calculateResists(AbstractCharacter ac) {
if (ac.getResists() != null)
ac.getResists().calculateResists(ac, true);
else
Logger.error("Unable to find resists for character " + ac.getObjectUUID());
}
private static float[] getArmorResists(Item armor, float[] phys) {
EnumMap<mbEnums.DamageType, Float> armorResists;
if (armor == null)
return phys;
if (armor.template.getItemType().equals(mbEnums.ItemType.ARMOR)) {
armorResists = armor.template.getCombatAttackResist();
phys[0] += armorResists.getOrDefault(mbEnums.DamageType.SLASHING, 0f);
phys[1] += armorResists.getOrDefault(mbEnums.DamageType.CRUSHING, 0f);
phys[2] += armorResists.getOrDefault(mbEnums.DamageType.PIERCING, 0f);
}
return phys;
}
/**
* Get mob resists from db if there, otherwise set defaults
*/
public static Resists getResists(int resistID) {
//check cache first
if (mobResists.containsKey(resistID))
return new Resists(mobResists.get(resistID));
//get from database
Resists resists = DbManager.ResistQueries.GET_RESISTS_FOR_MOB(resistID);
if (resists != null) {
mobResists.put(resistID, resists);
return new Resists(resists);
}
//failed, may want to debug this
return null;
}
/**
* Create generic resists for buildings
*/
public final void setBuildingResists() {
this.immuneToAll = false;
this.resists.put(mbEnums.DamageType.SLASHING, 85f);
this.resists.put(mbEnums.DamageType.CRUSHING, 85f);
this.resists.put(mbEnums.DamageType.SIEGE, 0f);
this.immuneTo.put(mbEnums.DamageType.PIERCING, true);
this.immuneTo.put(mbEnums.DamageType.MAGIC, true);
this.immuneTo.put(mbEnums.DamageType.BLEEDING, true);
this.immuneTo.put(mbEnums.DamageType.POISON, true);
this.immuneTo.put(mbEnums.DamageType.MENTAL, true);
this.immuneTo.put(mbEnums.DamageType.HOLY, true);
this.immuneTo.put(mbEnums.DamageType.UNHOLY, true);
this.immuneTo.put(mbEnums.DamageType.LIGHTNING, true);
this.immuneTo.put(mbEnums.DamageType.FIRE, true);
this.immuneTo.put(mbEnums.DamageType.COLD, true);
}
/**
* Create generic resists for mines
*/
public final void setMineResists() {
this.immuneToAll = false;
this.immuneTo.put(mbEnums.DamageType.SLASHING, true);
this.immuneTo.put(mbEnums.DamageType.CRUSHING, true);
this.immuneTo.put(mbEnums.DamageType.PIERCING, true);
this.immuneTo.put(mbEnums.DamageType.MAGIC, true);
this.immuneTo.put(mbEnums.DamageType.BLEEDING, true);
this.immuneTo.put(mbEnums.DamageType.POISON, true);
this.immuneTo.put(mbEnums.DamageType.MENTAL, true);
this.immuneTo.put(mbEnums.DamageType.HOLY, true);
this.immuneTo.put(mbEnums.DamageType.UNHOLY, true);
this.immuneTo.put(mbEnums.DamageType.LIGHTNING, true);
this.immuneTo.put(mbEnums.DamageType.FIRE, true);
this.immuneTo.put(mbEnums.DamageType.COLD, true);
this.resists.put(mbEnums.DamageType.SIEGE, 0f);
}
/**
* Create generic resists
*/
public final void setGenericResists() {
this.immuneToAll = false;
this.resists.put(mbEnums.DamageType.SLASHING, 0f);
this.resists.put(mbEnums.DamageType.CRUSHING, 0f);
this.resists.put(mbEnums.DamageType.PIERCING, 0f);
this.resists.put(mbEnums.DamageType.MAGIC, 0f);
this.resists.put(mbEnums.DamageType.BLEEDING, 0f);
this.resists.put(mbEnums.DamageType.POISON, 0f);
this.resists.put(mbEnums.DamageType.MENTAL, 0f);
this.resists.put(mbEnums.DamageType.HOLY, 0f);
this.resists.put(mbEnums.DamageType.UNHOLY, 0f);
this.resists.put(mbEnums.DamageType.LIGHTNING, 0f);
this.resists.put(mbEnums.DamageType.FIRE, 0f);
this.resists.put(mbEnums.DamageType.COLD, 0f);
this.resists.put(mbEnums.DamageType.HEALING, 0f);
this.immuneTo.put(mbEnums.DamageType.SIEGE, true);
}
/**
* Get a resist
*/
public float getResist(mbEnums.DamageType type, int trains) {
//get resisted amount
Float amount = 0f;
if (this.resists.containsKey(type))
amount = this.resists.get(type);
//add protection
if (trains > 0 && protection != null && type.equals(this.protection)) {
float prot = 50 + this.protectionTrains - trains;
amount += (prot >= 0) ? prot : 0;
}
if (amount == null)
return 0f;
if (amount > 75f)
return 75f;
return amount;
}
/**
* get immuneTo
*/
public boolean immuneTo(mbEnums.DamageType type) {
if (this.immuneTo.containsKey(type))
return this.immuneTo.get(type);
else
return false;
}
/**
* get immuneToAll
*/
public boolean immuneToAll() {
return this.immuneToAll;
}
public boolean immuneToAttacks() {
return immuneTo(mbEnums.DamageType.ATTACK);
}
/**
* Set a resist
*/
public void setResist(mbEnums.DamageType type, float value) {
this.resists.put(type, value);
}
/**
* set immuneToAll
*/
public void setImmuneToAll(boolean value) {
this.immuneToAll = value;
}
/**
* set resists from mobbase
*/
public void setMobResists(int resistID) {
//TODO add this in later
//calls `static_npc_mob_resists` table WHERE `ID`='resistID'
}
/**
* get Damage after resist
* Expects heals as negative damage and damage as positive damage for fortitudes.
*/
public float getResistedDamage(AbstractCharacter source, AbstractCharacter target, mbEnums.DamageType type, float damage, int trains) {
//handle fortitudes
damage = handleFortitude(target, type, damage);
//calculate armor piercing
float ap = source.getBonuses().getFloatPercentAll(ModType.ArmorPiercing, SourceType.None);
float damageAfterResists = damage * (1 - (this.getResist(type, trains) * 0.01f) + ap);
//check to see if any damage absorbers should cancel
if (target != null)
target.cancelOnTakeDamage(type, (damageAfterResists));
return damageAfterResists;
}
public void calculateResists(AbstractCharacter ac, boolean val) {
this.immuneTo.clear();
// get resists for runes
PlayerBonuses rb = ac.getBonuses();
float slash = 0f, crush = 0f, pierce = 0f, magic = 0f, bleed = 0f, mental = 0f, holy = 0f, unholy = 0f, poison = 0f, lightning = 0f, fire = 0f, cold = 0f, healing = 0f;
if (rb != null) {
// Handle immunities
if (rb.getBool(ModType.ImmuneTo, SourceType.Stun))
this.immuneTo.put(mbEnums.DamageType.STUN, true);
if (rb.getBool(ModType.ImmuneTo, SourceType.Blind))
this.immuneTo.put(mbEnums.DamageType.BLINDNESS, true);
if (rb.getBool(ModType.ImmuneToAttack, SourceType.None))
this.immuneTo.put(mbEnums.DamageType.ATTACK, true);
if (rb.getBool(ModType.ImmuneToPowers, SourceType.None))
this.immuneTo.put(mbEnums.DamageType.POWERS, true);
if (rb.getBool(ModType.ImmuneTo, SourceType.Powerblock))
this.immuneTo.put(mbEnums.DamageType.POWERINHIBITOR, true);
if (rb.getBool(ModType.ImmuneTo, SourceType.DeBuff))
this.immuneTo.put(mbEnums.DamageType.DEBUFF, true);
if (rb.getBool(ModType.ImmuneTo, SourceType.Fear))
this.immuneTo.put(mbEnums.DamageType.FEAR, true);
if (rb.getBool(ModType.ImmuneTo, SourceType.Charm))
this.immuneTo.put(mbEnums.DamageType.CHARM, true);
if (rb.getBool(ModType.ImmuneTo, SourceType.Root))
this.immuneTo.put(mbEnums.DamageType.ROOT, true);
if (rb.getBool(ModType.ImmuneTo, SourceType.Snare))
this.immuneTo.put(mbEnums.DamageType.SNARE, true);
// Handle resists
slash += rb.getFloat(ModType.Resistance, SourceType.Slashing);
crush += rb.getFloat(ModType.Resistance, SourceType.Crushing);
pierce += rb.getFloat(ModType.Resistance, SourceType.Piercing);
magic += rb.getFloat(ModType.Resistance, SourceType.Magic);
bleed += rb.getFloat(ModType.Resistance, SourceType.Bleeding);
poison += rb.getFloat(ModType.Resistance, SourceType.Poison);
mental += rb.getFloat(ModType.Resistance, SourceType.Mental);
holy += rb.getFloat(ModType.Resistance, SourceType.Holy);
unholy += rb.getFloat(ModType.Resistance, SourceType.Unholy);
lightning += rb.getFloat(ModType.Resistance, SourceType.Lightning);
fire += rb.getFloat(ModType.Resistance, SourceType.Fire);
cold += rb.getFloat(ModType.Resistance, SourceType.Cold);
healing += rb.getFloat(ModType.Resistance, SourceType.Healing); // DamageType.Healing.name());
}
// get resists from equipment
if (ac.getObjectType().equals(mbEnums.GameObjectType.PlayerCharacter)) {
if (ac.charItemManager != null && ac.charItemManager.getEquipped() != null) {
float[] phys = {0f, 0f, 0f};
ConcurrentHashMap<mbEnums.EquipSlotType, Item> equip = ac.charItemManager.getEquipped();
// get base physical resists
phys = Resists.getArmorResists(equip.get(mbEnums.EquipSlotType.HELM), phys);
phys = Resists.getArmorResists(equip.get(mbEnums.EquipSlotType.CHEST), phys);
phys = Resists.getArmorResists(equip.get(mbEnums.EquipSlotType.UPARM), phys);
phys = Resists.getArmorResists(equip.get(mbEnums.EquipSlotType.HANDS), phys);
phys = Resists.getArmorResists(equip.get(mbEnums.EquipSlotType.LEGS), phys);
phys = Resists.getArmorResists(equip.get(mbEnums.EquipSlotType.FEET), phys);
slash += phys[0];
crush += phys[1];
pierce += phys[2];
}
}
this.resists.put(mbEnums.DamageType.SLASHING, slash);
this.resists.put(mbEnums.DamageType.CRUSHING, crush);
this.resists.put(mbEnums.DamageType.PIERCING, pierce);
this.resists.put(mbEnums.DamageType.MAGIC, magic);
this.resists.put(mbEnums.DamageType.BLEEDING, bleed);
this.resists.put(mbEnums.DamageType.POISON, poison);
this.resists.put(mbEnums.DamageType.MENTAL, mental);
this.resists.put(mbEnums.DamageType.HOLY, holy);
this.resists.put(mbEnums.DamageType.UNHOLY, unholy);
this.resists.put(mbEnums.DamageType.LIGHTNING, lightning);
this.resists.put(mbEnums.DamageType.FIRE, fire);
this.resists.put(mbEnums.DamageType.COLD, cold);
this.resists.put(mbEnums.DamageType.HEALING, healing);
this.immuneTo.put(mbEnums.DamageType.SIEGE, true);
// debug printing of resists
// printResists(pc);
}
public void printResistsToClient(PlayerCharacter pc) {
for (mbEnums.DamageType dt : resists.keySet())
ChatManager.chatSystemInfo(pc, " resist." + dt.name() + ": " + resists.get(dt));
for (mbEnums.DamageType dt : immuneTo.keySet())
ChatManager.chatSystemInfo(pc, " immuneTo." + dt.name() + ": " + immuneTo.get(dt));
ChatManager.chatSystemInfo(pc, " immuneToAll: " + this.immuneToAll);
if (protection != null)
ChatManager.chatSystemInfo(pc, " Protection: " + protection.name() + ", Trains: " + protectionTrains);
else
ChatManager.chatSystemInfo(pc, " Protection: None");
}
public String getResists(PlayerCharacter pc) {
String out = pc.getName();
out += "Resists: ";
Iterator<mbEnums.DamageType> it = this.resists.keySet().iterator();
while (it.hasNext()) {
mbEnums.DamageType damType = it.next();
String dtName = damType.name();
out += dtName + '=' + this.resists.get(dtName) + ", ";
}
out += "ImmuneTo: ";
it = this.immuneTo.keySet().iterator();
while (it.hasNext()) {
mbEnums.DamageType damType = it.next();
String dtName = damType.name();
out += dtName + '=' + this.resists.get(dtName) + ", ";
}
if (protection != null)
out += "Protection: " + protection.name() + ", Trains: " + protectionTrains;
else
out += "Protection: none";
return out;
}
}