|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2022
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
|
|
|
|
package engine.objects;
|
|
|
|
|
|
|
|
import engine.InterestManagement.InterestManager;
|
|
|
|
import engine.InterestManagement.Terrain;
|
|
|
|
import engine.InterestManagement.WorldGrid;
|
|
|
|
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.mbEnums;
|
|
|
|
import engine.mbEnums.*;
|
|
|
|
import engine.net.ByteBufferWriter;
|
|
|
|
import engine.net.Dispatch;
|
|
|
|
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.powers.effectmodifiers.AbstractEffectModifier;
|
|
|
|
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("Default");
|
|
|
|
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 byte aoecntr = 0;
|
|
|
|
|
|
|
|
public int hidden = 0; // current rank of hide/sneak/invis
|
|
|
|
public CopyOnWriteArrayList<Integer> minions = new CopyOnWriteArrayList();
|
|
|
|
|
|
|
|
public ArrayList<CharacterRune> runes;
|
|
|
|
|
|
|
|
|
|
|
|
public mbEnums.MonsterType absRace;
|
|
|
|
public ClassType absBaseClass = null;
|
|
|
|
public ClassType absPromotionClass = null;
|
|
|
|
public mbEnums.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(mbEnums.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) {
|
|
|
|
AbstractCharacter.__serializeForClientMsg(abstractCharacter, writer);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void __serializeForClientMsg(AbstractCharacter abstractCharacter, final ByteBufferWriter writer) {
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void serializeForClientMsgOtherPlayer(AbstractCharacter abstractCharacter, final ByteBufferWriter writer, final boolean asciiLastName) {
|
|
|
|
|
|
|
|
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);
|
|
|
|
DispatchManager.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);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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));
|
|
|
|
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();
|
|
|
|
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));
|
|
|
|
|
|
|
|
PlayerBonuses bonuses = abstractCharacter.bonuses;
|
|
|
|
if(bonuses != null){
|
|
|
|
ModType modType = ModType.AttackDelay;
|
|
|
|
for (AbstractEffectModifier mod : bonuses.bonusFloats.keySet()) {
|
|
|
|
|
|
|
|
if (mod.getPercentMod() == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
if (!mod.modType.equals(modType))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (bonuses.bonusFloats.get(mod) == null)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
speed *= (1 + bonuses.bonusFloats.get(mod));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//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) {
|
|
|
|
|
|
|
|
//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;
|
|
|
|
}
|
|
|
|
|
|
|
|
@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;
|
|
|
|
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;
|
|
|
|
if (building != null) {
|
|
|
|
//look for region in the building we are in
|
|
|
|
for (Regions regionCycle : building.getBounds().getRegions()) {
|
|
|
|
float regionHeight = regionCycle.highLerp.y - regionCycle.lowLerp.y;
|
|
|
|
if (regionHeight < 10)
|
|
|
|
regionHeight = 10;
|
|
|
|
if (regionCycle.isPointInPolygon(value) && Math.abs(regionCycle.highLerp.y - value.y) < regionHeight)
|
|
|
|
region = regionCycle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
float regionHeightOffset = 0;
|
|
|
|
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);
|
|
|
|
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) {
|
|
|
|
|
|
|
|
if (this.getObjectTypeMask() == 2050) {//MOB?
|
|
|
|
if (value == null) {
|
|
|
|
if (this.isCombat()) {
|
|
|
|
this.setCombat(false);
|
|
|
|
UpdateStateMsg rwss = new UpdateStateMsg();
|
|
|
|
rwss.setPlayer(this);
|
|
|
|
DispatchManager.sendToAllInRange(this, rwss);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!this.isCombat()) {
|
|
|
|
this.setCombat(true);
|
|
|
|
UpdateStateMsg rwss = new UpdateStateMsg();
|
|
|
|
rwss.setPlayer(this);
|
|
|
|
DispatchManager.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);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.getObjectType().equals(GameObjectType.Mob)) {
|
|
|
|
//handle hate value addition
|
|
|
|
Mob target = (Mob) this;
|
|
|
|
if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) {
|
|
|
|
|
|
|
|
if(target.playerAgroMap.containsKey(attacker.getObjectUUID()))
|
|
|
|
target.playerAgroMap.put(attacker.getObjectUUID(), target.playerAgroMap.get(attacker.getObjectUUID()) + value);
|
|
|
|
else
|
|
|
|
target.playerAgroMap.put(attacker.getObjectUUID(), value);
|
|
|
|
|
|
|
|
|
|
|
|
if (target.isPlayerGuard()) {
|
|
|
|
if (target.guardedCity != null && target.guardedCity.cityOutlaws.contains(attacker.getObjectUUID()) == false)
|
|
|
|
target.guardedCity.cityOutlaws.add(attacker.getObjectUUID());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (this.getObjectType().equals(GameObjectType.PlayerCharacter)) {
|
|
|
|
City playerCity = ZoneManager.getCityAtLocation(this.loc);
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|