// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.objects; import engine.Enum; import engine.Enum.*; import engine.InterestManagement.InterestManager; import engine.InterestManagement.WorldGrid; import engine.exception.SerializationException; import engine.gameManager.CombatManager; import engine.gameManager.ConfigManager; import engine.gameManager.MovementManager; import engine.gameManager.PowersManager; 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.DispatchMessage; import engine.net.client.msg.UpdateStateMsg; import engine.powers.EffectsBase; import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantReadWriteLock; public abstract class AbstractCharacter extends AbstractWorldObject { protected CharacterItemManager charItemManager; 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 powers; public ConcurrentHashMap 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 timers; protected ConcurrentHashMap timestamps; protected int atrHandOne; protected int atrHandTwo; protected int minDamageHandOne; protected int maxDamageHandOne; protected int minDamageHandTwo; protected int maxDamageHandTwo; protected float rangeHandOne; protected float rangeHandTwo; protected float speedHandOne; protected float speedHandTwo; protected 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 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 final ConcurrentHashMap siegeMinionMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); 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 equip = null; if (playerCharacter.charItemManager != null) equip = playerCharacter.charItemManager.getEquipped(); if (equip != null) { for (Item item : equip.values()) { item.clearBonuses(); if (item != null) { ConcurrentHashMap 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 playerCharacter.calculateItemBonuses(); //recalculate formulas PlayerCharacter.recalculatePlayerStatsOnLoad(playerCharacter); } catch (Exception e) { Logger.error(e); } } } public static Regions InsideBuildingRegionGoingDown(AbstractCharacter player) { HashSet buildings = WorldGrid.getObjectsInRangePartial(player, 1000, MBServerStatics.MASK_BUILDING); Regions tempRegion = null; for (AbstractWorldObject awo : buildings) { Building building = (Building) awo; if (building.getBounds() == null) continue; if (!Bounds.collide(player.getLoc(), building.getBounds())) continue; for (Regions region : building.getBounds().getRegions()) { if (!region.isPointInPolygon(player.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 worldObject, final Vector3fImmutable targetLoc) { Regions targetRegion = Regions.GetRegionForTeleport(targetLoc); worldObject.locationLock.writeLock().lock(); try { MovementManager.translocate(worldObject, targetLoc, targetRegion); if (worldObject.getObjectType().equals(GameObjectType.PlayerCharacter)) InterestManager.INTERESTMANAGER.HandleLoadForTeleport((PlayerCharacter) worldObject); } catch (Exception e) { Logger.error(e); } finally { worldObject.locationLock.writeLock().unlock(); } } 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 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; } public final CharacterItemManager getCharItemManager() { return this.charItemManager; } public final void setDebug( final int value, final boolean toggle ) { if (toggle) { this.debug |= value; //turn on debug } else { this.debug &= ~value; //turn off debug } } public final boolean getDebug(final int value) { return ((this.debug & value) != 0); } @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 getSkills() { return this.skills; } public final ConcurrentHashMap 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() { // Treb range does not appear to be set here // what gives? if (this.getObjectType() == GameObjectType.Mob) { Mob mob = (Mob) this; if (mob.isSiege()) { return 300; } float range = 8; if (((Mob) this).getEquip().get(1) != null) { range = ((Mob) this).getEquip().get(1).getItemBase().getRange(); } else if (((Mob) this).getEquip().get(2) != null) { range = ((Mob) this).getEquip().get(2).getItemBase().getRange(); } 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 getInventory() { return this.getInventory(false); } /* * Utils */ public final ArrayList 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) { super.setLoc(value); // 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 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 getRecycleTimers() { return this.recycleTimers; } public final ConcurrentHashMap 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); } 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) { Regions targetRegion = Regions.GetRegionForTeleport(targetLoc); locationLock.writeLock().lock(); try { MovementManager.translocate(this, targetLoc, targetRegion); 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 equip = null; if (this.charItemManager != null) { equip = this.charItemManager.getEquipped(); } if (equip != null) { for (Item item : equip.values()) { item.clearBonuses(); if (item != null) { ConcurrentHashMap 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 pc.calculateItemBonuses(); //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; } }