forked from MagicBane/Server
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.
2439 lines
79 KiB
2439 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); |
|
} |
|
|
|
} |
|
|
|
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)); |
|
|
|
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 (ItemTemplate.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 || ItemTemplate.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); |
|
DispatchMessage.sendToAllInRange(this, rwss); |
|
} |
|
} 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); |
|
} |
|
|
|
if (this.getObjectType().equals(GameObjectType.Mob)) { |
|
//handle hate value addition |
|
Mob target = (Mob) this; |
|
if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter)) { |
|
target.playerAgroMap.put(attacker.getObjectUUID(), target.playerAgroMap.get(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; |
|
} |
|
}
|
|
|