Public Repository for the Magicbane Shadowbane Emulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2442 lines
79 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.objects;
import engine.Enum;
import engine.Enum.*;
import engine.InterestManagement.InterestManager;
import engine.InterestManagement.Terrain;
import engine.InterestManagement.WorldGrid;
import engine.exception.SerializationException;
import engine.gameManager.*;
import engine.job.AbstractJob;
import engine.job.JobContainer;
import engine.job.JobScheduler;
import engine.jobs.ChantJob;
import engine.jobs.PersistentAoeJob;
import engine.jobs.TrackJob;
import engine.math.AtomicFloat;
import engine.math.Bounds;
import engine.math.Vector3fImmutable;
import engine.net.ByteBufferWriter;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.client.ClientConnection;
import engine.net.client.msg.ApplyRuneMsg;
import engine.net.client.msg.UpdateStateMsg;
import engine.powers.EffectsBase;
import engine.powers.PowersBase;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public abstract class AbstractCharacter extends AbstractWorldObject {
public CharacterItemManager charItemManager = new CharacterItemManager(this);
private final ReentrantReadWriteLock healthLock = new ReentrantReadWriteLock();
public short level;
public AbstractWorldObject combatTarget;
public int contractUUID;
public Contract contract;
public String firstName;
public String lastName;
protected short statStrCurrent;
protected short statDexCurrent;
protected short statConCurrent;
protected short statIntCurrent;
protected short statSpiCurrent;
protected short unusedStatPoints;
protected int exp;
public int buildingUUID;
public Building building;
public Vector3fImmutable bindLoc;
protected Vector3fImmutable faceDir;
public int guildUUID;
public Guild guild;
protected byte runningTrains;
protected ConcurrentHashMap<Integer, CharacterPower> powers;
public ConcurrentHashMap<String, CharacterSkill> skills;
// Variables NOT to be stored in db
protected boolean sit = false;
protected boolean walkMode;
protected boolean combat = false;
public Vector3fImmutable endLoc = Vector3fImmutable.ZERO;
protected boolean itemCasting = false;
// nextEndLoc is used to store the next end location when someone is clicking
// around the ground while other timers like changeAltitude are still
// ticking down so that mobs/players following dont just move away to your projected location
protected Vector3fImmutable nextEndLoc = Vector3fImmutable.ZERO;
protected float speed;
protected AtomicFloat stamina = new AtomicFloat();
protected float staminaMax;
protected AtomicFloat mana = new AtomicFloat();
protected float manaMax; // Health/Mana/Stamina
protected AtomicBoolean isAlive = new AtomicBoolean(true);
public Resists resists = new Resists("Genric");
protected ConcurrentHashMap<String, JobContainer> timers;
protected ConcurrentHashMap<String, Long> timestamps;
public int atrHandOne;
public int atrHandTwo;
public int minDamageHandOne;
public int maxDamageHandOne;
public int minDamageHandTwo;
public int maxDamageHandTwo;
public float rangeHandOne;
public float rangeHandTwo;
public float speedHandOne;
public float speedHandTwo;
public int defenseRating;
protected boolean isActive; // <-Do not use this for deleting character!
protected float altitude = 0; // 0=on terrain, 1=tier 1, 2=tier 2, etc.
protected ConcurrentHashMap<Integer, JobContainer> recycleTimers;
protected PlayerBonuses bonuses;
protected JobContainer lastChant;
protected boolean isCasting = false;
protected long lastSetLocUpdate = 0L;
protected int inBuilding = -1; // -1 not in building 0 on ground floor, 1 on first floor etc
protected int inBuildingID = 0;
protected int inFloorID = -1;
protected int liveCounter = 0;
protected int debug = 0;
protected boolean movingUp = false;
private float desiredAltitude = 0;
private long takeOffTime = 0;
private long lastHateUpdate = 0;
private byte aoecntr = 0;
public int hidden = 0; // current rank of hide/sneak/invis
public CopyOnWriteArrayList<Integer> minions = new CopyOnWriteArrayList();
public ArrayList<CharacterRune> runes;
public Enum.MonsterType absRace;
public ClassType absBaseClass = null;
public ClassType absPromotionClass = null;
public Enum.SexType absGender = null;
public EnumSet<DisciplineType> absDisciplines;
// Modifiers
public short statStrBase;
public short statDexBase;
public short statConBase;
public short statIntBase;
public short statSpiBase;
public Race race;
public BaseClass baseClass;
public PromotionClass promotionClass;
public AbstractCharacter() {
super();
this.firstName = "";
this.lastName = "";
this.statStrCurrent = (short) 0;
this.statDexCurrent = (short) 0;
this.statConCurrent = (short) 0;
this.statIntCurrent = (short) 0;
this.statSpiCurrent = (short) 0;
this.unusedStatPoints = (short) 0;
this.level = (short) 0; // TODO get this from MobsBase later
this.exp = 1;
this.walkMode = true;
this.bindLoc = Vector3fImmutable.ZERO;
this.faceDir = Vector3fImmutable.ZERO;
this.runningTrains = (byte) 0;
this.skills = new ConcurrentHashMap<>();
this.powers = new ConcurrentHashMap<>();
this.initializeCharacter();
}
/**
* No Id Constructor
*/
public AbstractCharacter(
final String firstName,
final String lastName,
final short statStrCurrent,
final short statDexCurrent,
final short statConCurrent,
final short statIntCurrent,
final short statSpiCurrent,
final short level,
final int exp,
final Vector3fImmutable bindLoc,
final Vector3fImmutable faceDir,
final Guild guild,
final byte runningTrains
) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.statStrCurrent = statStrCurrent;
this.statDexCurrent = statDexCurrent;
this.statConCurrent = statConCurrent;
this.statIntCurrent = statIntCurrent;
this.statSpiCurrent = statSpiCurrent;
this.level = level;
this.exp = exp;
this.walkMode = true;
this.bindLoc = bindLoc;
this.faceDir = faceDir;
this.guild = guild;
this.runningTrains = runningTrains;
this.powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
this.skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
this.initializeCharacter();
}
/**
* Normal Constructor
*/
public AbstractCharacter(
final String firstName,
final String lastName,
final short statStrCurrent,
final short statDexCurrent,
final short statConCurrent,
final short statIntCurrent,
final short statSpiCurrent,
final short level,
final int exp,
final Vector3fImmutable bindLoc,
final Vector3fImmutable currentLoc,
final Vector3fImmutable faceDir,
final Guild guild,
final byte runningTrains,
final int newUUID
) {
super(newUUID);
this.firstName = firstName;
this.lastName = lastName;
this.statStrCurrent = statStrCurrent;
this.statDexCurrent = statDexCurrent;
this.statConCurrent = statConCurrent;
this.statIntCurrent = statIntCurrent;
this.statSpiCurrent = statSpiCurrent;
this.level = level;
this.exp = exp;
this.walkMode = true;
this.bindLoc = bindLoc;
this.faceDir = faceDir;
this.guild = guild;
this.runningTrains = runningTrains;
this.powers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
this.skills = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
this.initializeCharacter();
}
/**
* ResultSet Constructor for players
*/
public AbstractCharacter(
final ResultSet rs,
final boolean isPlayer
) throws SQLException {
super(rs);
this.firstName = rs.getString("char_firstname");
this.lastName = rs.getString("char_lastname");
this.level = 1;
this.exp = rs.getInt("char_experience");
this.walkMode = false;
this.bindLoc = new Vector3fImmutable(0f, 0f, 0f);
this.endLoc = Vector3fImmutable.ZERO;
this.faceDir = Vector3fImmutable.ZERO;
final int guildID = rs.getInt("GuildUID");
final Guild errantGuild = Guild.getErrantGuild();
if (guildID == errantGuild.getObjectUUID()) {
this.guild = errantGuild;
} else {
this.guild = Guild.getGuild(guildID);
if (this.guild == null) {
this.guild = Guild.getErrantGuild();
}
}
if (this.guild == null)
this.guild = errantGuild;
this.skills = new ConcurrentHashMap<>();
this.powers = new ConcurrentHashMap<>();
this.initializeCharacter();
}
/**
* ResultSet Constructor for NPC/Mobs
*/
public AbstractCharacter(final ResultSet rs) throws SQLException {
super(rs);
this.firstName = "";
this.lastName = "";
this.statStrCurrent = (short) 0;
this.statDexCurrent = (short) 0;
this.statConCurrent = (short) 0;
this.statIntCurrent = (short) 0;
this.statSpiCurrent = (short) 0;
this.unusedStatPoints = (short) 0;
this.level = (short) 0; // TODO get this from MobsBase later
this.exp = 1;
this.walkMode = true;
this.bindLoc = Vector3fImmutable.ZERO;
this.faceDir = Vector3fImmutable.ZERO;
this.runningTrains = (byte) 0;
this.skills = new ConcurrentHashMap<>();
this.powers = new ConcurrentHashMap<>();
initializeCharacter();
}
/**
* ResultSet Constructor for static Mobs
*/
public AbstractCharacter(final ResultSet rs, final int objectUUID) throws SQLException {
super(objectUUID);
this.firstName = "";
this.lastName = "";
this.statStrCurrent = (short) 0;
this.statDexCurrent = (short) 0;
this.statConCurrent = (short) 0;
this.statIntCurrent = (short) 0;
this.statSpiCurrent = (short) 0;
this.unusedStatPoints = (short) 0;
this.level = (short) 0; // TODO get this from MobsBase later
this.exp = 1;
this.walkMode = true;
this.bindLoc = new Vector3fImmutable(rs.getFloat("spawnX"), rs.getFloat("spawnY"), rs.getFloat("spawnZ"));
if (ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER))
this.setLoc(this.bindLoc);
this.endLoc = Vector3fImmutable.ZERO;
this.faceDir = Vector3fImmutable.ZERO;
final int guildID = rs.getInt("GuildID");
if (guildID == Guild.getErrantGuild().getObjectUUID()) {
this.guild = Guild.getErrantGuild();
} else {
this.guild = Guild.getGuild(guildID);
}
if (this.guild == null)
this.guild = Guild.getErrantGuild();
this.runningTrains = (byte) 0;
this.skills = new ConcurrentHashMap<>();
this.powers = new ConcurrentHashMap<>();
this.initializeCharacter();
}
public static int getBankCapacity() {
return 500;
}
public static int getVaultCapacity() {
return 5000;
}
public static void _serializeForClientMsg(AbstractCharacter abstractCharacter, final ByteBufferWriter writer) throws SerializationException {
AbstractCharacter.__serializeForClientMsg(abstractCharacter, writer);
}
public static void __serializeForClientMsg(AbstractCharacter abstractCharacter, final ByteBufferWriter writer) throws SerializationException {
}
public static void serializeForClientMsgOtherPlayer(AbstractCharacter abstractCharacter, final ByteBufferWriter writer, final boolean asciiLastName) throws SerializationException {
switch (abstractCharacter.getObjectType()) {
case PlayerCharacter:
PlayerCharacter.serializePlayerForClientMsgOtherPlayer((PlayerCharacter) abstractCharacter, writer, asciiLastName);
break;
case Mob:
Mob.serializeMobForClientMsgOtherPlayer((Mob) abstractCharacter, writer);
break;
case NPC:
NPC.serializeNpcForClientMsgOtherPlayer((NPC) abstractCharacter, writer, asciiLastName);
break;
}
//TODO INPUT SWITCH CASE ON GAME OBJECTS TO CALL SPECIFIC METHODS.
}
public static final void serializeForTrack(AbstractCharacter abstractCharacter, final ByteBufferWriter writer, boolean isGroup) {
writer.putInt(abstractCharacter.getObjectType().ordinal());
writer.putInt(abstractCharacter.getObjectUUID());
if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter)) {
writer.putString(abstractCharacter.getName());
} else {
writer.putString(abstractCharacter.getFirstName());
}
writer.put(isGroup ? (byte) 1 : (byte) 0);
if (abstractCharacter.guild != null) {
Guild.serializeForTrack(abstractCharacter.guild, writer);
} else {
Guild.serializeErrantForTrack(writer);
}
}
public static void runBonusesOnLoad(PlayerCharacter playerCharacter) {
// synchronized with getBonuses()
synchronized (playerCharacter.bonuses) {
try {
//run until no new bonuses are applied
// clear bonuses and reapply rune bonuses
if (playerCharacter.getObjectType() == GameObjectType.PlayerCharacter) {
playerCharacter.bonuses.calculateRuneBaseEffects(playerCharacter);
} else {
playerCharacter.bonuses.clearRuneBaseEffects();
}
// apply effect bonuses
for (Effect eff : playerCharacter.effects.values()) {
eff.applyBonus(playerCharacter);
}
//apply item bonuses for equipped items
ConcurrentHashMap<EquipSlotType, Item> equip = null;
if (playerCharacter.charItemManager != null)
equip = playerCharacter.charItemManager.getEquipped();
if (equip != null) {
for (Item item : equip.values()) {
item.clearBonuses();
if (item != null) {
ConcurrentHashMap<String, Effect> effects = item.getEffects();
if (effects != null) {
for (Effect eff : effects.values()) {
eff.applyBonus(item, playerCharacter);
}
}
}
}
}
//recalculate passive defenses
playerCharacter.setPassives();
//flip the active bonus set for synchronization purposes
//do this after all bonus updates, but before recalculations.
// recalculate everything
//calculate item bonuses
calculateItemBonuses(playerCharacter);
//recalculate formulas
PlayerCharacter.recalculatePlayerStatsOnLoad(playerCharacter);
} catch (Exception e) {
Logger.error(e);
}
}
}
public static Regions InsideBuildingRegionGoingDown(AbstractCharacter abstractCharacter) {
HashSet<AbstractWorldObject> buildings = WorldGrid.getObjectsInRangePartial(abstractCharacter, 1000, MBServerStatics.MASK_BUILDING);
Regions tempRegion = null;
for (AbstractWorldObject awo : buildings) {
Building building = (Building) awo;
if (building.getBounds() == null)
continue;
if (!Bounds.collide(abstractCharacter.getLoc(), building.getBounds()))
continue;
for (Regions region : building.getBounds().getRegions()) {
if (!region.isPointInPolygon(abstractCharacter.getLoc()))
continue;
if (!region.isOutside())
continue;
if (tempRegion == null)
tempRegion = region;
if (tempRegion.highLerp.y < region.highLerp.y)
tempRegion = region;
}
if (tempRegion != null)
break;
}
return tempRegion;
}
public static boolean CanFly(AbstractCharacter flyer) {
boolean canFly = false;
PlayerBonuses bonus = flyer.getBonuses();
if (bonus != null && !bonus.getBool(ModType.NoMod, SourceType.Fly) && bonus.getBool(ModType.Fly, SourceType.None) && flyer.isAlive())
canFly = true;
return canFly;
}
public static void teleport(AbstractCharacter abstractCharacter, final Vector3fImmutable targetLoc) {
abstractCharacter.locationLock.writeLock().lock();
try {
MovementManager.translocate(abstractCharacter, targetLoc);
if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter))
InterestManager.INTERESTMANAGER.HandleLoadForTeleport((PlayerCharacter) abstractCharacter);
} catch (Exception e) {
Logger.error(e);
} finally {
abstractCharacter.locationLock.writeLock().unlock();
}
}
public static void removeRune(PlayerCharacter playerCharacter, ClientConnection origin, int runeID) {
if (playerCharacter == null || origin == null) {
return;
}
//remove only if rune is discipline
if (runeID < 3001 || runeID > 3048) {
return;
}
//see if pc has rune
ArrayList<CharacterRune> runes = playerCharacter.getRunes();
if (runes == null)
return;
CharacterRune found = playerCharacter.getRune(runeID);
if (found == null)
return;
//TODO see if player needs to refine skills or powers first
//attempt remove rune from player
if (!CharacterRune.removeRune(playerCharacter, runeID))
return;
//update client with removed rune.
ApplyRuneMsg arm = new ApplyRuneMsg(playerCharacter.getObjectType().ordinal(), playerCharacter.getObjectUUID(), runeID);
Dispatch dispatch = Dispatch.borrow(playerCharacter, arm);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
}
public static void removeAllBlessings(PlayerCharacter playerCharacter) {
PowersBase[] powers = new PowersBase[3];
powers[0] = PowersManager.getPowerByIDString("BLS-POWER");
powers[1] = PowersManager.getPowerByIDString("BLS-FORTUNE");
powers[2] = PowersManager.getPowerByIDString("BLS-WISDOM");
for (PowersBase power : powers) {
PowersManager.removeEffect(playerCharacter, power.getActions().get(0), true, false);
}
}
8 months ago
public static void AssignDefenseValue(AbstractCharacter abstractCharacter) {
ConcurrentHashMap<EquipSlotType, Item> equipped = abstractCharacter.charItemManager.getEquipped();
if (abstractCharacter.effects != null && abstractCharacter.effects.containsKey("DeathShroud"))
abstractCharacter.defenseRating = (short) 0;
else {
// calculate defense for equipment
float defense = abstractCharacter.statDexCurrent * 2;
defense += getShieldDefense(abstractCharacter, equipped.get(EquipSlotType.LHELD));
defense += getArmorDefense(abstractCharacter, equipped.get(EquipSlotType.HELM));
defense += getArmorDefense(abstractCharacter, equipped.get(EquipSlotType.CHEST));
defense += getArmorDefense(abstractCharacter, equipped.get(EquipSlotType.UPARM));
defense += getArmorDefense(abstractCharacter, equipped.get(EquipSlotType.HANDS));
defense += getArmorDefense(abstractCharacter, equipped.get(EquipSlotType.LEGS));
defense += getArmorDefense(abstractCharacter, equipped.get(EquipSlotType.FEET));
defense += getWeaponDefense(abstractCharacter, equipped);
if (abstractCharacter.bonuses != null) {
// add any bonuses
defense += (short) abstractCharacter.bonuses.getFloat(ModType.DCV, SourceType.None);
// Finally multiply any percent modifiers. DO THIS LAST!
float pos_Bonus = abstractCharacter.bonuses.getFloatPercentPositive(ModType.DCV, SourceType.None);
defense = (short) (defense * (1 + pos_Bonus));
//Lucky rune applies next
//applied runes will be calculated and added to the normal bonuses. no need for this garbage anymore
//defense = (short) (defense * (1 + ((float) this.bonuses.getShort("rune.Defense") / 100)));
//and negative percent modifiers
//already done...
float neg_Bonus = abstractCharacter.bonuses.getFloatPercentNegative(ModType.DCV, SourceType.None);
defense = (short) (defense * (1 + neg_Bonus));
} else
// TODO add error log here
Logger.error("Error: missing bonuses");
defense = (defense < 1) ? 1 : defense;
abstractCharacter.defenseRating = (short) (defense + 0.5f);
}
}
8 months ago
/**
* @param abstractCharacter
* @ Calculates Atr (both hands) Defense, and Damage for pc
*/
public static void calculateAtrDefenseDamage(AbstractCharacter abstractCharacter) {
if (abstractCharacter.charItemManager == null || abstractCharacter.charItemManager.getEquipped() == null || abstractCharacter.skills == null) {
Logger.error("Player " + abstractCharacter.getObjectUUID() + " missing skills or equipment");
defaultAtrAndDamage(abstractCharacter, true);
defaultAtrAndDamage(abstractCharacter, false);
abstractCharacter.defenseRating = 0;
return;
}
AssignDefenseValue(abstractCharacter);
// calculate atr and damage for each hand
ConcurrentHashMap<EquipSlotType, Item> equipped = abstractCharacter.charItemManager.getEquipped();
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.RHELD), true, equipped.get(EquipSlotType.RHELD));
AssignDamageAtrForPlayers(abstractCharacter, equipped.get(EquipSlotType.LHELD), false, equipped.get(EquipSlotType.LHELD));
8 months ago
if (abstractCharacter.getObjectType().equals(GameObjectType.Mob)) {
Mob mob = (Mob) abstractCharacter;
8 months ago
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();
abstractCharacter.defenseRating += mob.mobBase.getDefenseRating();
}
}
/**
* @ Calculates Atr, and Damage for each weapon
*/
public static void AssignDamageAtrForPlayers(AbstractCharacter abstractCharacter, Item weapon, boolean mainHand, Item otherHand) {
// make sure weapon exists
boolean noWeapon = false;
ItemTemplate weaponTemplate = null;
if (weapon == null)
noWeapon = true;
else {
ItemTemplate itemTemplate = weapon.template;
if (itemTemplate == null)
noWeapon = true;
else if (!weapon.template.item_type.equals(ItemType.WEAPON)) {
defaultAtrAndDamage(abstractCharacter, mainHand);
return;
} else
weaponTemplate = itemTemplate;
}
float skillPercentage, masteryPercentage;
float mastDam;
int min, max;
float speed = 20f;
boolean strBased = false;
// get skill percentages and min and max damage for weapons
if (noWeapon) {
if (mainHand) {
Item off = abstractCharacter.charItemManager.getEquipped().get(EquipSlotType.LHELD);
if (off != null && off.template != null && off.template.item_type.equals(ItemType.WEAPON))
abstractCharacter.rangeHandOne = 10 * (1 + (abstractCharacter.statStrBase / 600)); // Set
// to
// no
// weapon
// range
else
abstractCharacter.rangeHandOne = -1; // set to do not attack
} else
abstractCharacter.rangeHandTwo = -1; // set to do not attack
skillPercentage = getModifiedAmount(abstractCharacter.skills.get("Unarmed Combat"));
masteryPercentage = getModifiedAmount(abstractCharacter.skills.get("Unarmed Combat Mastery"));
if (masteryPercentage == 0f)
mastDam = CharacterSkill.getQuickMastery(abstractCharacter, "Unarmed Combat Mastery");
else
mastDam = masteryPercentage;
// TODO Correct these
min = 1;
max = 3;
} else {
if (mainHand)
abstractCharacter.rangeHandOne = weapon.template.item_weapon_max_range * (1 + (abstractCharacter.statStrBase / 600));
else
abstractCharacter.rangeHandTwo = weapon.template.item_weapon_max_range * (1 + (abstractCharacter.statStrBase / 600));
if (abstractCharacter.bonuses != null) {
float range_bonus = 1 + abstractCharacter.bonuses.getFloatPercentAll(ModType.WeaponRange, SourceType.None);
if (mainHand)
abstractCharacter.rangeHandOne *= range_bonus;
else
abstractCharacter.rangeHandTwo *= range_bonus;
}
skillPercentage = getModifiedAmount(abstractCharacter.skills.get(weapon.template.item_skill_used));
masteryPercentage = getModifiedAmount(abstractCharacter.skills.get(weaponTemplate.item_skill_mastery_used));
if (masteryPercentage == 0f)
mastDam = 0f;
// mastDam = CharacterSkill.getQuickMastery(this, wb.getMastery());
else
mastDam = masteryPercentage;
int[] damages = (int[]) weaponTemplate.item_weapon_damage.values().toArray()[0];
min = damages[0];
max = damages[1];
strBased = weaponTemplate.item_primary_attr.equals(AttributeType.Strength);
}
if (abstractCharacter.effects != null && abstractCharacter.effects.containsKey("DeathShroud"))
// No Atr in deathshroud.
if (mainHand)
abstractCharacter.atrHandOne = (short) 0;
else
abstractCharacter.atrHandTwo = (short) 0;
else {
// calculate atr
float atr = 0;
atr += (int) skillPercentage * 4f; //<-round down skill% -
atr += (int) masteryPercentage * 3f;
if (abstractCharacter.statStrCurrent > abstractCharacter.statDexCurrent)
atr += abstractCharacter.statStrCurrent / 2;
else
atr += abstractCharacter.statDexCurrent / 2;
// add in any bonuses to atr
if (abstractCharacter.bonuses != null) {
// Add any base bonuses
atr += abstractCharacter.bonuses.getFloat(ModType.OCV, SourceType.None);
// Finally use any multipliers. DO THIS LAST!
float pos_Bonus = (1 + abstractCharacter.bonuses.getFloatPercentPositive(ModType.OCV, SourceType.None));
atr *= pos_Bonus;
// next precise
//runes will have their own bonuses.
// atr *= (1 + ((float) this.bonuses.getShort("rune.Attack") / 100));
//and negative percent modifiers
float neg_Bonus = abstractCharacter.bonuses.getFloatPercentNegative(ModType.OCV, SourceType.None);
atr *= (1 + neg_Bonus);
}
atr = (atr < 1) ? 1 : atr;
// set atr
if (mainHand)
abstractCharacter.atrHandOne = (short) (atr + 0.5f);
else
abstractCharacter.atrHandTwo = (short) (atr + 0.5f);
}
//calculate speed
if (weaponTemplate != null)
speed = weaponTemplate.item_weapon_wepspeed;
else
speed = 20f; //unarmed attack speed
if (weapon != null)
speed *= (1 + abstractCharacter.bonuses.getFloatPercentAll(ModType.WeaponSpeed, SourceType.None));
speed *= (1 + abstractCharacter.bonuses.getFloatPercentAll(ModType.AttackDelay, SourceType.None));
if (speed < 10)
speed = 10;
//add min/max damage bonuses for weapon
if (weapon != null) {
// Add any base bonuses
min += weapon.getBonus(ModType.MinDamage, SourceType.None);
max += weapon.getBonus(ModType.MaxDamage, SourceType.None);
min += weapon.getBonus(ModType.MeleeDamageModifier, SourceType.None);
max += weapon.getBonus(ModType.MeleeDamageModifier, SourceType.None);
// Finally use any multipliers. DO THIS LAST!
float percentMinDamage = 1;
float percentMaxDamage = 1;
percentMinDamage += weapon.getBonusPercent(ModType.MinDamage, SourceType.None);
percentMinDamage += weapon.getBonusPercent(ModType.MeleeDamageModifier, SourceType.None);
percentMaxDamage += weapon.getBonusPercent(ModType.MaxDamage, SourceType.None);
percentMaxDamage += weapon.getBonusPercent(ModType.MeleeDamageModifier, SourceType.None);
min *= percentMinDamage;
max *= percentMaxDamage;
}
//if duel wielding, cut damage by 30%
if (otherHand != null) {
ItemTemplate ibo = otherHand.template;
if (ibo != null && otherHand.template.equals(ItemType.WEAPON)) {
min *= 0.7f;
max *= 0.7f;
}
}
// calculate damage
float minDamage;
float maxDamage;
float pri = (strBased) ? (float) abstractCharacter.statStrCurrent : (float) abstractCharacter.statDexCurrent;
float sec = (strBased) ? (float) abstractCharacter.statDexCurrent : (float) abstractCharacter.statStrCurrent;
minDamage = (float) (min * ((0.0315f * Math.pow(pri, 0.75f)) + (0.042f * Math.pow(sec, 0.75f)) + (0.01f * ((int) skillPercentage + (int) mastDam))));
maxDamage = (float) (max * ((0.0785f * Math.pow(pri, 0.75f)) + (0.016f * Math.pow(sec, 0.75f)) + (0.0075f * ((int) skillPercentage + (int) mastDam))));
minDamage = (float) ((int) (minDamage + 0.5f)); //round to nearest decimal
maxDamage = (float) ((int) (maxDamage + 0.5f)); //round to nearest decimal
// Half damage if in death shroud
if (abstractCharacter.effects != null && abstractCharacter.effects.containsKey("DeathShroud")) {
minDamage *= 0.5f;
maxDamage *= 0.5f;
}
// add in any bonuses to damage
if (abstractCharacter.bonuses != null) {
// Add any base bonuses
minDamage += abstractCharacter.bonuses.getFloat(ModType.MinDamage, SourceType.None);
maxDamage += abstractCharacter.bonuses.getFloat(ModType.MaxDamage, SourceType.None);
minDamage += abstractCharacter.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None);
maxDamage += abstractCharacter.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None);
// Finally use any multipliers. DO THIS LAST!
float percentMinDamage = 1;
float percentMaxDamage = 1;
percentMinDamage += abstractCharacter.bonuses.getFloatPercentAll(ModType.MinDamage, SourceType.None);
percentMinDamage += abstractCharacter.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None);
percentMaxDamage += abstractCharacter.bonuses.getFloatPercentAll(ModType.MaxDamage, SourceType.None);
percentMaxDamage += abstractCharacter.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None);
minDamage *= percentMinDamage;
maxDamage *= percentMaxDamage;
}
// set damages
if (mainHand) {
abstractCharacter.minDamageHandOne = (int) minDamage;
abstractCharacter.maxDamageHandOne = (int) maxDamage;
abstractCharacter.speedHandOne = speed;
} else {
abstractCharacter.minDamageHandTwo = (int) minDamage;
abstractCharacter.maxDamageHandTwo = (int) maxDamage;
abstractCharacter.speedHandTwo = speed;
}
}
/**
* @ Calculates Defense for shield
*/
private static float getShieldDefense(AbstractCharacter abstractCharacter, Item shield) {
if (shield == null)
return 0;
if (ItemManager.isShield(shield) == false)
return 0;
ItemTemplate template = shield.template;
if (template == null)
return 0;
CharacterSkill blockSkill = abstractCharacter.skills.get("Block");
float skillMod;
if (blockSkill == null) {
skillMod = 0;
} else
skillMod = blockSkill.getModifiedAmount();
float def = template.item_defense_rating;
//apply item defense bonuses
if (shield != null) {
def += shield.getBonus(ModType.DR, SourceType.None);
def *= (1 + shield.getBonusPercent(ModType.DR, SourceType.None));
}
// float val = ((float)ab.getDefense()) * (1 + (skillMod / 100));
return (def * (1 + ((int) skillMod / 100f)));
}
/**
* @ Calculates Defense for armor
*/
private static float getArmorDefense(AbstractCharacter abstractCharacter, Item armor) {
if (armor == null)
return 0;
ItemTemplate template = armor.template;
if (template == null)
return 0;
if (!armor.template.item_type.equals(ItemType.ARMOR))
return 0;
if (armor.template.item_skill_used.isEmpty())
return template.item_defense_rating;
CharacterSkill armorSkill = abstractCharacter.skills.get(armor.template.item_skill_used);
if (abstractCharacter.getObjectType().equals(GameObjectType.PlayerCharacter) && armorSkill == null) {
Logger.error("Player " + abstractCharacter.getObjectUUID()
+ " has armor equipped without the nescessary skill to equip it");
return template.item_defense_rating;
}
if (armorSkill == null)
return template.item_defense_rating; // Mobiles do not have armor skills @TODO
float def = template.item_defense_rating;
//apply item defense bonuses
if (armor != null) {
def += armor.getBonus(ModType.DR, SourceType.None);
def *= (1 + armor.getBonusPercent(ModType.DR, SourceType.None));
}
return (def * (1 + ((int) armorSkill.getModifiedAmount() / 50f)));
}
/**
* @ Calculates Defense for weapon
*/
private static float getWeaponDefense(AbstractCharacter abstractCharacter, ConcurrentHashMap<EquipSlotType, Item> equipped) {
Item weapon = equipped.get(EquipSlotType.RHELD);
ItemTemplate weaponTemplate = null;
CharacterSkill skill, mastery;
float val = 0;
boolean unarmed = false;
if (weapon == null) {
weapon = equipped.get(EquipSlotType.LHELD);
if (weapon == null || ItemManager.isShield(weapon))
unarmed = true;
else
weaponTemplate = weapon.template;
} else
weaponTemplate = weapon.template;
if (weaponTemplate == null)
unarmed = true;
if (unarmed) {
skill = abstractCharacter.skills.get("Unarmed Combat");
mastery = abstractCharacter.skills.get("Unarmed Combat Mastery");
} else {
skill = abstractCharacter.skills.get(weapon.template.item_skill_used);
mastery = abstractCharacter.skills.get(weaponTemplate.item_skill_mastery_used);
}
if (skill != null)
val += (int) skill.getModifiedAmount() / 2f;
if (mastery != null)
val += (int) mastery.getModifiedAmount() / 2f;
return val;
}
//Don't call this function directly. linked from pc.calculateSkills()
//through SkillCalcJob. Designed to only run from one worker thread
public static void runSkillCalc(AbstractCharacter abstractCharacter) {
8 months ago
//see if any new skills or powers granted
CharacterSkill.calculateSkills(abstractCharacter);
// calculate granted Trains in powers.
CharacterPower.grantTrains(abstractCharacter);
//see if any new powers unlocked from previous check
CharacterPower.calculatePowers(abstractCharacter);
}
//calculate item bonuses here
public static void calculateItemBonuses(AbstractCharacter abstractCharacter) {
if (abstractCharacter.charItemManager == null || abstractCharacter.bonuses == null)
return;
ConcurrentHashMap<EquipSlotType, Item> equipped = abstractCharacter.charItemManager.getEquipped();
for (Item item : equipped.values()) {
ItemTemplate ib = item.template;
if (ib == null)
continue;
//TODO add effect bonuses in here for equipped items
}
}
/**
* @ Defaults ATR, Defense and Damage for player
*/
private static void defaultAtrAndDamage(AbstractCharacter abstractCharacter, boolean mainHand) {
if (mainHand) {
abstractCharacter.atrHandOne = 0;
abstractCharacter.minDamageHandOne = 0;
abstractCharacter.maxDamageHandOne = 0;
abstractCharacter.rangeHandOne = -1;
abstractCharacter.speedHandOne = 20;
} else {
abstractCharacter.atrHandTwo = 0;
abstractCharacter.minDamageHandTwo = 0;
abstractCharacter.maxDamageHandTwo = 0;
abstractCharacter.rangeHandTwo = -1;
abstractCharacter.speedHandTwo = 20;
}
}
private static float getModifiedAmount(CharacterSkill skill) {
if (skill == null)
return 0f;
return skill.getModifiedAmount();
}
private void initializeCharacter() {
this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
this.timestamps = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
final long l = System.currentTimeMillis();
this.timestamps.put("Health Recovery", l);
this.timestamps.put("Stamina Recovery", l);
this.timestamps.put("Mana Recovery", l);
this.recycleTimers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
}
protected abstract ConcurrentHashMap<Integer, CharacterPower> initializePowers();
public final void addPersistantAoe(
final String name,
final int duration,
final PersistentAoeJob asj,
final EffectsBase eb,
final int trains
) {
if (!isAlive()) {
return;
}
final JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration);
final Effect eff = new Effect(jc, eb, trains);
aoecntr++;
this.effects.put(name + aoecntr, eff);
eff.setPAOE();
}
public final void setLastChant(
final int duration,
final ChantJob cj
) {
if (!isAlive()) {
return;
}
if (this.lastChant != null) {
this.lastChant.cancelJob();
}
this.lastChant = JobScheduler.getInstance().scheduleJob(cj, duration);
}
public final void cancelLastChant() {
if (this.lastChant != null) {
this.lastChant.cancelJob();
this.lastChant = null;
}
}
public final void cancelLastChantIfSame(final Effect eff) {
if (eff == null || this.lastChant == null) {
return;
}
final AbstractJob aj = this.lastChant.getJob();
if (aj == null || (!(aj instanceof ChantJob))) {
return;
}
final int token = ((ChantJob) aj).getPowerToken();
if (eff.getPowerToken() == token && token != 0) {
this.cancelLastChant();
}
}
/*
* Getters
*/
public final short getUnusedStatPoints() {
return this.unusedStatPoints;
}
8 months ago
@Override
public String getName() {
if (this.firstName.length() == 0 && this.lastName.length() == 0) {
return "Unnamed " + '(' + this.getObjectUUID() + ')';
} else if (this.lastName.length() == 0) {
return this.getFirstName();
} else {
return this.getFirstName() + ' ' + this.getLastName();
}
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(final String name) {
this.firstName = name;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(final String name) {
this.lastName = name;
}
public final short getStatStrCurrent() {
return this.statStrCurrent;
}
public final short getStatDexCurrent() {
return this.statDexCurrent;
}
public final short getStatConCurrent() {
return this.statConCurrent;
}
public final short getStatIntCurrent() {
return this.statIntCurrent;
}
public final short getStatSpiCurrent() {
return this.statSpiCurrent;
}
public short getLevel() {
return this.level;
}
public void setLevel(final short value) {
this.level = value;
}
public final boolean isActive() {
return this.isActive;
}
public final void setActive(final boolean value) {
this.isActive = value;
}
public final Resists getResists() {
if (this.resists == null)
return Resists.getResists(0);
return this.resists;
}
public final void setResists(final Resists value) {
this.resists = value;
}
public final int getExp() {
return this.exp;
}
public final JobContainer getLastPower() {
if (this.timers == null) {
return null;
}
return this.timers.get("LastPower");
}
public final void setLastPower(final JobContainer jc) {
if (this.timers != null) {
this.timers.put("LastPower", jc);
}
}
public final void clearLastPower() {
if (this.timers != null) {
this.timers.remove("LastPower");
}
}
public final JobContainer getLastItem() {
if (this.timers == null) {
return null;
}
return this.timers.get("LastItem");
}
public final int getIsSittingAsInt() {
if (!this.isAlive()) {
return 1;
}
if (this.sit) {
return 4;
} else {
if (this.isMoving())
return 7;
else
return 5;
}
}
public final int getIsWalkingAsInt() {
if (this.walkMode) {
return 1;
}
return 2;
}
public final int getIsCombatAsInt() {
if (this.combat) {
return 2;
}
return 1;
}
public final int getIsFlightAsInt() {
if (this.altitude > 0) {
return 3;
}
if (this.getObjectType().equals(GameObjectType.PlayerCharacter))
if (((PlayerCharacter) this).isLastSwimming())
return 1; //swimming
return 2; //ground
}
public final void clearTimer(final String name) {
if (this.timers != null) {
this.timers.remove(name);
}
}
public abstract Vector3fImmutable getBindLoc();
public final void setBindLoc(final Vector3fImmutable value) {
this.bindLoc = value;
}
public final Vector3fImmutable getFaceDir() {
return this.faceDir;
}
public final void setFaceDir(final Vector3fImmutable value) {
this.faceDir = value;
}
public final Vector3fImmutable getEndLoc() {
return this.endLoc;
}
public final void setEndLoc(final Vector3fImmutable value) {
if (value.x > MBServerStatics.MAX_PLAYER_X_LOC)
return;
if (value.z < MBServerStatics.MAX_PLAYER_Y_LOC)
return;
this.endLoc = value;
// reset the location timer so our next call to getLoc is correct
this.resetLastSetLocUpdate();
}
public final Vector3fImmutable getNextEndLoc() {
// this is only used when users are changing their end
// location while a timer like changeAltitude is ticking down
return this.nextEndLoc;
}
public final void stopMovement(Vector3fImmutable stopLoc) {
locationLock.writeLock().lock();
try {
this.setLoc(stopLoc);
this.endLoc = Vector3fImmutable.ZERO;
this.resetLastSetLocUpdate();
} catch (Exception e) {
Logger.error(e);
} finally {
locationLock.writeLock().unlock();
}
}
public final boolean isMoving() {
// I might be on my way but my movement is paused
// due to a flight alt change
//TODO who the fuck wrote changeHeightJob. FIX THIS.
if (this.endLoc.equals(Vector3fImmutable.ZERO) || this.endLoc.equals(this.bindLoc))
return false;
if (this.takeOffTime != 0)
return false;
if (this.isCasting && this.getObjectType().equals(GameObjectType.PlayerCharacter))
return false;
return true;
}
public final boolean useFlyMoveRegen() {
if (this.endLoc.x != 0 && this.endLoc.z != 0)
return true;
return false;
}
public boolean asciiLastName() {
return true;
}
public final ConcurrentHashMap<String, CharacterSkill> getSkills() {
return this.skills;
}
public final ConcurrentHashMap<Integer, CharacterPower> getPowers() {
return this.powers;
}
public final int getInBuilding() {
return this.inBuilding;
}
public final void setInBuilding(final int floor) {
this.inBuilding = floor;
}
public Guild getGuild() {
return this.guild;
}
/*
* Setters
*/
public void setGuild(final Guild value) {
this.guild = value;
}
public int getGuildUUID() {
return this.guild.getObjectUUID();
}
public final int getRank() {
return (this.level / 10);
}
public final int getAtrHandOne() {
return this.atrHandOne;
}
public final int getAtrHandTwo() {
return this.atrHandTwo;
}
public final int getMinDamageHandOne() {
return this.minDamageHandOne;
}
public final int getMaxDamageHandOne() {
return this.maxDamageHandOne;
}
public final int getMinDamageHandTwo() {
return this.minDamageHandTwo;
}
public final int getMaxDamageHandTwo() {
return this.maxDamageHandTwo;
}
public final int getDefenseRating() {
return this.defenseRating;
}
public final float getSpeedHandOne() {
return this.speedHandOne;
}
public final float getSpeedHandTwo() {
return this.speedHandTwo;
}
public final float getRange() {
if (this.getObjectType() == GameObjectType.Mob) {
Mob mob = (Mob) this;
if (mob.isSiege()) {
return 300;
}
float range = 8;
8 months ago
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;
}
// TODO Is this clamp from live?
if (range > 80)
range = 80;
return range;
}
if (this.rangeHandOne > this.rangeHandTwo)
return this.rangeHandOne;
return this.rangeHandTwo;
}
public abstract float getPassiveChance(
final String type,
final int attackerLevel,
final boolean fromCombat);
public abstract float getSpeed();
public final int getBankCapacityRemaining() {
return (AbstractCharacter.getBankCapacity() - this.charItemManager.getBankWeight());
}
public final int getVaultCapacityRemaining() {
return (AbstractCharacter.getVaultCapacity() - this.charItemManager.getVaultWeight());
}
public final ArrayList<Item> getInventory() {
return this.getInventory(false);
}
/*
* Utils
*/
public final ArrayList<Item> getInventory(final boolean getGold) {
if (this.charItemManager == null) {
return new ArrayList<>();
}
return this.charItemManager.getInventory(getGold);
}
@Override
public Vector3fImmutable getLoc() {
return super.getLoc();
}
@Override
public final void setLoc(final Vector3fImmutable value) {
Building building = BuildingManager.getBuildingAtLocation(this.loc);
Regions region = null;
8 months ago
if (building != null) {
//look for region in the building we are in
1 year ago
for (Regions regionCycle : building.getBounds().getRegions()) {
float regionHeight = regionCycle.highLerp.y - regionCycle.lowLerp.y;
8 months ago
if (regionHeight < 10)
1 year ago
regionHeight = 10;
1 year ago
if (regionCycle.isPointInPolygon(value) && Math.abs(regionCycle.highLerp.y - value.y) < regionHeight)
region = regionCycle;
1 year ago
}
}
float regionHeightOffset = 0;
8 months ago
if (region != null) {
this.region = region;
regionHeightOffset = region.lerpY(this);
this.inBuilding = region.level; // -1 not in building 0 on ground floor, 1 on first floor etc
this.inBuildingID = region.parentBuildingID;
this.inFloorID = region.room;
} else {
this.region = null;
this.inBuilding = -1;
this.inBuildingID = 0;
this.inFloorID = -1;
}
float terrainHeight = Terrain.getWorldHeight(value);
8 months ago
Vector3fImmutable finalLocation = new Vector3fImmutable(value.x, terrainHeight + regionHeightOffset, value.z);
super.setLoc(finalLocation); // set the location in the world
this.resetLastSetLocUpdate();
}
public Vector3fImmutable getMovementLoc() {
if (this.endLoc.equals(Vector3fImmutable.ZERO))
return super.getLoc();
if (this.takeOffTime != 0)
return super.getLoc();
return super.getLoc().moveTowards(this.endLoc, this.getSpeed() * ((System.currentTimeMillis() - lastSetLocUpdate) * .001f));
}
public final void setBindLoc(final float x, final float y, final float z) {
this.bindLoc = new Vector3fImmutable(x, y, z);
}
public final void resetLastSetLocUpdate() {
this.lastSetLocUpdate = System.currentTimeMillis();
}
public void setIsCasting(final boolean isCasting) {
this.isCasting = isCasting;
}
public final boolean isCasting() {
return this.isCasting;
}
@Override
public final boolean isAlive() {
return this.isAlive.get();
}
public final boolean isSafeMode() {
if (this.resists == null)
return false;
for (Effect eff : this.getEffects().values()) {
if (eff.getEffectToken() == -1661750486)
return true;
}
return this.resists.immuneToAll();
}
public abstract void killCharacter(final AbstractCharacter killer);
public abstract void killCharacter(final String reason);
/**
* Determines if the character is in a lootable state.
*
* @return True if lootable.
*/
public abstract boolean canBeLooted();
public float calcHitBox() {
if (this.getObjectType() == GameObjectType.PlayerCharacter) {
// hit box radius is str/100 (gets diameter of hitbox) /2 (as we want a radius)
// note this formula is guesswork
if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) {
Logger.info("Hit box radius for " + this.getFirstName() + " is " + (this.statStrCurrent / 200f));
}
return ((PlayerCharacter) this).getStrForClient() / 200f;
//TODO CALCULATE MOB HITBOX BECAUSE FAIL EMU IS FAIL!!!!!!!
} else if (this.getObjectType() == GameObjectType.Mob) {
if (MBServerStatics.COMBAT_TARGET_HITBOX_DEBUG) {
Logger.info("Hit box radius for " + this.getFirstName() + " is " + ((Mob) this).getMobBase().getHitBoxRadius());
}
return ((Mob) this).getMobBase().getHitBoxRadius();
}
return 0f;
}
public final boolean isSit() {
return this.sit;
}
public final void setSit(final boolean value) {
if (this.sit != value) {
// change sit/stand and sync location
this.sit = value;
if (value == true) // we have been told to sit
{
this.stopMovement(this.getLoc());
}
}
}
public final boolean isWalk() {
return this.walkMode;
}
public final boolean isCombat() {
return this.combat;
}
public final void setCombat(final boolean value) {
this.combat = value;
}
public final void setWalkMode(final boolean value) {
// sync movement location as getLoc gets where we are at the exact moment in time (i.e. not the last updated loc)
this.setLoc(this.getLoc());
if (this.walkMode == value) {
return;
} else {
this.walkMode = value;
}
}
public final AbstractWorldObject getCombatTarget() {
return this.combatTarget;
}
public final void setCombatTarget(final AbstractWorldObject value) {
8 months ago
if (this.getObjectTypeMask() == 2050) {//MOB?
if (value == null) {
if (this.isCombat()) {
this.setCombat(false);
UpdateStateMsg rwss = new UpdateStateMsg();
rwss.setPlayer(this);
DispatchMessage.sendToAllInRange(this, rwss);
}
8 months ago
} else {
if (!this.isCombat()) {
this.setCombat(true);
UpdateStateMsg rwss = new UpdateStateMsg();
rwss.setPlayer(this);
DispatchMessage.sendToAllInRange(this, rwss);
}
}
}
this.combatTarget = value;
}
public final ConcurrentHashMap<String, JobContainer> getTimers() {
if (this.timers == null) {
this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
}
return this.timers;
}
public final int getLiveCounter() {
return this.liveCounter;
}
public final void addTimer(
final String name,
final AbstractJob asj,
final int duration
) {
final JobContainer jc = JobScheduler.getInstance().scheduleJob(asj, duration);
if (this.timers == null) {
this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
}
this.timers.put(name, jc);
}
public final void renewTimer(
final String name,
final AbstractJob asj,
final int duration
) {
this.cancelTimer(name);
this.addTimer(name, asj, duration);
}
public final ConcurrentHashMap<Integer, JobContainer> getRecycleTimers() {
return this.recycleTimers;
}
public final ConcurrentHashMap<String, Long> getTimestamps() {
return this.timestamps;
}
public final long getTimeStamp(final String name) {
if (this.timestamps.containsKey(name)) {
return this.timestamps.get(name);
}
return 0L;
}
public final void setTimeStamp(final String name, final long value) {
this.timestamps.put(name, value);
}
public final void setTimeStampNow(final String name) {
this.timestamps.put(name, System.currentTimeMillis());
}
public final void cancelTimer(final String name) {
cancelTimer(name, true);
}
public final void cancelTimer(final String name, final boolean jobRunning) {
if (this.timers == null) {
this.timers = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
}
if (this.timers.containsKey(name)) {
if (jobRunning) {
this.timers.get(name).cancelJob();
}
this.timers.remove(name);
}
}
public final float modifyHealth(
final float value,
final AbstractCharacter attacker,
final boolean fromCost) {
try {
try {
boolean ready = this.healthLock.writeLock().tryLock(1, TimeUnit.SECONDS);
while (!ready)
ready = this.healthLock.writeLock().tryLock(1, TimeUnit.SECONDS);
if (!this.isAlive())
return 0;
Float oldHealth, newHealth;
if (!this.isAlive())
return 0f;
oldHealth = this.health.get();
newHealth = oldHealth + value;
if (newHealth > this.healthMax)
newHealth = healthMax;
this.health.set(newHealth);
if (newHealth <= 0) {
if (this.isAlive.compareAndSet(true, false)) {
killCharacter(attacker);
return newHealth - oldHealth;
} else
return 0f; //already dead, don't send damage again
} // past this lock!
//TODO why is Handle REtaliate and cancelontakedamage in modifyHealth? shouldnt this be outside this method?
if (value < 0f && !fromCost) {
this.cancelOnTakeDamage();
CombatManager.handleRetaliate(this, attacker);
}
8 months ago
if (this.getObjectType().equals(GameObjectType.Mob)) {
//handle hate value addition
8 months ago
Mob target = (Mob) this;
if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) {
target.playerAgroMap.put(attacker.getObjectUUID(), target.playerAgroMap.get(attacker.getObjectUUID()) + value);
8 months ago
if (target.isPlayerGuard()) {
if (target.guardedCity != null && target.guardedCity.cityOutlaws.contains(attacker.getObjectUUID()) == false)
target.guardedCity.cityOutlaws.add(attacker.getObjectUUID());
}
}
8 months ago
} else if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
City playerCity = ZoneManager.getCityAtLocation(this.loc);
8 months ago
if (playerCity != null) {
if (!attacker.getGuild().getNation().equals(playerCity.getGuild().getNation()))
if (!playerCity.getGuild().getNation().getAllyList().contains(attacker.getGuild().getNation()))
if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()))
playerCity.cityOutlaws.add(attacker.getObjectUUID());
}
}
return newHealth - oldHealth;
} finally {
this.healthLock.writeLock().unlock();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return 0;
}
public float getCurrentHitpoints() {
return this.health.get();
}
public final float modifyMana(
final float value,
final AbstractCharacter attacker
) {
return this.modifyMana(value, attacker, false);
}
public final float modifyMana(
final float value,
final AbstractCharacter attacker,
final boolean fromCost
) {
if (!this.isAlive()) {
return 0f;
}
boolean worked = false;
Float oldMana = 0f, newMana = 0f;
while (!worked) {
oldMana = this.mana.get();
newMana = oldMana + value;
if (newMana > this.manaMax) {
newMana = manaMax;
} else if (newMana < 0) {
newMana = 0f;
}
worked = this.mana.compareAndSet(oldMana, newMana);
}
if (value < 0f && !fromCost) {
this.cancelOnTakeDamage();
CombatManager.handleRetaliate(this, attacker);
}
return newMana - oldMana;
}
/*
* Serializing
*/
public final float modifyStamina(
final float value,
final AbstractCharacter attacker
) {
return this.modifyStamina(value, attacker, false);
}
public final float modifyStamina(
final float value,
final AbstractCharacter attacker,
final boolean fromCost
) {
if (!this.isAlive()) {
return 0f;
}
boolean worked = false;
Float oldStamina = 0f, newStamina = 0f;
while (!worked) {
oldStamina = this.stamina.get();
newStamina = oldStamina + value;
if (newStamina > this.staminaMax) {
newStamina = staminaMax;
} else if (newStamina < 0) {
newStamina = 0f;
}
worked = this.stamina.compareAndSet(oldStamina, newStamina);
}
if (value < 0f && !fromCost) {
this.cancelOnTakeDamage();
CombatManager.handleRetaliate(this, attacker);
}
return newStamina - oldStamina;
}
public final float setMana(
final float value,
final AbstractCharacter attacker
) {
return setMana(value, attacker, false);
}
public final float setMana(
final float value,
final AbstractCharacter attacker,
final boolean fromCost
) {
if (!this.isAlive()) {
return 0f;
}
boolean worked = false;
Float oldMana = 0f, newMana = 0f;
while (!worked) {
oldMana = this.mana.get();
newMana = value;
if (newMana > this.manaMax) {
newMana = manaMax;
} else if (newMana < 0) {
newMana = 0f;
}
worked = this.mana.compareAndSet(oldMana, newMana);
}
if (oldMana > newMana && !fromCost) {
this.cancelOnTakeDamage();
CombatManager.handleRetaliate(this, attacker);
}
return newMana - oldMana;
}
public final float setStamina(
final float value,
final AbstractCharacter attacker
) {
return setStamina(value, attacker, false);
}
public final float setStamina(
final float value,
final AbstractCharacter attacker,
final boolean fromCost
) {
if (!this.isAlive()) {
return 0f;
}
boolean worked = false;
Float oldStamina = 0f, newStamina = 0f;
while (!worked) {
oldStamina = this.stamina.get();
newStamina = value;
if (newStamina > this.staminaMax) {
newStamina = staminaMax;
} else if (newStamina < 0) {
newStamina = 0f;
}
worked = this.stamina.compareAndSet(oldStamina, newStamina);
}
if (oldStamina > newStamina && !fromCost) {
this.cancelOnTakeDamage();
CombatManager.handleRetaliate(this, attacker);
}
return newStamina - oldStamina;
}
public final float getStamina() {
if (this.getObjectType() == GameObjectType.Mob)
return this.getStaminaMax();
return this.stamina.get();
}
public final float getMana() {
if (this.getObjectType() == GameObjectType.Mob)
return this.getManaMax();
return this.mana.get();
}
public final float getStaminaMax() {
if (this.getObjectType() == GameObjectType.Mob)
return 2000;
return this.staminaMax;
}
public final float getManaMax() {
if (this.getObjectType() == GameObjectType.Mob)
return 2000;
return this.manaMax;
}
public final PlayerBonuses getBonuses() {
return this.bonuses;
}
public void teleport(final Vector3fImmutable targetLoc) {
locationLock.writeLock().lock();
try {
MovementManager.translocate(this, targetLoc);
MovementManager.sendRWSSMsg(this);
} catch (Exception e) {
Logger.error(e);
} finally {
locationLock.writeLock().unlock();
}
}
/*
* Cancel effects upon actions
*/
public final void cancelOnAttack() { // added to one spot
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnAttack() && eff.cancel()) {
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnAttack(this);
}
public final void cancelOnAttackSwing() { // added
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnAttackSwing() && eff.cancel()) {
//System.out.println("canceling on AttackSwing");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnAttackSwing(this);
}
public final void cancelOnCast() {
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnCast() && eff.cancel()) {
// Don't cancel the track effect on the character being tracked
if (eff.getJob() != null && eff.getJob() instanceof TrackJob) {
if (((TrackJob) eff.getJob()).getSource().getObjectUUID()
== this.getObjectUUID()) {
continue;
}
}
//System.out.println("canceling on Cast");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnCast(this);
}
public final void cancelOnSpell() {
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnCastSpell() && eff.cancel()) {
//System.out.println("canceling on CastSpell");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnSpell(this);
}
public final void cancelOnMove() { // added
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnMove() && eff.cancel()) {
//System.out.println("canceling on Move");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnMove(this);
}
public final void cancelOnSit() { // added
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnSit() && eff.cancel()) {
//System.out.println("canceling on Sit");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnSit(this);
}
public final void cancelOnTakeDamage() {
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnTakeDamage() && eff.cancel()) {
//System.out.println("canceling on Take Damage");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnTakeDamage(this);
}
public final void cancelOnTakeDamage(final DamageType type, final float amount) {
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnTakeDamage(type, amount) && eff.cancel()) {
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
}
public final Effect getDamageAbsorber() {
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.isDamageAbsorber()) {
return eff;
}
}
return null;
}
public final void cancelOnUnEquip() {
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null)
continue;
if (eff.cancelOnUnEquip() && eff.cancel()) {
//System.out.println("canceling on UnEquip");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnUnEquip(this);
}
public final void cancelOnStun() {
boolean changed = false;
for (String s : this.effects.keySet()) {
Effect eff = this.effects.get(s);
if (eff == null) {
Logger.error("null effect for " + this.getObjectUUID() + " : effect " + s);
continue;
}
if (eff.cancelOnStun() && eff.cancel()) {
//System.out.println("canceling on Stun");
eff.cancelJob();
this.effects.remove(s);
changed = true;
}
}
if (changed) {
applyBonuses();
}
PowersManager.cancelOnStun(this);
}
//Call to apply any new effects to player
public synchronized void applyBonuses() {
PlayerCharacter player;
//tell the player to applyBonuses because something has changed
//start running the bonus calculations
try {
runBonuses();
// Check if calculations affected flight.
if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
player = (PlayerCharacter) this;
// Ground players who cannot fly but are currently flying
if (CanFly(player) == false &&
player.getMovementState().equals(MovementState.FLYING))
PlayerCharacter.GroundPlayer(player);
}
} catch (Exception e) {
Logger.error("Error in run bonuses for object UUID " + this.getObjectUUID());
Logger.error(e);
}
}
//Don't call this function directly. linked from ac.applyBonuses()
//through BonusCalcJob. Designed to only run from one worker thread
public final void runBonuses() {
// synchronized with getBonuses()
synchronized (this.bonuses) {
try {
//run until no new bonuses are applied
// clear bonuses and reapply rune bonuses
if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
this.bonuses.calculateRuneBaseEffects((PlayerCharacter) this);
} else {
this.bonuses.clearRuneBaseEffects();
}
// apply effect bonuses
for (Effect eff : this.effects.values()) {
eff.applyBonus(this);
}
//apply item bonuses for equipped items
ConcurrentHashMap<EquipSlotType, Item> equip = null;
if (this.charItemManager != null) {
equip = this.charItemManager.getEquipped();
}
if (equip != null) {
for (Item item : equip.values()) {
item.clearBonuses();
if (item != null) {
ConcurrentHashMap<String, Effect> effects = item.getEffects();
if (effects != null) {
for (Effect eff : effects.values()) {
eff.applyBonus(item, this);
}
}
}
}
}
//recalculate passive defenses
if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
((PlayerCharacter) this).setPassives();
}
// recalculate everything
if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
PlayerCharacter pc = (PlayerCharacter) this;
//calculate item bonuses
calculateItemBonuses(pc);
//recalculate formulas
pc.recalculatePlayerStats(true);
} else if (this.getObjectType().equals(GameObjectType.Mob)) {
Mob mob = (Mob) this;
//recalculate formulas
mob.recalculateStats();
}
} catch (Exception e) {
Logger.error(e);
}
}
}
public int getInBuildingID() {
return inBuildingID;
}
public void setInBuildingID(int inBuildingID) {
this.inBuildingID = inBuildingID;
}
public int getInFloorID() {
return inFloorID;
}
public void setInFloorID(int inFloorID) {
this.inFloorID = inFloorID;
}
public float getDesiredAltitude() {
return desiredAltitude;
}
public void setDesiredAltitude(float desiredAltitude) {
this.desiredAltitude = desiredAltitude;
}
public long getTakeOffTime() {
return takeOffTime;
}
public void setTakeOffTime(long takeOffTime) {
this.takeOffTime = takeOffTime;
}
public boolean isItemCasting() {
return itemCasting;
}
public void setItemCasting(boolean itemCasting) {
this.itemCasting = itemCasting;
}
//updates
public void update() {
}
public void updateRegen() {
}
public void updateMovementState() {
}
public void updateLocation() {
}
public void updateFlight() {
}
public void dynamicUpdate(UpdateType updateType) {
if (this.updateLock.writeLock().tryLock()) {
try {
switch (updateType) {
case ALL:
update();
break;
case REGEN:
updateRegen();
break;
case LOCATION:
update();
break;
case MOVEMENTSTATE:
update();
break;
case FLIGHT:
updateFlight();
break;
}
} catch (Exception e) {
Logger.error(e);
} finally {
this.updateLock.writeLock().unlock();
}
}
}
public boolean isMovingUp() {
return movingUp;
}
public void setMovingUp(boolean movingUp) {
this.movingUp = movingUp;
}
}