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

3121 lines
107 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.objects;
import engine.Enum;
import engine.Enum.*;
import engine.InterestManagement.HeightMap;
import engine.InterestManagement.RealmMap;
import engine.InterestManagement.WorldGrid;
import engine.db.archive.CharacterRecord;
import engine.db.archive.DataWarehouse;
import engine.db.archive.PvpRecord;
import engine.gameManager.*;
import engine.jobs.DeferredPowerJob;
import engine.math.Bounds;
import engine.math.FastMath;
import engine.math.Vector3fImmutable;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.client.ClientConnection;
import engine.net.client.msg.*;
import engine.server.MBServerStatics;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class PlayerCharacter extends AbstractCharacter {
//This object is to be used as the lock in a synchronized statement
//any time the name of a PlayerCharacter needs to be set or
//changed. It ensures the uniqueness check and subsequent
//database update can happen exclusively.
public static final Object FirstNameLock = new Object();
public final ReadWriteLock respawnLock = new ReentrantReadWriteLock(true);
public final ArrayList<Mob> necroPets = new ArrayList<>();
private final Account account;
private final Race race;
private final byte skinColor;
private final byte hairColor;
private final byte beardColor;
private final byte hairStyle;
private final byte beardStyle;
//All Guild information should be held here
private final AtomicInteger guildStatus;
public final AtomicInteger strMod = new AtomicInteger(); // Stat Modifiers
public final AtomicInteger dexMod = new AtomicInteger();
public final AtomicInteger conMod = new AtomicInteger();
public final AtomicInteger intMod = new AtomicInteger();
public final AtomicInteger spiMod = new AtomicInteger();
private final ReadWriteLock teleportLock = new ReentrantReadWriteLock(true);
private final HashMap<Integer, Long> summoners = new HashMap<>();
private final HashSet<AbstractWorldObject> loadedObjects = new HashSet<>();
private final ConcurrentHashMap<Integer, LinkedList<Long>> chatChanFloodList = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private final ConcurrentHashMap<Integer, Long> killMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
public final AtomicInteger trainsAvailable = new AtomicInteger(0); // num skill trains not used
public boolean notDeleted; // <-Use this for deleting character
// ===========================================
// Variables NOT to put into the database!!!! (session only)
// ===========================================
public short statStrMax; // Max Base Stats
public short statDexMax;
public short statConMax;
public short statIntMax;
public short statSpiMax;
public short statStrMin; // Min Base Stats
public short statDexMin;
public short statConMin;
public short statIntMin;
public short statSpiMin;
// Current Stats before Equip and Effect
// Modifiers
public short statStrBase;
public short statDexBase;
public short statConBase;
public short statIntBase;
public short statSpiBase;
public short trainedStatPoints = 0;
public boolean isCSR = false;
//TODO Public fields break OO!!!
public boolean newChar;
public LinkedList<Integer> pvpKills;
public LinkedList<Integer> pvpDeaths;
public int lastBuildingAccessed = 0;
public boolean RUN_MAGICTREK = true;
public float centerHeight = 0;
public float landingAltitude = 0;
public int bindBuilding = 0;
public FriendStatus friendStatus = FriendStatus.Available;
protected ArrayList<CharacterRune> runes;
private BaseClass baseClass;
public PromotionClass promotionClass;
private ConcurrentHashMap<Integer, String> ignoredPlayerIDs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
private boolean lfGroup;
private boolean lfGuild;
private boolean recruiting = false;
private MovementState movementState = MovementState.IDLE;
private MovementState lastMovementState = MovementState.IDLE;
public int overFlowEXP = 0;
private int lastGuildToInvite;
private int lastGroupToInvite;
private boolean follow = false;
private HashSet<AbstractWorldObject> loadedStaticObjects = new HashSet<>();
private Vector3fImmutable lastStaticLoc = new Vector3fImmutable(0.0f, 0.0f, 0.0f);
private GameObjectType lastTargetType;
private int lastTargetID;
private int hidden = 0; // current rank of hide/sneak/invis
private int seeInvis = 0; // current rank of see invis
public float speedMod;
private boolean teleportMode = false; // Teleport on MoveToPoint
private long lastPlayerAttackTime = 0;
public long lastUpdateTime = System.currentTimeMillis();
public long lastStamUpdateTime = System.currentTimeMillis();
private boolean safeZone = false;
private int bindBuildingID;
/*
DataWarehouse based kill/death tracking.
These sets contain the last 10 UUID's
*/
private int lastRealmID = -2;
private int subRaceID = 0;
private DeferredPowerJob weaponPower;
private NPC lastNPCDialog;
private Mob pet;
//Used for skill/Power calculation optimization
public CharacterTitle title = CharacterTitle.NONE;
public boolean asciiLastName = true;
public boolean initialized = false;
public boolean enteredWorld = false;
private boolean canBreathe = true;
private String hash;
private ArrayList<GuildHistory> guildHistory = new ArrayList<>();
private boolean wasTripped75 = false;
private boolean wasTripped50 = false;
private boolean wasTripped25 = false;
private float characterHeight = 0;
public boolean lastSwimming = false;
public boolean dirtyLoad = true;
public final ReadWriteLock dirtyLock = new ReentrantReadWriteLock(true);
public float ZergMultiplier = 1.0f;
public boolean isBoxed = false;
/**
* No Id Constructor
*/
public PlayerCharacter(String firstName, String lastName, short strMod, short dexMod, short conMod, short intMod,
short spiMod, Guild guild, byte runningTrains, Account account, Race race, BaseClass baseClass, byte skinColor, byte hairColor,
byte beardColor, byte hairStyle, byte beardStyle) {
super(firstName, lastName, (short) 1, (short) 1, (short) 1, (short) 1, (short) 1, (short) 1, 0,
Vector3fImmutable.ZERO, Vector3fImmutable.ZERO,
guild, runningTrains);
this.runes = new ArrayList<>();
this.account = account;
this.notDeleted = true;
this.race = race;
this.baseClass = baseClass;
this.skinColor = skinColor;
this.hairColor = hairColor;
this.beardColor = beardColor;
this.hairStyle = hairStyle;
this.beardStyle = beardStyle;
this.lfGroup = false;
this.lfGuild = false;
this.strMod.set(strMod);
this.dexMod.set(dexMod);
this.conMod.set(conMod);
this.intMod.set(intMod);
this.spiMod.set(spiMod);
this.guildStatus = new AtomicInteger(0);
this.bindBuildingID = -1;
}
/**
* ResultSet Constructor
*/
public PlayerCharacter(ResultSet rs) throws SQLException {
super(rs, true);
this.runes = DbManager.CharacterRuneQueries.GET_RUNES_FOR_CHARACTER(this.getObjectUUID());
int accountID = rs.getInt("parent");
this.account = DbManager.AccountQueries.GET_ACCOUNT(accountID);
this.gridObjectType = GridObjectType.DYNAMIC;
this.notDeleted = rs.getBoolean("char_isActive");
int raceID = rs.getInt("char_raceID");
this.race = Race.getRace(raceID);
int baseClassID = rs.getInt("char_baseClassID");
this.baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID);
int promotionClassID = rs.getInt("char_promotionClassID");
this.promotionClass = DbManager.PromotionQueries.GET_PROMOTION_CLASS(promotionClassID);
this.skinColor = rs.getByte("char_skinColor");
this.hairColor = rs.getByte("char_hairColor");
this.beardColor = rs.getByte("char_beardColor");
this.hairStyle = rs.getByte("char_hairStyle");
this.beardStyle = rs.getByte("char_beardStyle");
this.lfGroup = false;
this.lfGuild = false;
//TODO Unify game object with database after DB overhaul
this.guildStatus = new AtomicInteger(0);
Guild guild = Guild.getGuild(this.getGuildUUID());
PlayerManager.setGuildLeader(this, guild != null && guild.isGuildLeader(this.getObjectUUID()));
boolean hasAnniversery = rs.getBoolean("anniversery");
PlayerManager.setInnerCouncil(this, rs.getBoolean("guild_isInnerCouncil"));
PlayerManager.setFullMember(this, rs.getBoolean("guild_isFullMember"));
PlayerManager.setTaxCollector(this, rs.getBoolean("guild_isTaxCollector"));
PlayerManager.setRecruiter(this, rs.getBoolean("guild_isRecruiter"));
PlayerManager.setGuildTitle(this, rs.getInt("guild_title"));
if (this.account != null)
this.ignoredPlayerIDs = DbManager.PlayerCharacterQueries.GET_IGNORE_LIST(this.account.getObjectUUID(), false);
this.strMod.set(rs.getShort("char_strMod"));
this.dexMod.set(rs.getShort("char_dexMod"));
this.conMod.set(rs.getShort("char_conMod"));
this.intMod.set(rs.getShort("char_intMod"));
this.spiMod.set(rs.getShort("char_spiMod"));
this.bindBuildingID = rs.getInt("char_bindBuilding");
this.hash = rs.getString("hash");
// For debugging skills
// CharacterSkill.printSkills(this);
}
/*
* Getters
*/
public byte getHairStyle() {
return hairStyle;
}
public byte getBeardStyle() {
return beardStyle;
}
public DeferredPowerJob getWeaponPower() {
return this.weaponPower;
}
public void setWeaponPower(DeferredPowerJob value) {
this.weaponPower = value;
}
public void setSafeZone(boolean value) {
this.safeZone = value;
}
public boolean inSafeZone() {
return this.safeZone;
}
public boolean isInSafeZone() {
Zone zone = ZoneManager.findSmallestZone(this.getLoc());
if (zone != null) {
return zone.getSafeZone() == (byte) 1;
}
return false;
//return this.safeZone;
}
/**
* @return the account
*/
public Account getAccount() {
return account;
}
public void deactivateCharacter() {
this.notDeleted = false;
DbManager.PlayerCharacterQueries.SET_DELETED(this);
DbManager.removeFromCache(this);
}
public void activateCharacter() {
this.notDeleted = true;
DbManager.PlayerCharacterQueries.SET_DELETED(this);
}
public boolean isDeleted() {
return !this.notDeleted;
}
public ArrayList<CharacterRune> getRunes() {
return this.runes;
}
public CharacterRune getRune(int runeID) {
if (this.runes == null)
return null;
for (CharacterRune cr : this.runes) {
if (cr.getRuneBase() != null && cr.getRuneBase().getObjectUUID() == runeID)
return cr;
}
return null;
}
public boolean addRune(CharacterRune value) {
if (this.runes.size() > 12) // Max Runes
return false;
if (this.runes.indexOf(value) != -1) // Already contains rune
return false;
this.runes.add(value);
return true;
}
public boolean removeRune(CharacterRune value) {
int index = this.runes.indexOf(value);
if (index == -1)
return false;
this.runes.remove(index);
return true;
}
public CharacterRune removeRune(int runeID) {
Iterator<CharacterRune> it = this.runes.iterator();
while (it.hasNext()) {
CharacterRune cr = it.next();
if (cr != null) {
RuneBase rb = cr.getRuneBase();
if (rb != null)
if (runeID == rb.getObjectUUID()) {
it.remove();
DbManager.CharacterRuneQueries.DELETE_CHARACTER_RUNE(cr);
return cr;
}
}
}
return null;
}
/**
* @ Kill this Character
*/
@Override
public void killCharacter(AbstractCharacter attacker) {
killCleanup();
// *** Mobs have a separate combat path? Crazy shit!
// *** Mobs don't get Experience for killing players. everything else is done in killCleanup();
if (attacker.getObjectType().equals(GameObjectType.PlayerCharacter) == false) {
Zone zone = ZoneManager.findSmallestZone(this.getLoc());
//DeathShroud
if (zone.getSafeZone() == 0)
PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
//enable this to give players deathshroud if mobs kill player.
// Zone zone = ZoneManager.findSmallestZone(this.getLoc());
// if (zone.getSafeZone() == 0)
// PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
return;
}
// Death to other player.
// TODO Send PvP and guild/nation message
PlayerCharacter att = (PlayerCharacter) attacker;
String message = this.getFirstName();
if (this.guild != null && (!(this.guild.getName().equals("Errant"))))
message += " of " + this.guild.getName();
message += " was killed by " + att.getFirstName();
if (att.guild != null && (!(att.guild.getName().equals("Errant"))))
message += " of " + att.guild.getName();
message += "!";
//see if we shold grant xp to attacker
boolean doPVPEXP = false;
long lastKill = att.getLastKillOfTarget(this.getObjectUUID());
//if ((System.currentTimeMillis() - lastKill) > MBServerStatics.PLAYER_KILL_XP_TIMER)
//if (attacker.getLevel() > 39 && this.getLevel() > 39) {
//Guild aN = null;
//Guild tN = null;
//if (attacker.getGuild() != null)
// aN = attacker.getGuild().getNation();
//if (this.getGuild() != null)
// tN = this.getGuild().getNation();
//if (aN == null || tN == null || aN.isEmptyGuild() || Guild.sameGuild(aN, tN) || this.isDeathShroud()) {
//skip giving xp if same guild or attacker is errant, or target is in death shroud.
//} else {
doPVPEXP = true;
//}
// }
//apply death shroud to non safeholds.
Zone zone = ZoneManager.findSmallestZone(this.getLoc());
//DeathShroud
if (zone != null && zone.getSafeZone() == 0)
PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
if (doPVPEXP) {
Group g = GroupManager.getGroup((PlayerCharacter) attacker);
Experience.doExperience((PlayerCharacter) attacker, this, g);
}
ChatManager.chatPVP(message);
/*
Update kill / death tracking lists
Each character on list is unique. Only once!
*/
PlayerCharacter aggressorCharacter = (PlayerCharacter) attacker;
boolean containsVictim = true;
boolean containsAttacker = true;
containsVictim = aggressorCharacter.pvpKills.contains(this.getObjectUUID());
containsAttacker = aggressorCharacter.pvpKills.contains(this.getObjectUUID());
// Rorate attacker's kill list
if ((aggressorCharacter.pvpKills.size() == 10) && containsVictim == false)
aggressorCharacter.pvpKills.removeLast();
if (containsVictim == false)
aggressorCharacter.pvpKills.addFirst(this.getObjectUUID());
// Rotate the poor victim's deathlist
if ((this.pvpDeaths.size() == 10) && containsAttacker == false)
this.pvpDeaths.removeLast();
if (containsAttacker == false)
this.pvpDeaths.addFirst(this.getObjectUUID());
// DataWarehouse: store pvp event
PvpRecord pvpRecord = PvpRecord.borrow((PlayerCharacter) attacker, this, this.getLoc(), doPVPEXP);
DataWarehouse.pushToWarehouse(pvpRecord);
// Mark kill time in killmap
att.updateKillMap(this.getObjectUUID());
}
@Override
public void killCharacter(String reason) {
killCleanup();
Zone zone = ZoneManager.findSmallestZone(this.getLoc());
if (zone.getSafeZone() == 0)
PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
// Send death message if needed
if (reason.equals("Water")) {
TargetedActionMsg targetedActionMsg = new TargetedActionMsg(this, true);
Dispatch dispatch = Dispatch.borrow(this, targetedActionMsg);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
String message = this.getFirstName();
if (this.guild != null && (!(this.guild.getName().equals("Errant"))))
message += " of " + this.guild.getName();
else
message += this.getLastName();
message += " was killed by water!";
ChatManager.chatPVP(message);
}
}
private void killCleanup() {
this.stopMovement(this.getLoc());
this.health.set(-1);
//remove pet
if (this.pet != null)
this.dismissPet();
NPCManager.dismissNecroPets(this);
// remove flight job.
this.setTakeOffTime(0);
this.setDesiredAltitude(0);
this.altitude = (float) 0;
// Release Mine Claims
Mine.releaseMineClaims(this);
this.getCharItemManager().closeTradeWindow();
//increment live counter. This is to prevent double kills from casts
this.liveCounter++;
//remove any effects
try {
this.clearEffects();
} catch (Exception e) {
Logger.error("PlayerCharacter.KillCleanup", e.getMessage());
}
//remove the SIT flag
this.setSit(false);
// sends a kill message to ensure the Player falls over.
this.respawnLock.writeLock().lock();
try {
if (SessionManager.getPlayerCharacterByID(this.getObjectUUID()) == null && !this.enteredWorld) {
WorldGrid.RemoveWorldObject(this);
PlayerManager.respawn(this, false, false, true);
} else {
TargetedActionMsg killmsg = new TargetedActionMsg(this, true);
DispatchMessage.dispatchMsgToInterestArea(this, killmsg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
}
} catch (Exception e) {
Logger.error(e);
} finally {
this.respawnLock.writeLock().unlock();
}
// TODO damage equipped items
if (this.charItemManager != null)
this.charItemManager.damageAllGear();
// TODO cleanup any timers
//recalculate inventory weights
if (this.charItemManager != null) {
this.charItemManager.endTrade(true);
this.charItemManager.calculateWeights();
this.charItemManager.updateInventory();
}
}
public void updateKillMap(int target) {
this.killMap.put(target, System.currentTimeMillis());
}
public long getLastKillOfTarget(int target) {
if (this.killMap.containsKey(target))
return this.killMap.get(target);
return 0L;
}
public boolean isDeathShroud() {
return this.effects != null && this.effects.containsKey("DeathShroud");
}
public void setSafeMode() {
PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, -1661758934, 40, false);
}
public boolean safemodeInvis() {
if (!this.effects.containsKey("Invisible"))
return false;
Effect eff = this.effects.get("Invisible");
if (eff == null)
return false;
return eff.getEffectToken() == -1661751254;
}
/**
* @return the race
*/
public Race getRace() {
return race;
}
public int getRaceID() {
if (race != null)
return race.getRaceRuneID();
return 0;
}
/**
* @return the baseClass
*/
public BaseClass getBaseClass() {
return baseClass;
}
public int getBaseClassID() {
if (baseClass != null)
return baseClass.getObjectUUID();
return 0;
}
public int getBaseClassToken() {
if (this.baseClass == null)
return 0;
else
return this.baseClass.getToken();
}
public boolean setBaseClass(int value) {
BaseClass bs = BaseClass.getBaseClass(value);
if (bs != null) {
this.baseClass = bs;
return true;
}
return false;
}
@Override
public Vector3fImmutable getBindLoc() {
Vector3fImmutable bindLocation;
// Return garbage and early exit if this is the login server.
// getBindLoc() does a TOL lookup, which also then loads the
// city and other garbage not needed on the login server.
if (ConfigManager.serverType.equals(ServerType.LOGINSERVER))
return Vector3fImmutable.ZERO;
Building bindBuilding = PlayerManager.getUpdatedBindBuilding(this);
//handle rented room binds.
if (bindBuilding == null) {
bindLocation = Enum.Ruins.getRandomRuin().getLocation();
return bindLocation;
}
bindLocation = BuildingManager.GetBindLocationForBuilding(bindBuilding);
if (bindLocation == null)
bindLocation = Enum.Ruins.getRandomRuin().getLocation();
if(this.guild.getNation().equals(Guild.getErrantGuild())){
bindLocation = Vector3fImmutable.getRandomPointOnCircle(BuildingManager.getBuilding(27977).loc,20f);
}
return bindLocation;
}
public int getInventoryCapacity() {
return statStrBase * 3;
}
public int getInventoryCapacityRemaining() {
return (this.getInventoryCapacity() - this.charItemManager.getInventoryWeight());
}
/**
* @return the PromotionClass
*/
public PromotionClass getPromotionClass() {
return promotionClass;
}
public int getPromotionClassID() {
if (promotionClass != null)
return promotionClass.getObjectUUID();
return 0;
}
public boolean setPromotionClass(int value) {
PromotionClass promotionClass = PromotionClass.GetPromtionClassFromCache(value);
if (promotionClass == null)
return false;
if (!DbManager.PlayerCharacterQueries.SET_PROMOTION_CLASS(this, value))
return false;
this.promotionClass = promotionClass;
// Warehouse this event
CharacterRecord.updatePromotionClass(this);
return true;
}
/**
* @return the skinColor
*/
public byte getSkinColor() {
return skinColor;
}
/**
* @return the hairColor
*/
public byte getHairColor() {
return hairColor;
}
/**
* @return the beardColor
*/
public byte getBeardColor() {
return beardColor;
}
public int getIsLfGroupAsInt() {
if (lfGroup)
return 2;
return 1;
}
public final void toggleLFGroup() {
this.lfGroup = !this.lfGroup;
}
public final void toggleLFGuild() {
this.lfGuild = !this.lfGuild;
}
public final void toggleRecruiting() {
this.recruiting = !this.recruiting;
}
public final boolean isLFGroup() {
return this.lfGroup;
}
public final boolean isLFGuild() {
return this.lfGuild;
}
public final boolean isRecruiting() {
return this.recruiting;
}
public final int getHeadlightsAsInt() {
if (this.lfGroup)
if (this.lfGuild)
if (this.recruiting)
return 14; // LFGroup + LFGuild + Recruiting
else
return 6; // LFGroup + LFGuild
else if (this.recruiting)
return 10; // LFGroup + Recruiting
else
return 2; // LFGroup only
else if (this.lfGuild)
if (this.recruiting)
return 12; // LFGuild + Recruiting
else
return 4; // LFGuild only
else if (this.recruiting)
return 8; // Recruiting only
else
return 0; // No Headlights
}
public int getStrMax() {
return this.statStrMax;
}
public int getDexMax() {
return this.statDexMax;
}
public int getConMax() {
return this.statConMax;
}
public int getIntMax() {
return this.statIntMax;
}
public int getSpiMax() {
return this.statSpiMax;
}
public void setLastTarget(GameObjectType type, int id) {
this.lastTargetType = type;
this.lastTargetID = id;
}
public GameObjectType getLastTargetType() {
return this.lastTargetType;
}
public int getLastTargetID() {
return this.lastTargetID;
}
/*
* Serializing
*/
public synchronized int getBindBuildingID() {
return this.bindBuildingID;
}
public synchronized void setBindBuildingID(int value) {
DbManager.PlayerCharacterQueries.SET_BIND_BUILDING(this, value);
this.bindBuildingID = value;
}
public AbstractGameObject getLastTarget() {
if (this.lastTargetType == GameObjectType.unknown)
return null;
switch (this.lastTargetType) {
// Make sure these only return an object that is
// already in the GOM, and doesn't reload from the DB
case PlayerCharacter:
return DbManager.getFromCache(GameObjectType.PlayerCharacter, this.lastTargetID);
case Building:
return DbManager.getFromCache(GameObjectType.Building, this.lastTargetID);
case NPC:
return NPC.getFromCache(this.lastTargetID);
case Mob:
return Mob.getFromCache(this.lastTargetID);
case Item:
return DbManager.getFromCache(GameObjectType.Item, this.lastTargetID);
case Corpse:
return DbManager.getFromCache(GameObjectType.Corpse, this.lastTargetID);
default:
// Ignore exception for MobLoot? ***Check
if (this.lastTargetType != GameObjectType.MobLoot)
Logger.error("getLastTarget() unhandled object type: "
+ this.lastTargetType.toString());
}
return null;
}
public Vector3fImmutable getLastStaticLoc() {
return this.lastStaticLoc;
}
public void setLastStaticLoc(Vector3fImmutable value) {
this.lastStaticLoc = value;
}
public int getHidden() {
return this.hidden;
}
public void setHidden(int value) {
this.hidden = value;
}
public int getSeeInvis() {
if (this.getDebug(8)) //<-added for see invis debug devcmd
return 10000;
return this.seeInvis;
}
public void setSeeInvis(int value) {
this.seeInvis = value;
}
public long getLastPlayerAttackTime() {
return this.lastPlayerAttackTime;
}
public void setLastPlayerAttackTime() {
this.lastPlayerAttackTime = System.currentTimeMillis();
}
public NPC getLastNPCDialog() {
return this.lastNPCDialog;
}
public void setLastNPCDialog(NPC value) {
this.lastNPCDialog = value;
}
public Mob getPet() {
return this.pet;
}
public void setPet(Mob mob) {
if (mob == null)
return;
this.pet = mob;
}
public void dismissPet() {
if (this.pet != null) {
this.pet.dismiss();
this.pet = null;
}
}
//called to verify player has correct item equipped for casting.
public boolean validEquip(int slot, String type) {
if (this.charItemManager == null)
return false;
Item item = this.charItemManager.getEquipped(slot);
if (item == null)
return false;
ItemBase ib = item.getItemBase();
if (ib != null) {
if ((ib.getType().equals(ItemType.WEAPON))
&& (ib.getSkillRequired().equals(type) || ib.getMastery().equals(type)))
return true;
return (ib.getType().equals(ItemType.ARMOR))
&& (ib.getSkillRequired().equals(type));
}
return false;
}
public short getPCLevel() {
short level = (short) Experience.getLevel(this.exp);
if (this.promotionClass == null && level >= 10)
return (short) 10;
if (this.overFlowEXP > 0)
return this.level;
return level;
}
@Override
public float getSpeed() {
float speed;
if (PlayerManager.isFlying(this))
if (this.walkMode) {
speed = race.getRaceType().getRunSpeed().getFlyWalk();
} else {
speed = race.getRaceType().getRunSpeed().getFlyRun();
}
else if (this.lastSwimming == true)
speed = MBServerStatics.SWIMSPEED;
else if (this.walkMode) {
if (this.isCombat())
speed = race.getRaceType().getRunSpeed().getWalkCombat();
else
speed = race.getRaceType().getRunSpeed().getWalkStandard();
} else {
if (this.isCombat())
speed = race.getRaceType().getRunSpeed().getRunCombat();
else
speed = race.getRaceType().getRunSpeed().getRunStandard();
}
float mod = this.speedMod;
float endSpeed = speed * mod;
if (endSpeed > 41 && !this.isCSR)
endSpeed = 41;
return endSpeed;
}
public ClientConnection getClientConnection() {
return SessionManager.getClientConnection(this);
}
public String getCombinedName() {
return this.getName();
}
public long getLastGuildToInvite() {
return this.lastGuildToInvite;
}
public void setLastGuildToInvite(int value) {
this.lastGuildToInvite = value;
}
public boolean getFollow() {
return this.follow;
}
public void setFollow(boolean value) {
this.follow = value;
}
public boolean toggleFollow() {
this.follow = !this.follow;
return this.follow;
}
public int getLastGroupToInvite() {
return this.lastGroupToInvite;
}
public void setLastGroupToInvite(int value) {
this.lastGroupToInvite = value;
}
@Override
public float getAltitude() {
if (this.altitude < 0)
this.altitude = 0;
//player has reached desired altitude, return normal altitude.
if (this.getTakeOffTime() == 0)
return this.altitude;
//sanity check if desired altitude is the same as current altitude. return desired altitude.
if (this.altitude == this.getDesiredAltitude()) {
return this.getDesiredAltitude();
}
//calculate how much the player has moved up
float amountMoved = (System.currentTimeMillis() - this.getTakeOffTime()) * MBServerStatics.FLY_RATE; //FUCK DIVIDING
//Player is moving up
if (this.getDesiredAltitude() > this.altitude) {
//if amount moved passed desiredAltitude, return the desired altitude.
if (this.altitude + amountMoved >= this.getDesiredAltitude())
return this.getDesiredAltitude();
return this.altitude + amountMoved;
//Player is moving down
} else {
//if amount moved passed desiredAltitude, return the desired altitude.
if (this.altitude - amountMoved <= this.getDesiredAltitude())
return this.getDesiredAltitude();
return this.altitude - amountMoved;
}
}
public void setAltitude(float value) {
this.altitude = value;
}
public HashSet<AbstractWorldObject> getLoadedObjects() {
return this.loadedObjects;
}
public HashSet<AbstractWorldObject> getLoadedStaticObjects() {
return this.loadedStaticObjects;
}
public boolean isTeleportMode() {
return teleportMode;
}
public void setTeleportMode(boolean teleportMode) {
this.teleportMode = teleportMode;
}
public long chatFloodTime(int chatOpcode, long chatTimeMilli, int qtyToSave) {
if (qtyToSave < 1)
return 0L; // disabled
LinkedList<Long> times = null;
long oldestTime;
synchronized (chatChanFloodList) {
if (!chatChanFloodList.containsKey(chatOpcode)) {
times = new LinkedList<>();
for (int i = 0; i < qtyToSave; i++) {
times.add(0L);
}
chatChanFloodList.put(chatOpcode, times);
} else
times = chatChanFloodList.get(chatOpcode);
oldestTime = times.getLast();
times.removeLast();
times.addFirst(chatTimeMilli);
}
return oldestTime;
}
public void addIgnoredPlayer(Account ac, String name) {
if (ac == null)
return;
int acID = ac.getObjectUUID();
if (acID < 1)
return;
if (ignoredPlayerIDs == null)
return;
if (acID == getObjectUUID())
return; // yourself
ignoredPlayerIDs.put(acID, name);
}
public void removeIgnoredPlayer(Account ac) {
if (ac == null)
return;
int acID = ac.getObjectUUID();
if (acID < 1)
return;
if (ignoredPlayerIDs == null)
return;
if (acID == getObjectUUID())
return; // yourself
ignoredPlayerIDs.remove(acID);
}
public boolean isIgnoringPlayer(PlayerCharacter pc) {
if (pc == null)
return false;
if (pc.account == null)
return false;
return isIgnoringPlayer(pc.account);
}
public boolean isIgnoringPlayer(Account ac) {
if (ac == null)
return false;
int acID = ac.getObjectUUID();
if (acID < 1)
return false;
return ignoredPlayerIDs.containsKey(acID);
}
public String[] getIgnoredPlayerNames() {
int size = ignoredPlayerIDs.size();
String[] ary = new String[size];
for (int i = 0; i < size; i++) {
// ary[i] = PlayerCharacter.getFirstName(ignoredPlayerIDs.get(i));
ary[i] = ignoredPlayerIDs.get(i);
}
return ary;
}
public int getStrMod() {
return this.strMod.get();
}
public int getDexMod() {
return this.dexMod.get();
}
public int getConMod() {
return this.conMod.get();
}
public int getIntMod() {
return this.intMod.get();
}
public int getSpiMod() {
return this.spiMod.get();
}
public boolean isMale() {
if (this.race == null)
return true;
return (this.race.getRaceType().getCharacterSex().equals(CharacterSex.MALE));
}
public boolean canSee(PlayerCharacter tar) {
if (tar == null)
return false;
if (this.equals(tar))
return true;
return this.getSeeInvis() >= tar.hidden && !tar.safemodeInvis();
}
public void recalculatePlayerStats(boolean initialized) {
//calculate base stats
this.calculateBaseStats();
//calculate base skills
CharacterSkill.updateAllBaseAmounts(this);
this.calculateModifiedStats();
//calculate modified skills
CharacterSkill.updateAllModifiedAmounts(this);
this.updateScaleHeight();
//calculate modified stats
//calculate ATR, damage and defense
this.calculateAtrDefenseDamage();
//calculate movement bonus
PlayerManager.calculateSpeedMod(this);
// recalculate Max Health/Mana/Stamina
this.calculateMaxHealthManaStamina();
// recalculate Resists
Resists.calculateResists(this);
}
/**
* @ Recalculate player after promoting or gaining a level
*/
public void recalculate() {
this.applyBonuses();
this.trainsAvailable.set(CharacterSkill.getTrainsAvailable(this));
if (this.trainsAvailable.get() < 0)
recalculateTrains();
//this.resists.calculateResists(this);
// calculate skills and powers. Make sure none are missing.
this.calculateSkills();
// calculate powers again. See if any new powers unlocked
this.calculateSkills();
}
//This is run to auto-fix any overage on skill training.
public void recalculateTrains() {
int trainsAvailable = CharacterSkill.getTrainsAvailable(this);
if (trainsAvailable < 0) {
//refine powers first, run twice to catch any prereqs
ConcurrentHashMap<Integer, CharacterPower> powers = this.getPowers();
for (int i = 0; i < 2; i++) {
for (CharacterPower p : powers.values()) {
if (trainsAvailable >= 0)
return;
while (p.getTrains() > 0 && p.refine(this)) {
trainsAvailable++;
if (trainsAvailable >= 0)
return;
}
}
}
//refine skills
ConcurrentHashMap<String, CharacterSkill> skills = this.getSkills();
for (CharacterSkill s : skills.values()) {
if (trainsAvailable >= 0)
return;
while (s.getNumTrains() > 0 && s.refine(this)) {
if (CharacterSkill.getTrainsAvailable(this) >= 0)
return;
}
}
}
}
/**
* @ Calculates Base Stats Call this when modifying stats or adding/removing
* runes
*/
public void calculateBaseStats() {
if (this.race == null || this.baseClass == null)
// Logger.getInstance().log( LogEventType.ERROR,
// "PlayerCharacter.updateBaseStats: Missing race or baseclass for Player "
// + this.getUUID());
return;
// get base stats and total available
int strMin = this.race.getStrStart() + this.baseClass.getStrMod() - 5;
int dexMin = this.race.getDexStart() + this.baseClass.getDexMod() - 5;
int conMin = this.race.getConStart() + this.baseClass.getConMod() - 5;
int intMin = this.race.getIntStart() + this.baseClass.getIntMod() - 5;
int spiMin = this.race.getSpiStart() + this.baseClass.getSpiMod() - 5;
int str = this.race.getStrStart() + this.baseClass.getStrMod() + this.strMod.get();
int dex = this.race.getDexStart() + this.baseClass.getDexMod() + this.dexMod.get();
int con = this.race.getConStart() + this.baseClass.getConMod() + this.conMod.get();
int intt = this.race.getIntStart() + this.baseClass.getIntMod() + this.intMod.get();
int spi = this.race.getSpiStart() + this.baseClass.getSpiMod() + this.spiMod.get();
int strMax = this.race.getStrMax();
int dexMax = this.race.getDexMax();
int conMax = this.race.getConMax();
int intMax = this.race.getIntMax();
int spiMax = this.race.getSpiMax();
int available = this.race.getStartingPoints() - this.strMod.get() - this.dexMod.get() - this.conMod.get() - this.intMod.get() - this.spiMod.get();
if (level < 20)
available += (level - 1) * 5;
else if (level < 30)
available += 90 + (level - 19) * 4;
else if (level < 40)
available += 130 + (level - 29) * 3;
else if (level < 50)
available += 160 + (level - 39) * 2;
else
available += 180 + (level - 49);
// modify for any runes applied.
for (CharacterRune rune : this.runes) {
if (rune.getRuneBase() == null)
// Logger.getInstance().log( LogEventType.ERROR,
// "PlayerCharacter.updateBaseStats: Missing runebase for rune "
// + rune.getUUID());
continue;
ArrayList<RuneBaseAttribute> attrs = rune.getRuneBase().getAttrs();
if (attrs == null)
// Logger.getInstance().log( LogEventType.ERROR,
// "PlayerCharacter.updateBaseStats: Missing attributes for runebase "
// + rune.getRuneBase().getUUID());
continue;
for (RuneBaseAttribute abr : attrs) {
int attrID = abr.getAttributeID();
int value = abr.getModValue();
switch (attrID) {
case MBServerStatics.RUNE_COST_ATTRIBUTE_ID:
available -= value;
break;
case MBServerStatics.RUNE_STR_ATTRIBUTE_ID:
str += value;
strMin += value;
break;
case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID:
dex += value;
dexMin += value;
break;
case MBServerStatics.RUNE_CON_ATTRIBUTE_ID:
con += value;
conMin += value;
break;
case MBServerStatics.RUNE_INT_ATTRIBUTE_ID:
intt += value;
intMin += value;
break;
case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID:
spi += value;
spiMin += value;
break;
case MBServerStatics.RUNE_STR_MAX_ATTRIBUTE_ID:
strMax += value;
break;
case MBServerStatics.RUNE_DEX_MAX_ATTRIBUTE_ID:
dexMax += value;
break;
case MBServerStatics.RUNE_CON_MAX_ATTRIBUTE_ID:
conMax += value;
break;
case MBServerStatics.RUNE_INT_MAX_ATTRIBUTE_ID:
intMax += value;
break;
case MBServerStatics.RUNE_SPI_MAX_ATTRIBUTE_ID:
spiMax += value;
break;
default:
}
}
//Set titles based on rune..
switch (rune.getRuneBaseID()) {
default:
break;
case 2901: //CSR 1
this.title = CharacterTitle.CSR_1;
break;
case 2902: //CSR 1
this.title = CharacterTitle.CSR_2;
break;
case 2903: //CSR 1
this.title = CharacterTitle.CSR_3;
break;
case 2904: //CSR 1
this.title = CharacterTitle.CSR_4;
break;
case 2910: //Wolfpack Developer
this.title = CharacterTitle.DEVELOPER;
break;
case 2911: //QA Test Rune
this.title = CharacterTitle.QA;
break;
}
}
//hack check. Make sure available does not go below 0.
//subtract from each stat until available is 0 or greater.
if (available < 0) {
while (this.spiMod.get() > 0 && available < 0) {
this.spiMod.decrementAndGet();
spi--;
available++;
}
while (this.conMod.get() > 0 && available < 0) {
this.conMod.decrementAndGet();
con--;
available++;
}
while (this.strMod.get() > 0 && available < 0) {
this.strMod.decrementAndGet();
str--;
available++;
}
while (this.dexMod.get() > 0 && available < 0) {
this.dexMod.decrementAndGet();
dex--;
available++;
}
while (this.intMod.get() > 0 && available < 0) {
this.intMod.decrementAndGet();
intt--;
available++;
}
//update database
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
}
this.statStrBase = (short) str;
this.statDexBase = (short) dex;
this.statConBase = (short) con;
this.statIntBase = (short) intt;
this.statSpiBase = (short) spi;
this.statStrMax = (short) (strMax);
this.statDexMax = (short) (dexMax);
this.statConMax = (short) (conMax);
this.statIntMax = (short) (intMax);
this.statSpiMax = (short) (spiMax);
this.statStrMin = (short) strMin;
this.statDexMin = (short) dexMin;
this.statConMin = (short) conMin;
this.statIntMin = (short) intMin;
this.statSpiMin = (short) spiMin;
this.unusedStatPoints = (short) available;
this.trainedStatPoints = 0;
// Testing, allow characters to have more stats then normal for formula checking
if (this.statStrBase > this.statStrMax)
this.statStrMax = this.statStrBase;
if (this.statDexBase > this.statDexMax)
this.statDexMax = this.statDexBase;
if (this.statConBase > this.statConMax)
this.statConMax = this.statConBase;
if (this.statIntBase > this.statIntMax)
this.statIntMax = this.statIntBase;
if (this.statSpiBase > this.statSpiMax)
this.statSpiMax = this.statSpiBase;
// Modified stats must be recalculated when base stats are
//calculateModifiedStats();
//update hide and seeInvis levels
if (this.bonuses != null) {
this.hidden = (int) bonuses.getFloat(ModType.Invisible, SourceType.None);
this.seeInvis = (int) bonuses.getFloat(ModType.SeeInvisible, SourceType.None);
} else {
this.hidden = (byte) 0;
this.seeInvis = (byte) 0;
}
//check is player is a CSR
this.isCSR = this.containsCSRRune();
}
private boolean containsCSRRune() {
if (this.race != null && this.race.getRaceType().equals(RaceType.CSRMALE))
return true;
if (this.baseClass != null && this.baseClass.getObjectUUID() > 2900 && this.baseClass.getObjectUUID() < 2905)
return true;
if (this.promotionClass != null && this.promotionClass.getObjectUUID() > 2900 && this.promotionClass.getObjectUUID() < 2905)
return true;
if (this.runes == null)
return false;
for (CharacterRune rune : this.runes) {
if (rune == null || rune.getRuneBase() == null)
continue;
RuneBase rb = rune.getRuneBase();
if (rb.getObjectUUID() > 2900 && rb.getObjectUUID() < 2905)
return true;
if (rb.getObjectUUID() == 2910)
return true;
}
return false;
}
public boolean isCSR() {
return this.isCSR;
}
public void setAsciiLastName(boolean value) {
this.asciiLastName = value;
}
public boolean _asciiLastName() {
return this.asciiLastName;
}
/**
* @ Calculates Modified Stats Call this when changing equipment or
* add/removing effect. skips base stat modification.
*/
public void calculateModifiedStats() {
float strVal = this.statStrBase;
float dexVal = this.statDexBase;
float conVal = this.statConBase;
float intVal = this.statIntBase;
float spiVal = this.statSpiBase;
float dexPenalty = getDexPenalty();
// TODO modify for equipment
if (this.bonuses != null) {
// modify for effects
strVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Strength));
dexVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Dexterity));
conVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Constitution));
intVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Intelligence));
spiVal += Math.round(this.bonuses.getFloat(ModType.Attr, SourceType.Spirit));
// apply dex penalty for armor
dexVal *= dexPenalty;
// modify percent amounts. DO THIS LAST!
strVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Strength));
dexVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Dexterity));
conVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Constitution));
intVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Intelligence));
spiVal *= (1 + this.bonuses.getFloatPercentAll(ModType.Attr, SourceType.Spirit));
} else
// apply dex penalty for armor
dexVal *= dexPenalty;
// Set current stats
this.statStrCurrent = (strVal < 1) ? (short) 1 : (short) strVal;
this.statDexCurrent = (dexVal < 1) ? (short) 1 : (short) dexVal;
this.statConCurrent = (conVal < 1) ? (short) 1 : (short) conVal;
this.statIntCurrent = (intVal < 1) ? (short) 1 : (short) intVal;
this.statSpiCurrent = (spiVal < 1) ? (short) 1 : (short) spiVal;
// recalculate skills
//CharacterSkill.updateAllBaseAmounts(this);
// recalculate Max Health/Mana/Stamina
//calculateMaxHealthManaStamina();
// recalculate Resists
//this.resists.calculateResists(this);
}
public float getDexPenalty() {
if (this.charItemManager == null || this.charItemManager.getEquipped() == null) {
Logger.error("Player " + this.getObjectUUID() + " missing equipment");
return 1f;
}
ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
float dexPenalty = 0f;
dexPenalty += PlayerManager.getDexPenalty(equipped.get(MBServerStatics.SLOT_HELMET));
dexPenalty += PlayerManager.getDexPenalty(equipped.get(MBServerStatics.SLOT_CHEST));
dexPenalty += PlayerManager.getDexPenalty(equipped.get(MBServerStatics.SLOT_ARMS));
dexPenalty += PlayerManager.getDexPenalty(equipped.get(MBServerStatics.SLOT_GLOVES));
dexPenalty += PlayerManager.getDexPenalty(equipped.get(MBServerStatics.SLOT_LEGGINGS));
dexPenalty += PlayerManager.getDexPenalty(equipped.get(MBServerStatics.SLOT_FEET));
return (1 - (dexPenalty / 100));
}
public int getStrForClient() {
return this.statStrCurrent - this.race.getStrStart() - this.baseClass.getStrMod();
}
public int getDexForClient() {
return this.statDexCurrent - this.race.getDexStart() - this.baseClass.getDexMod();
}
public int getConForClient() {
return this.statConCurrent - this.race.getConStart() - this.baseClass.getConMod();
}
public int getIntForClient() {
return this.statIntCurrent - this.race.getIntStart() - this.baseClass.getIntMod();
}
public int getSpiForClient() {
return this.statSpiCurrent - this.race.getSpiStart() - this.baseClass.getSpiMod();
}
public int getTrainsAvailable() {
return this.trainsAvailable.get();
}
public void modifyTrainsAvailable(int amount) {
boolean worked = false;
while (!worked) {
int old = this.trainsAvailable.get();
int newVal = old + amount;
// if (newVal < 0)
// newVal = 0;
worked = this.trainsAvailable.compareAndSet(old, newVal);
}
}
// Reset any data that should not persist from a previous session
public void resetDataAtLogin() {
loadedObjects.clear();
loadedStaticObjects.clear();
lastStaticLoc = Vector3fImmutable.ZERO;
setLastTarget(GameObjectType.unknown, 0);
this.follow = false;
}
/**
* @ Calculates Atr (both hands) Defense, and Damage for pc
*/
public void calculateAtrDefenseDamage() {
if (this.charItemManager == null || this.charItemManager.getEquipped() == null || this.skills == null) {
Logger.error("Player " + this.getObjectUUID() + " missing skills or equipment");
defaultAtrAndDamage(true);
defaultAtrAndDamage(false);
this.defenseRating = 0;
return;
}
ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
// // Reset passives
// if (this.bonuses != null) {
// this.bonuses.setBool("Block", false);
// this.bonuses.setBool("Parry", false);
// if (this.baseClass != null && this.baseClass.getUUID() == 2502)
// this.bonuses.setBool("Dodge", true);
// else
// this.bonuses.setBool("Dodge", false);
// }
// calculate atr and damage for each hand
calculateAtrDamageForWeapon(equipped.get(MBServerStatics.SLOT_MAINHAND), true, equipped.get(MBServerStatics.SLOT_OFFHAND));
calculateAtrDamageForWeapon(equipped.get(MBServerStatics.SLOT_OFFHAND), false, equipped.get(MBServerStatics.SLOT_MAINHAND));
// No Defense while in DeathShroud
if (this.effects != null && this.effects.containsKey("DeathShroud"))
this.defenseRating = (short) 0;
else {
// calculate defense for equipment
float defense = this.statDexCurrent * 2;
defense += getShieldDefense(equipped.get(MBServerStatics.SLOT_OFFHAND));
defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_HELMET));
defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_CHEST));
defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_ARMS));
defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_GLOVES));
defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_LEGGINGS));
defense += getArmorDefense(equipped.get(MBServerStatics.SLOT_FEET));
defense += getWeaponDefense(equipped);
if (this.bonuses != null) {
// add any bonuses
defense += (short) this.bonuses.getFloat(ModType.DCV, SourceType.None);
// Finally multiply any percent modifiers. DO THIS LAST!
float pos_Bonus = this.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 = this.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;
this.defenseRating = (short) (defense + 0.5f);
}
}
/**
* @ Calculates Atr, and Damage for each weapon
*/
private void calculateAtrDamageForWeapon(Item weapon, boolean mainHand, Item otherHand) {
// make sure weapon exists
boolean noWeapon = false;
ItemBase wb = null;
if (weapon == null)
noWeapon = true;
else {
ItemBase ib = weapon.getItemBase();
if (ib == null)
noWeapon = true;
else if (!ib.getType().equals(ItemType.WEAPON)) {
defaultAtrAndDamage(mainHand);
return;
} else
wb = ib;
}
float skillPercentage, masteryPercentage;
float mastDam;
float min, max;
float speed = 20f;
boolean strBased = false;
ItemBase wbMain = (weapon != null) ? weapon.getItemBase() : null;
ItemBase wbOff = (otherHand != null) ? otherHand.getItemBase() : null;
// get skill percentages and min and max damage for weapons
if (noWeapon) {
if (mainHand) {
Item off = this.charItemManager.getEquipped().get(MBServerStatics.SLOT_OFFHAND);
if (off != null && off.getItemBase() != null && off.getItemBase().getType().equals(ItemType.WEAPON))
this.rangeHandOne = 10 * (1 + (this.statStrBase / 600)); // Set
// to
// no
// weapon
// range
else
this.rangeHandOne = -1; // set to do not attack
} else
this.rangeHandTwo = -1; // set to do not attack
skillPercentage = PlayerManager.getModifiedAmount(this.skills.get("Unarmed Combat"));
masteryPercentage = PlayerManager.getModifiedAmount(this.skills.get("Unarmed Combat Mastery"));
if (masteryPercentage == 0f)
mastDam = CharacterSkill.getQuickMastery(this, "Unarmed Combat Mastery");
else
mastDam = masteryPercentage;
// TODO Correct these
min = 1;
max = 3;
} else {
if (mainHand)
this.rangeHandOne = weapon.getItemBase().getRange() * (1 + (this.statStrBase / 600));
else
this.rangeHandTwo = weapon.getItemBase().getRange() * (1 + (this.statStrBase / 600));
if (this.bonuses != null) {
float range_bonus = 1 + this.bonuses.getFloatPercentAll(ModType.WeaponRange, SourceType.None);
if (mainHand)
this.rangeHandOne *= range_bonus;
else
this.rangeHandTwo *= range_bonus;
}
skillPercentage = PlayerManager.getModifiedAmount(this.skills.get(wb.getSkillRequired()));
masteryPercentage = PlayerManager.getModifiedAmount(this.skills.get(wb.getMastery()));
if (masteryPercentage == 0f)
mastDam = 0f;
// mastDam = CharacterSkill.getQuickMastery(this, wb.getMastery());
else
mastDam = masteryPercentage;
min = (float) wb.getMinDamage();
max = (float) wb.getMaxDamage();
strBased = wb.isStrBased();
//
// Add parry bonus for weapon and allow parry if needed
// // Only Fighters and Thieves can Parry
// if ((this.baseClass != null && this.baseClass.getUUID() == 2500)
// || (this.promotionClass != null && this.promotionClass.getUUID() == 2520)) {
// if (wbMain == null || wbMain.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
// if (wbOff == null || wbOff.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
// this.bonuses.setBool("Parry", true);
// }
// }
}
if (this.effects != null && this.effects.containsKey("DeathShroud"))
// No Atr in deathshroud.
if (mainHand)
this.atrHandOne = (short) 0;
else
this.atrHandTwo = (short) 0;
else {
// calculate atr
float atr = 0;
atr += (int) skillPercentage * 4f; //<-round down skill% -
atr += (int) masteryPercentage * 3f;
if (this.statStrCurrent > this.statDexCurrent)
atr += statStrCurrent / 2;
else
atr += statDexCurrent / 2;
// add in any bonuses to atr
if (this.bonuses != null) {
// Add any base bonuses
atr += this.bonuses.getFloat(ModType.OCV, SourceType.None);
// Finally use any multipliers. DO THIS LAST!
float pos_Bonus = (1 + this.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 = this.bonuses.getFloatPercentNegative(ModType.OCV, SourceType.None);
atr *= (1 + neg_Bonus);
}
atr = (atr < 1) ? 1 : atr;
// set atr
if (mainHand)
this.atrHandOne = (short) (atr + 0.5f);
else
this.atrHandTwo = (short) (atr + 0.5f);
}
//calculate speed
if (wb != null)
speed = wb.getSpeed();
else
speed = 20f; //unarmed attack speed
if (weapon != null)
speed *= (1 + this.bonuses.getFloatPercentAll(ModType.WeaponSpeed, SourceType.None));
speed *= (1 + this.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) {
ItemBase ibo = otherHand.getItemBase();
if (ibo != null && ibo.getType().equals(ItemType.WEAPON)) {
min *= 0.7f;
max *= 0.7f;
}
}
// calculate damage
float minDamage;
float maxDamage;
float pri = (strBased) ? (float) this.statStrCurrent : (float) this.statDexCurrent;
float sec = (strBased) ? (float) this.statDexCurrent : (float) this.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 (this.effects != null && this.effects.containsKey("DeathShroud")) {
minDamage *= 0.5f;
maxDamage *= 0.5f;
}
// add in any bonuses to damage
if (this.bonuses != null) {
// Add any base bonuses
minDamage += this.bonuses.getFloat(ModType.MinDamage, SourceType.None);
maxDamage += this.bonuses.getFloat(ModType.MaxDamage, SourceType.None);
minDamage += this.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None);
maxDamage += this.bonuses.getFloat(ModType.MeleeDamageModifier, SourceType.None);
// Finally use any multipliers. DO THIS LAST!
float percentMinDamage = 1;
float percentMaxDamage = 1;
percentMinDamage += this.bonuses.getFloatPercentAll(ModType.MinDamage, SourceType.None);
percentMinDamage += this.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None);
percentMaxDamage += this.bonuses.getFloatPercentAll(ModType.MaxDamage, SourceType.None);
percentMaxDamage += this.bonuses.getFloatPercentAll(ModType.MeleeDamageModifier, SourceType.None);
minDamage *= percentMinDamage;
maxDamage *= percentMaxDamage;
}
// set damages
if (mainHand) {
this.minDamageHandOne = (int) minDamage;
this.maxDamageHandOne = (int) maxDamage;
this.speedHandOne = speed;
} else {
this.minDamageHandTwo = (int) minDamage;
this.maxDamageHandTwo = (int) maxDamage;
this.speedHandTwo = speed;
}
}
/**
* @ Calculates Defense for shield
*/
private float getShieldDefense(Item shield) {
if (shield == null)
return 0;
ItemBase ab = shield.getItemBase();
if (ab == null || !ab.isShield())
return 0;
CharacterSkill blockSkill = this.skills.get("Block");
float skillMod;
if (blockSkill == null) {
skillMod = 0;
} else
skillMod = blockSkill.getModifiedAmount();
float def = ab.getDefense();
//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)));
}
public void setPassives() {
if (this.bonuses != null) {
ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
Item off = equipped.get(MBServerStatics.SLOT_OFFHAND);
Item main = equipped.get(MBServerStatics.SLOT_MAINHAND);
ItemBase wbMain = null;
ItemBase wbOff = null;
if (main != null)
wbMain = main.getItemBase();
if (off != null)
wbOff = off.getItemBase();
//set block if block found
this.bonuses.setBool(ModType.Block, SourceType.None, false);
if (this.baseClass != null && (this.baseClass.getObjectUUID() == 2500 || this.baseClass.getObjectUUID() == 2501))
if (off != null && off.getItemBase() != null && off.getItemBase().isShield())
this.bonuses.setBool(ModType.Block, SourceType.None, true);
//set dodge if rogue
if (this.baseClass != null && this.baseClass.getObjectUUID() == 2502)
this.bonuses.setBool(ModType.Dodge, SourceType.None, true);
else
this.bonuses.setBool(ModType.Dodge, SourceType.None, false);
//set parry if fighter or thief and no invalid weapon found
this.bonuses.setBool(ModType.Parry, SourceType.None, false);
if ((this.baseClass != null && this.baseClass.getObjectUUID() == 2500)
|| (this.promotionClass != null && this.promotionClass.getObjectUUID() == 2520))
if (wbMain == null || wbMain.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
if (wbOff == null || wbOff.getRange() < MBServerStatics.RANGED_WEAPON_RANGE)
this.bonuses.setBool(ModType.Parry, SourceType.None, true);
}
}
/**
* @ Calculates Defense for armor
*/
private float getArmorDefense(Item armor) {
if (armor == null)
return 0;
ItemBase ib = armor.getItemBase();
if (ib == null)
return 0;
if (!ib.getType().equals(ItemType.ARMOR))
return 0;
if (ib.getSkillRequired().isEmpty())
return ib.getDefense();
CharacterSkill armorSkill = this.skills.get(ib.getSkillRequired());
if (armorSkill == null) {
Logger.error("Player " + this.getObjectUUID()
+ " has armor equipped without the nescessary skill to equip it");
return ib.getDefense();
}
float def = ib.getDefense();
//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 float getWeaponDefense(ConcurrentHashMap<Integer, Item> equipped) {
Item weapon = equipped.get(MBServerStatics.SLOT_MAINHAND);
ItemBase wb = null;
CharacterSkill skill, mastery;
float val = 0;
boolean unarmed = false;
if (weapon == null) {
weapon = equipped.get(MBServerStatics.SLOT_OFFHAND);
if (weapon == null || weapon.getItemBase().isShield())
unarmed = true;
else
wb = weapon.getItemBase();
} else
wb = weapon.getItemBase();
if (wb == null)
unarmed = true;
if (unarmed) {
skill = this.skills.get("Unarmed Combat");
mastery = this.skills.get("Unarmed Combat Mastery");
} else {
skill = this.skills.get(wb.getSkillRequired());
mastery = this.skills.get(wb.getMastery());
}
if (skill != null)
val += (int) skill.getModifiedAmount() / 2f;
if (mastery != null)
val += (int) mastery.getModifiedAmount() / 2f;
return val;
}
//Call this function to recalculate granted skills and powers for player
public synchronized void calculateSkills() {
//tell the player to applyBonuses because something has changed
runSkillCalc();
//start running the skill/power calculations
}
//Don't call this function directly. linked from pc.calculateSkills()
//through SkillCalcJob. Designed to only run from one worker thread
public void runSkillCalc() {
try {
//see if any new skills or powers granted
CharacterSkill.calculateSkills(this);
// calculate granted Trains in powers.
CharacterPower.grantTrains(this);
//see if any new powers unlocked from previous check
CharacterPower.calculatePowers(this);
} catch (Exception e) {
}
}
//calculate item bonuses here
public void calculateItemBonuses() {
if (this.charItemManager == null || this.bonuses == null)
return;
ConcurrentHashMap<Integer, Item> equipped = this.charItemManager.getEquipped();
for (Item item : equipped.values()) {
ItemBase ib = item.getItemBase();
if (ib == null)
continue;
//TODO add effect bonuses in here for equipped items
}
}
/**
* @ Defaults ATR, Defense and Damage for player
*/
private void defaultAtrAndDamage(boolean mainHand) {
if (mainHand) {
this.atrHandOne = 0;
this.minDamageHandOne = 0;
this.maxDamageHandOne = 0;
this.rangeHandOne = -1;
this.speedHandOne = 20;
} else {
this.atrHandTwo = 0;
this.minDamageHandTwo = 0;
this.maxDamageHandTwo = 0;
this.rangeHandTwo = -1;
this.speedHandTwo = 20;
}
}
public void calculateMaxHealthManaStamina() {
float h = 1f;
float m = 0f;
float s = 0f;
float baseHealth = 15f;
float baseMana = 5f;
float baseStamina = 1f;
float promoHealth = 0f;
float promoMana = 0f;
float promoStamina = 0f;
float raceHealth = 0f;
float raceMana = 0f;
float raceStamina = 0f;
float toughness = 0f;
float athletics = 0f;
//get baseclass modifiers
if (this.baseClass != null) {
baseHealth = this.baseClass.getHealthMod();
baseMana = this.baseClass.getManaMod();
baseStamina = this.baseClass.getStaminaMod();
} else {
//TODO log error here
}
//get promotion modifiers
if (this.promotionClass != null) {
promoHealth = this.promotionClass.getHealthMod();
promoMana = this.promotionClass.getManaMod();
promoStamina = this.promotionClass.getStaminaMod();
}
// next get racial modifer
if (this.race != null) {
raceHealth += this.race.getHealthBonus();
raceMana += this.race.getManaBonus();
raceStamina += this.race.getStaminaBonus();
} else {
//TODO log error here
}
//Get level modifers
float f = 0;
float g = 0;
if (this.level < 10 || this.promotionClass == null)
f = this.level;
else if (this.level < 20) {
f = this.level;
g = this.level - 9;
} else if (level < 30) {
f = (float) (19 + (this.level - 19) * 0.8);
g = (float) (10 + (this.level - 19) * 0.8);
} else if (level < 40) {
f = (float) (27 + (this.level - 29) * 0.6);
g = (float) (18 + (this.level - 29) * 0.6);
} else if (level < 50) {
f = (float) (33 + (this.level - 39) * 0.4);
g = (float) (24 + (this.level - 39) * 0.4);
} else if (level < 60) {
f = (float) (37 + (this.level - 49) * 0.2);
g = (float) (28 + (this.level - 49) * 0.2);
} else {
f = (float) (39 + (this.level - 59) * 0.1);
g = (float) (30 + (this.level - 59) * 0.1);
}
//get toughness and athletics amount
if (this.skills != null) {
if (this.skills.containsKey("Toughness"))
toughness = this.skills.get("Toughness").getModifiedAmount();
if (this.skills.containsKey("Athletics"))
athletics = this.skills.get("Athletics").getModifiedAmount();
}
h = (((f * baseHealth) + (g * promoHealth)) * (0.3f + (0.005f * this.statConCurrent)) + (this.statConCurrent + raceHealth)) * (1 + (int) toughness / 400f);
m = ((f * baseMana) + (g * promoMana)) * (0.3f + (0.005f * this.statSpiCurrent)) + (this.statSpiCurrent + raceMana);
s = (((f * baseStamina) + (g * promoStamina)) * (0.3f + (0.005f * this.statConCurrent)) + (this.statConCurrent + raceStamina)) * (1 + (int) athletics / 300f);
// s = f * (baseStamina + 1.75f) * .5f + this.statConCurrent + raceStamina;
// Apply any bonuses from runes and effects
if (this.bonuses != null) {
//apply effects
h += this.bonuses.getFloat(ModType.HealthFull, SourceType.None);
m += this.bonuses.getFloat(ModType.ManaFull, SourceType.None);
s += this.bonuses.getFloat(ModType.StaminaFull, SourceType.None);
h *= (1 + this.bonuses.getFloatPercentAll(ModType.HealthFull, SourceType.None));
m *= (1 + this.bonuses.getFloatPercentAll(ModType.ManaFull, SourceType.None));
s *= (1 + this.bonuses.getFloatPercentAll(ModType.StaminaFull, SourceType.None));
}
// Set max health, mana and stamina
if (h > 0)
this.healthMax = h;
else
this.healthMax = 1;
if (m > -1)
this.manaMax = m;
else
this.manaMax = 0;
if (s > -1)
this.staminaMax = s;
else
this.staminaMax = 0;
// Update health, mana and stamina if needed
if (this.getCurrentHitpoints() > this.healthMax)
this.setHealth(this.healthMax);
if (this.mana.get() > this.manaMax)
this.mana.set(this.manaMax);
if (this.stamina.get() > this.staminaMax)
this.stamina.set(staminaMax);
}
@Override
public float getPassiveChance(String type, int attackerLevel, boolean fromCombat) {
if (this.skills == null || this.bonuses == null)
return 0f;
ModType modType = ModType.GetModType(type);
// must be allowed to use this passive
if (!this.bonuses.getBool(modType, SourceType.None))
return 0f;
// must not be stunned
if (this.bonuses.getBool(ModType.Stunned, SourceType.None))
return 0f;
// Get base skill amount
CharacterSkill sk = this.skills.get(type);
float amount;
if (sk == null)
amount = CharacterSkill.getQuickMastery(this, type);
else
amount = sk.getModifiedAmount();
// Add bonuses
amount += this.bonuses.getFloat(modType, SourceType.None);
// Add item bonuses and return
if (type.equals(ModType.Dodge) && !fromCombat)
return ((amount / 4) - attackerLevel + this.getLevel()) / 4;
else
return (amount - attackerLevel + this.getLevel()) / 4;
}
public float getPassiveChance1(ModType modType, SourceType sourceType, int attackerLevel, boolean fromCombat) {
if (this.skills == null || this.bonuses == null)
return 0f;
// must be allowed to use this passive
if (!this.bonuses.getBool(modType, sourceType))
return 0f;
// must not be stunned
if (this.bonuses.getBool(ModType.Stunned, SourceType.None))
return 0f;
// Get base skill amount
CharacterSkill sk = this.skills.get(sourceType.name());
float amount;
if (sk == null)
amount = CharacterSkill.getQuickMastery(this, modType.name());
else
amount = sk.getModifiedAmount();
// Add bonuses
amount += this.bonuses.getFloat(modType, sourceType);
// Add item bonuses and return
if (sourceType.equals(SourceType.Dodge) && !fromCombat)
return ((amount / 4) - attackerLevel + this.getLevel()) / 4;
else
return (amount - attackerLevel + this.getLevel()) / 4;
}
public float getRegenModifier(ModType type) {
float regen = 1f;
if (this.bonuses != null)
// get regen bonus from effects
regen = this.bonuses.getRegen(type);
return regen;
}
@Override
public boolean canBeLooted() {
return !this.isAlive();
}
@Override
public void removeFromCache() {
Logger.info("Removing " + this.getName() + " from Object Cache.");
for (Item e : this.charItemManager.getEquipped().values()) {
e.removeFromCache();
}
for (Item i : this.charItemManager.getInventory(true)) {
i.removeFromCache();
}
for (Item b : this.charItemManager.getBank()) {
b.removeFromCache();
}
if (this.account.getLastCharIDUsed() == this.getObjectUUID())
for (Item v : this.charItemManager.getVault()) {
v.removeFromCache();
}
for (CharacterSkill cs : this.getSkills().values()) {
cs.removeFromCache();
}
for (CharacterPower ps : this.getPowers().values()) {
ps.removeFromCache();
}
for (CharacterRune cr : this.runes) {
cr.removeFromCache();
}
super.removeFromCache();
}
@Override
public void updateDatabase() {
}
@Override
public void runAfterLoad() {
// Init inventory
this.charItemManager = new CharacterItemManager(this);
Bounds playerBounds = Bounds.borrow();
playerBounds.setBounds(this.getLoc());
this.setBounds(playerBounds);
}
@Override
public ConcurrentHashMap<Integer, CharacterPower> initializePowers() {
return DbManager.CharacterPowerQueries.GET_POWERS_FOR_CHARACTER(this);
}
@Override
public final void setFirstName(final String name) {
super.setFirstName(name);
}
@Override
public void setLastName(final String name) {
super.setLastName(name);
}
@Override
public short getLevel() {
return this.getPCLevel();
}
@Override
public void setLevel(short targetLevel) {
short tmpLevel;
tmpLevel = targetLevel;
tmpLevel = (short) Math.min(tmpLevel, 75);
while (this.level < tmpLevel) {
PlayerManager.grantXP(this, Experience.getBaseExperience(tmpLevel) - this.exp);
}
}
@Override
public boolean asciiLastName() {
return this._asciiLastName();
}
@Override
public void setGuild(Guild value) {
if (value == null)
value = Guild.getErrantGuild();
int guildID = 0;
if (!value.isEmptyGuild())
guildID = value.getObjectUUID();
DbManager.PlayerCharacterQueries.UPDATE_GUILD(this, guildID);
super.setGuild(value);
// Player changed guild so let's invalidate the login server
// cache to reflect this event.
//Update player bind location;
Building cityTol = null;
if (value.getOwnedCity() != null)
cityTol = value.getOwnedCity().getTOL();
this.setBindBuildingID(cityTol != null ? cityTol.getObjectUUID() : 0);
//update binds, checks for nation tol if guild tol == null;
PlayerManager.getUpdatedBindBuilding(this);
DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(this.getObjectUUID(), "character");
}
public long getSummoner(int summoner) {
synchronized (this.summoners) {
if (!this.summoners.containsKey(summoner))
return 0;
return this.summoners.get(summoner);
}
}
public void addSummoner(int summoner, long time) {
synchronized (this.summoners) {
this.summoners.put(summoner, time);
}
}
public void removeSummoner(int summoner) {
synchronized (this.summoners) {
if (this.summoners.containsKey(summoner))
this.summoners.remove(summoner);
}
}
private double getDeltaTime() {
return (System.currentTimeMillis() - lastUpdateTime) * .001f;
}
private double getStamDeltaTime() {
return (System.currentTimeMillis() - lastStamUpdateTime) * .001f;
}
@Override
public void update(Boolean newSystem) {
//if(!newSystem)
// return;
if (this.updateLock.writeLock().tryLock()) {
try {
if (!this.isAlive() && this.isEnteredWorld()) {
if(!this.timestamps.containsKey("DeathTime")){
this.timestamps.put("DeathTime",System.currentTimeMillis());
}else if((System.currentTimeMillis() - this.timestamps.get("DeathTime")) > 600000)
PlayerManager.forceRespawn(this);
return;
}
this.updateLocation();
this.updateMovementState();
this.updateRegen();
if (this.getStamina() < 10) {
if (this.getAltitude() > 0 || this.getDesiredAltitude() > 0) {
PlayerManager.GroundPlayer(this);
this.updateRegen();
}
}
RealmMap.updateRealm(this);
PlayerManager.updateBlessingMessage(this);
this.safeZone = this.isInSafeZone();
if(!this.timestamps.containsKey("nextBoxCheck"))
this.timestamps.put("nextBoxCheck", System.currentTimeMillis() + 10000);
if(!this.isBoxed && this.timestamps.get("nextBoxCheck") < System.currentTimeMillis()) {
this.isBoxed = PlayerManager.checkIfBoxed(this);
this.timestamps.put("nextBoxCheck", System.currentTimeMillis() + 10000);
}
if(this.level < 10 && this.enteredWorld) {
// this.setLevel((short) 10);
while (this.level < 10) {
PlayerManager.grantXP(this, Experience.getBaseExperience(this.level + 1) - this.exp);
}
if(this.charItemManager != null && this.charItemManager.getGoldInventory() != null && this.charItemManager.getGoldInventory().getNumOfItems() < 1000) {
this.getCharItemManager().addGoldToInventory(1000, false);
this.getCharItemManager().addItemToInventory(new MobLoot(this, ItemBase.getItemBase(980066), 1, false).promoteToItem(this));
this.getCharItemManager().updateInventory();
}
}
if(this.isBoxed && !this.containsEffect(1672601862)) {
PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, 1672601862, 40, false);
}
if(PlayerManager.isFlying(this)){
//if (!AbstractCharacter.CanFly(this)) {
if(this.effects.containsKey("MoveBuff")){
PlayerManager.GroundPlayer(this);
//ChatManager.chatSystemInfo(this, "You Cannot Fly While Having A MovementBuff");
}
}
} catch (Exception e) {
Logger.error(e);
} finally {
this.updateLock.writeLock().unlock();
}
}
}
@Override
public void updateFlight() {
if (this.getAltitude() == 0 && this.getTakeOffTime() == 0)
return;
if (this.getTakeOffTime() == 0)
return;
if (this.getAltitude() == this.getDesiredAltitude()) {
if (this.getDesiredAltitude() == 0)
this.syncClient();
//landing in a building, mark altitude to 0 as player is no longer flying.
if (this.landingRegion != null) {
this.altitude = 0;
this.region = this.landingRegion;
this.loc = this.loc.setY(this.landingRegion.lerpY(this));
} else
this.altitude = this.getDesiredAltitude();
this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude());
this.setTakeOffTime(0);
MovementManager.finishChangeAltitude(this, this.getDesiredAltitude());
return;
}
this.loc = this.loc.setY(HeightMap.getWorldHeight(this) + this.getAltitude());
}
@Override
public void updateLocation() {
if (!this.isMoving())
return;
if (!this.isActive)
return;
Vector3fImmutable newLoc = this.getMovementLoc();
if (this.isAlive() == false || this.getBonuses().getBool(ModType.Stunned, SourceType.None) || this.getBonuses().getBool(ModType.CannotMove, SourceType.None)) {
//Target is stunned or rooted. Don't move
this.stopMovement(newLoc);
this.region = AbstractWorldObject.GetRegionByWorldObject(this);
return;
}
if (newLoc.equals(this.getEndLoc())) {
this.stopMovement(newLoc);
this.region = AbstractWorldObject.GetRegionByWorldObject(this);
if (this.getDebug(1))
ChatManager.chatSystemInfo(this,
"Arrived at End location. " + this.getEndLoc());
return;
//Next upda
}
setLoc(newLoc);
this.region = AbstractWorldObject.GetRegionByWorldObject(this);
if (this.getDebug(1))
ChatManager.chatSystemInfo(this,
"Distance to target " + this.getEndLoc().distance2D(this.getLoc()) + " speed " + this.getSpeed());
if (this.getStamina() < 10)
MovementManager.sendOOS(this);
// if (MBServerStatics.MOVEMENT_SYNC_DEBUG || this.getDebug(1))
// Logger.info("MovementManager", "Updating movement current loc:" + this.getLoc().getX() + " " + this.getLoc().getZ()
// + " end loc: " + this.getEndLoc().getX() + " " + this.getEndLoc().getZ() + " distance " + this.getEndLoc().distance2D(this.getLoc()));
}
@Override
public void updateMovementState() {
if (this.enteredWorld) {
if (!this.lastSwimming) {
boolean enterWater = PlayerManager.enterWater(this);
if (enterWater) {
this.lastSwimming = enterWater;
MovementManager.sendRWSSMsg(this);
}
} else {
if (PlayerManager.LeaveWater(this)) {
this.lastSwimming = false;
if (!this.isMoving())
MovementManager.sendRWSSMsg(this);
}
}
boolean breathe = PlayerManager.CanBreathe(this);
if (breathe != this.canBreathe) {
this.canBreathe = breathe;
// ChatManager.chatSystemInfo(this, "Breathe : " + this.canBreathe);
this.syncClient();
}
}
//char is flying
if (PlayerManager.isFlying(this) == true) {
this.movementState = MovementState.FLYING;
return;
}
// Char is not moving. Set sitting or idle
if (!this.isMoving()) {
if (this.sit == true)
this.movementState = MovementState.SITTING;
else
this.movementState = MovementState.IDLE;
return;
} else {
this.movementState = MovementState.RUNNING;
}
// Char is swimming // we now are saving lastSwimstate boolean, use this instead of calling getSwimming again.
if (this.lastSwimming == true) {
this.movementState = MovementState.SWIMMING;
return;
}
// Char is moving, yet not swimming or flying he must be running
this.movementState = MovementState.RUNNING;
}
@Override
public void updateRegen() {
float healthRegen = 0f;
float manaRegen = 0f;
float stamRegen = 0f;
boolean updateClient = false;
// Early exit if char is dead or disconnected
if ((this.isAlive() == false)
|| (this.isActive() == false) || this.getLoc().x == 0 && this.getLoc().z == 0)
return;
// Calculate Regen amount from last simulation tick
switch (this.movementState) {
case IDLE:
healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_IDLE) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * (getRegenModifier(ModType.HealthRecoverRate));
if (this.isCasting() || this.isItemCasting())
healthRegen *= .75f;
// Characters regen mana when in only walk mode and idle
if (this.walkMode)
manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_IDLE) * getRegenModifier(ModType.ManaRecoverRate));
else if (!this.isCasting() && !this.isItemCasting())
manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_IDLE) * getRegenModifier(ModType.ManaRecoverRate));
else
manaRegen = 0;
if (!PlayerManager.CanBreathe(this))
stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
else if ((!this.isCasting() && !this.isItemCasting()) || this.lastMovementState.equals(MovementState.FLYING))
stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
else
stamRegen = 0;
break;
case SITTING:
healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_SIT) + MBServerStatics.HEALTH_REGEN_SIT_STATIC) * getRegenModifier(ModType.HealthRecoverRate);
manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_SIT) * (getRegenModifier(ModType.ManaRecoverRate));
stamRegen = MBServerStatics.STAMINA_REGEN_SIT * getRegenModifier(ModType.StaminaRecoverRate);
break;
case RUNNING:
if (this.walkMode == true) {
healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate);
manaRegen = this.manaMax * MBServerStatics.MANA_REGEN_WALK * getRegenModifier(ModType.ManaRecoverRate);
stamRegen = MBServerStatics.STAMINA_REGEN_WALK;
} else {
healthRegen = 0;
manaRegen = 0;
if (this.combat == true)
stamRegen = MBServerStatics.STAMINA_REGEN_RUN_COMBAT;
else
stamRegen = MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT;
}
break;
case FLYING:
float seventyFive = this.staminaMax * .75f;
float fifty = this.staminaMax * .5f;
float twentyFive = this.staminaMax * .25f;
if (this.getDesiredAltitude() == 0 && this.getAltitude() <= 10) {
if (this.isCombat())
stamRegen = 0;
else
stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
} else if (!this.useFlyMoveRegen()) {
healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_IDLE) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * (getRegenModifier(ModType.HealthRecoverRate));
if (this.isCasting() || this.isItemCasting())
healthRegen *= .75f;
// Characters regen mana when in only walk mode and idle
if (this.walkMode)
manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_IDLE + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate));
else if (!this.isCasting() && !this.isItemCasting())
manaRegen = (this.manaMax * MBServerStatics.MANA_REGEN_IDLE + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate));
else
manaRegen = 0;
if (!this.isItemCasting() && !this.isCasting() || this.getTakeOffTime() != 0)
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_IDLE;
else
stamRegen = -1f;
} else if (this.walkMode == true) {
healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate);
manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_WALK) + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate));
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_WALK;
} else {
healthRegen = 0;
manaRegen = 0;
if (this.isCombat())
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN_COMBAT;
else
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN;
}
float oldStamina = this.stamina.get();
if (FastMath.between(oldStamina, 0, twentyFive) && !this.wasTripped25) {
updateClient = true;
this.wasTripped25 = true;
this.wasTripped50 = false;
this.wasTripped75 = false;
} else if (FastMath.between(oldStamina, twentyFive, fifty) && !this.wasTripped50) {
updateClient = true;
this.wasTripped25 = false;
this.wasTripped50 = true;
this.wasTripped75 = false;
} else if (FastMath.between(oldStamina, fifty, seventyFive) && !this.wasTripped75) {
updateClient = true;
this.wasTripped25 = false;
this.wasTripped50 = false;
this.wasTripped75 = true;
}
break;
case SWIMMING:
if (this.walkMode == true) {
healthRegen = ((this.healthMax * MBServerStatics.HEALTH_REGEN_WALK) + MBServerStatics.HEALTH_REGEN_IDLE_STATIC) * getRegenModifier(ModType.HealthRecoverRate);
manaRegen = ((this.manaMax * MBServerStatics.MANA_REGEN_WALK) + (this.getSpiMod() * .015f)) * (getRegenModifier(ModType.ManaRecoverRate));
stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
} else {
healthRegen = 0;
manaRegen = 0;
stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
if (this.combat == true)
stamRegen += MBServerStatics.STAMINA_REGEN_RUN_COMBAT;
else
stamRegen += MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT;
}
break;
}
// Are we drowning?
if ((this.getStamina() <= 0)
&& (PlayerManager.CanBreathe(this) == false))
healthRegen = (this.healthMax * -.03f);
// Multiple regen values by current deltaTime
// Logger.info("", healthRegen + "");
healthRegen *= getDeltaTime();
manaRegen *= getDeltaTime();
stamRegen *= getStamDeltaTime();
boolean workedHealth = false;
boolean workedMana = false;
boolean workedStamina = false;
float old, mod;
while (!workedHealth || !workedMana || !workedStamina) {
if (!this.isAlive() || !this.isActive())
return;
if (!workedHealth) {
old = this.health.get();
mod = old + healthRegen;
if (mod > this.healthMax)
mod = healthMax;
else if (mod <= 0) {
if (this.isAlive.compareAndSet(true, false))
killCharacter("Water");
return;
}
workedHealth = this.health.compareAndSet(old, mod);
}
if (!workedStamina) {
old = this.stamina.get();
mod = old + stamRegen;
if (mod > this.staminaMax)
mod = staminaMax;
else if (mod < 0)
mod = 0;
workedStamina = this.stamina.compareAndSet(old, mod);
}
if (!workedMana) {
old = this.mana.get();
mod = old + manaRegen;
if (mod > this.manaMax)
mod = manaMax;
else if (mod < 0)
mod = 0;
workedMana = this.mana.compareAndSet(old, mod);
}
}
if (updateClient)
this.syncClient();
// Reset this char's frame time.
this.lastUpdateTime = System.currentTimeMillis();
this.lastStamUpdateTime = System.currentTimeMillis();
}
public synchronized void updateStamRegen(long time) {
boolean disable = true;
if (disable)
return;
float stamRegen = 0f;
// Early exit if char is dead or disconnected
if ((this.isAlive() == false)
|| (this.isActive() == false) || this.getLoc().x == 0 && this.getLoc().z == 0)
return;
// Calculate Regen amount from last simulation tick
switch (this.movementState) {
case IDLE:
if (!PlayerManager.CanBreathe(this))
stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
else if ((!this.isCasting() && !this.isItemCasting()) || this.lastMovementState.equals(MovementState.FLYING))
stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
else
stamRegen = 0;
break;
case SITTING:
stamRegen = MBServerStatics.STAMINA_REGEN_SIT * getRegenModifier(ModType.StaminaRecoverRate);
break;
case RUNNING:
if (this.walkMode == true) {
stamRegen = MBServerStatics.STAMINA_REGEN_WALK;
} else {
if (this.combat == true)
stamRegen = MBServerStatics.STAMINA_REGEN_RUN_COMBAT;
else
stamRegen = MBServerStatics.STAMINA_REGEN_RUN_NONCOMBAT;
}
break;
case FLYING:
if (this.getDesiredAltitude() == 0 && this.getAltitude() <= 10) {
if (this.isCombat())
stamRegen = 0;
else
stamRegen = MBServerStatics.STAMINA_REGEN_IDLE * getRegenModifier(ModType.StaminaRecoverRate);
} else if (!this.isMoving()) {
if (!this.isItemCasting() && !this.isCasting() || this.getTakeOffTime() != 0)
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_IDLE;
else
stamRegen = -1f;
} else if (this.walkMode == true) {
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_WALK;
} else {
if (this.isCombat())
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN_COMBAT;
else
stamRegen = MBServerStatics.STAMINA_REGEN_FLY_RUN;
}
break;
case SWIMMING:
if (this.walkMode == true) {
stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
} else {
stamRegen = MBServerStatics.STAMINA_REGEN_SWIM;
}
break;
}
// Multiple regen values by current deltaTime
// Logger.info("", healthRegen + "");
stamRegen *= (time * .001f);
boolean workedStamina = false;
float old, mod;
while (!workedStamina) {
if (!this.isAlive() || !this.isActive())
return;
if (!workedStamina) {
old = this.stamina.get();
mod = old + stamRegen;
if (mod > this.staminaMax)
mod = staminaMax;
else if (mod < 0)
mod = 0;
workedStamina = this.stamina.compareAndSet(old, mod);
}
}
}
public void syncClient() {
ModifyHealthMsg modifyHealthMsg = new ModifyHealthMsg(null, this, 0, 1, 1, -1984683793, "", 0, 652920987);
//mhm.setOmitFromChat(0);
Dispatch dispatch = Dispatch.borrow(this, modifyHealthMsg);
DispatchMessage.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
}
public MovementState getMovementState() {
return movementState;
}
public String getHash() {
return hash;
}
public void setHash() {
this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID());
// Write hash to player character table
DataWarehouse.writeHash(DataRecordType.CHARACTER, this.getObjectUUID());
}
public AtomicInteger getGuildStatus() {
return guildStatus;
}
public int getLastRealmID() {
return lastRealmID;
}
public void setLastRealmID(int lastRealmID) {
this.lastRealmID = lastRealmID;
}
public int getSubRaceID() {
return subRaceID;
}
public void setSubRaceID(int subRaceID) {
this.subRaceID = subRaceID;
}
public ArrayList<GuildHistory> getGuildHistory() {
return guildHistory;
}
public void setGuildHistory(ArrayList<GuildHistory> guildHistory) {
this.guildHistory = guildHistory;
}
public void updateScaleHeight() {
float strengthScale = 0;
float unknownScale1 = 0;
float unknownScale2 = 0;
float unknownScale3 = 0;
float scaleHeight = 0;
if ((int) this.statStrBase > 40)
strengthScale = ((int) this.statStrBase - 40) * 0.0024999999f; //Y scale ?
unknownScale1 = (float) (((int) this.statStrBase * 0.0024999999f + strengthScale + 0.89999998) * race.getRaceType().getScaleHeight());
strengthScale = (int) this.statStrBase * 0.0037499999f + strengthScale + 0.85000002f; //strengthScale is different for x and z
unknownScale2 = strengthScale * race.getRaceType().getScaleHeight(); //x scale?
unknownScale3 = strengthScale * race.getRaceType().getScaleHeight(); //z Scale?
scaleHeight = (1.5f + unknownScale1);
this.characterHeight = scaleHeight;
this.centerHeight = scaleHeight;
}
public int getOverFlowEXP() {
return overFlowEXP;
}
public void setOverFlowEXP(int overFlowEXP) {
this.overFlowEXP = overFlowEXP;
}
public void setLastMovementState(MovementState lastMovementState) {
this.lastMovementState = lastMovementState;
}
@Override
public final void setIsCasting(final boolean isCasting) {
if (this.isCasting != isCasting)
this.update(false);
this.isCasting = isCasting;
}
@Override
public void setItemCasting(boolean itemCasting) {
if (this.itemCasting != itemCasting)
this.dynamicUpdate(UpdateType.REGEN);
this.itemCasting = itemCasting;
}
public void resetRegenUpdateTime() {
this.lastUpdateTime = System.currentTimeMillis();
this.lastStamUpdateTime = System.currentTimeMillis();
}
public float getCharacterHeight() {
return characterHeight;
}
public boolean isEnteredWorld() {
return enteredWorld;
}
public ReadWriteLock getTeleportLock() {
return teleportLock;
}
}