Owner death, logout or invisibility dismissed a commanded siege engine by clearing its target and marking it dead. Released engines now revert to MOBILE in place, stay alive and keep their current target so any nation member can re-command them with a Rod of Command. Commanding an engine no longer clears the target of the old or new engine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4974 lines
168 KiB
Java
4974 lines
168 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://www.magicbane.com
|
|
|
|
|
|
package engine.objects;
|
|
|
|
import engine.InterestManagement.InterestManager;
|
|
import engine.InterestManagement.RealmMap;
|
|
import engine.InterestManagement.Terrain;
|
|
import engine.InterestManagement.WorldGrid;
|
|
import engine.db.archive.CharacterRecord;
|
|
import engine.db.archive.DataWarehouse;
|
|
import engine.db.archive.PvpRecord;
|
|
import engine.gameManager.*;
|
|
import engine.job.JobContainer;
|
|
import engine.job.JobScheduler;
|
|
import engine.jobs.DeferredPowerJob;
|
|
import engine.jobs.FinishSpireEffectJob;
|
|
import engine.jobs.NoTimeJob;
|
|
import engine.math.Bounds;
|
|
import engine.math.FastMath;
|
|
import engine.math.Vector3fImmutable;
|
|
import engine.mbEnums;
|
|
import engine.mbEnums.*;
|
|
import engine.net.ByteBufferWriter;
|
|
import engine.net.Dispatch;
|
|
import engine.net.client.ClientConnection;
|
|
import engine.net.client.msg.*;
|
|
import engine.net.client.msg.login.CommitNewCharacterMsg;
|
|
import engine.powers.EffectsBase;
|
|
import engine.server.MBServerStatics;
|
|
import engine.server.login.LoginServer;
|
|
import engine.server.world.WorldServer;
|
|
import engine.util.MiscUtils;
|
|
import org.joda.time.DateTime;
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import java.sql.ResultSet;
|
|
import java.sql.SQLException;
|
|
import java.util.*;
|
|
import java.util.Map.Entry;
|
|
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.
|
|
private 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 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;
|
|
private final AtomicInteger strMod = new AtomicInteger(); // Stat Modifiers
|
|
private final AtomicInteger dexMod = new AtomicInteger();
|
|
private final AtomicInteger conMod = new AtomicInteger();
|
|
private final AtomicInteger intMod = new AtomicInteger();
|
|
private 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);
|
|
private 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
|
|
|
|
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;
|
|
|
|
|
|
private ConcurrentHashMap<Integer, String> ignoredPlayerIDs = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
|
|
private boolean lfGroup = false;
|
|
private boolean lfGuild = false;
|
|
private boolean recruiting = false;
|
|
private MovementState movementState = MovementState.IDLE;
|
|
private MovementState lastMovementState = MovementState.IDLE;
|
|
private 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 seeInvis = 0; // current rank of see invis
|
|
private float speedMod;
|
|
private boolean teleportMode = false; // Teleport on MoveToPoint
|
|
private float dexPenalty;
|
|
private long lastPlayerAttackTime = 0;
|
|
private long lastUpdateTime = System.currentTimeMillis();
|
|
private long lastStamUpdateTime = System.currentTimeMillis();
|
|
private boolean safeZone = false;
|
|
|
|
/*
|
|
DataWarehouse based kill/death tracking.
|
|
These sets contain the last 10 UUID's
|
|
*/
|
|
private boolean noTeleScreen = false;
|
|
private int lastRealmID = -2;
|
|
private int subRaceID = 0;
|
|
private boolean hasAnniversery = false;
|
|
private DeferredPowerJob weaponPower;
|
|
private NPC lastNPCDialog;
|
|
private Mob pet;
|
|
//Used for skill/Power calculation optimization
|
|
private CharacterTitle title = CharacterTitle.NONE;
|
|
private boolean asciiLastName = true;
|
|
private int spamCount = 0;
|
|
private boolean initialized = false;
|
|
private 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;
|
|
private boolean lastSwimming = false;
|
|
private boolean isTeleporting = false;
|
|
private boolean dirtyLoad = true;
|
|
private final ReadWriteLock dirtyLock = new ReentrantReadWriteLock(true);
|
|
|
|
/**
|
|
* 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.buildingUUID = -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());
|
|
if (guild != null && guild.isGuildLeader(this.getObjectUUID()))
|
|
this.setGuildLeader(true);
|
|
else
|
|
this.setGuildLeader(false);
|
|
|
|
this.hasAnniversery = rs.getBoolean("anniversery");
|
|
|
|
this.setInnerCouncil(rs.getBoolean("guild_isInnerCouncil"));
|
|
this.setFullMember(rs.getBoolean("guild_isFullMember"));
|
|
this.setTaxCollector(rs.getBoolean("guild_isTaxCollector"));
|
|
this.setRecruiter(rs.getBoolean("guild_isRecruiter"));
|
|
this.setGuildTitle(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.buildingUUID = rs.getInt("char_bindBuilding");
|
|
|
|
this.hash = rs.getString("hash");
|
|
|
|
|
|
// For debugging skills
|
|
// CharacterSkill.printSkills(this);
|
|
}
|
|
|
|
public static Building getUpdatedBindBuilding(PlayerCharacter player) {
|
|
Building returnBuilding = null;
|
|
|
|
//update bindBuilding based on Guild or nation TOL;
|
|
|
|
if (player.getBindBuildingID() == 0) {
|
|
|
|
returnBuilding = PlayerCharacter.getBindBuildingForGuild(player);
|
|
|
|
if (returnBuilding != null)
|
|
player.setBindBuildingID(returnBuilding.getObjectUUID());
|
|
return returnBuilding;
|
|
}
|
|
returnBuilding = BuildingManager.getBuildingFromCache(player.getBindBuildingID());
|
|
|
|
if (returnBuilding == null) {
|
|
returnBuilding = PlayerCharacter.getBindBuildingForGuild(player);
|
|
|
|
if (returnBuilding != null)
|
|
player.setBindBuildingID(returnBuilding.getObjectUUID());
|
|
}
|
|
return returnBuilding;
|
|
}
|
|
|
|
public static Building getBindBuildingForGuild(PlayerCharacter player) {
|
|
|
|
Building returnBuilding;
|
|
|
|
if (player.getGuild() == null || player.getGuild().isEmptyGuild())
|
|
return null;
|
|
|
|
if (player.getGuild().getOwnedCity() == null) {
|
|
|
|
if (player.getGuild().getNation().getOwnedCity() == null)
|
|
return null;
|
|
|
|
if (player.getGuild().getNation().getOwnedCity().getTOL() == null)
|
|
return null;
|
|
|
|
returnBuilding = player.getGuild().getNation().getOwnedCity().getTOL();
|
|
player.setBindBuildingID(returnBuilding.getObjectUUID());
|
|
return returnBuilding;
|
|
}
|
|
|
|
if (player.getGuild().getOwnedCity().getTOL() == null)
|
|
return null;
|
|
|
|
returnBuilding = player.getGuild().getOwnedCity().getTOL();
|
|
return returnBuilding;
|
|
}
|
|
|
|
public static void __serializeForClientMsg(PlayerCharacter playerCharacter, ByteBufferWriter writer) {
|
|
serializeForClientCommon(playerCharacter, writer, true, false, false, false);
|
|
}
|
|
|
|
public static void serializeForClientMsgLogin(PlayerCharacter playerCharacter, ByteBufferWriter writer) {
|
|
serializeForClientCommon(playerCharacter, writer, true, false, false, false);
|
|
}
|
|
|
|
public static void serializeForClientMsgCommit(PlayerCharacter playerCharacter, ByteBufferWriter writer) {
|
|
serializeForClientCommon(playerCharacter, writer, true, true, false, false);
|
|
}
|
|
|
|
public static void serializeForClientMsgFull(PlayerCharacter playerCharacter, ByteBufferWriter writer) {
|
|
serializeForClientCommon(playerCharacter, writer, false, false, false, false);
|
|
}
|
|
|
|
public static void serializeForClientMsgOtherPlayer(PlayerCharacter playerCharacter, ByteBufferWriter writer) {
|
|
serializeForClientCommon(playerCharacter, writer, false, false, true, false);
|
|
}
|
|
|
|
public static void serializePlayerForClientMsgOtherPlayer(PlayerCharacter playerCharacter, ByteBufferWriter writer, boolean hideAsciiLastName) {
|
|
serializeForClientCommon(playerCharacter, writer, false, false, true, hideAsciiLastName);
|
|
}
|
|
|
|
// TODO what is a Fresh Char?
|
|
private static void serializeForClientCommon(PlayerCharacter playerCharacter, ByteBufferWriter writer, boolean loginData, boolean freshChar, boolean otherPlayer, boolean hideAsciiLastName) {
|
|
|
|
/*
|
|
* RUNES
|
|
*/
|
|
// Handle Applied Runes
|
|
writer.putInt(0); // Pad
|
|
writer.putInt(0); // Pad
|
|
|
|
// Put number of runes
|
|
//We need to send all runes to everyone, otherwise playerCharacter will cause major issues
|
|
if (playerCharacter.promotionClass != null)
|
|
writer.putInt(playerCharacter.runes.size() + 3);
|
|
else
|
|
writer.putInt(playerCharacter.runes.size() + 2);
|
|
|
|
// Cant forget that Race and baseClass are technically Runes :0
|
|
if (playerCharacter.subRaceID != 0) {
|
|
writer.putInt(1); // For Race
|
|
writer.putInt(0); // Pad
|
|
writer.putInt(playerCharacter.subRaceID);
|
|
|
|
writer.putInt(mbEnums.GameObjectType.Race.ordinal());
|
|
writer.putInt(playerCharacter.subRaceID);
|
|
} else
|
|
playerCharacter.race.serializeForClientMsg(writer);
|
|
if (playerCharacter.promotionClass != null) {
|
|
BaseClass.serializeForClientMsg(playerCharacter.baseClass, writer, 2);
|
|
PromotionClass.serializeForClientMsg(playerCharacter.promotionClass, writer);
|
|
} else
|
|
BaseClass.serializeForClientMsg(playerCharacter.baseClass, writer, 3);
|
|
|
|
// Put runes.
|
|
|
|
for (CharacterRune rb : playerCharacter.runes) {
|
|
CharacterRune.serializeForClientMsg(rb, writer);
|
|
}
|
|
|
|
/*
|
|
* STATS
|
|
*/
|
|
// Number of Stats to follow
|
|
writer.putInt(5);
|
|
|
|
writer.putInt(MBServerStatics.STAT_STR_ID); // Strength ID
|
|
writer.putInt(freshChar ? 0 : playerCharacter.getStrMod());
|
|
|
|
writer.putInt(MBServerStatics.STAT_SPI_ID); // Spirit ID
|
|
writer.putInt(freshChar ? 0 : playerCharacter.getSpiMod());
|
|
|
|
writer.putInt(MBServerStatics.STAT_CON_ID); // Constitution ID
|
|
writer.putInt(freshChar ? 0 : playerCharacter.getConMod());
|
|
|
|
writer.putInt(MBServerStatics.STAT_DEX_ID); // Dexterity ID
|
|
writer.putInt(freshChar ? 0 : playerCharacter.getDexMod());
|
|
|
|
writer.putInt(MBServerStatics.STAT_INT_ID); // Intelligence ID
|
|
writer.putInt(freshChar ? 0 : playerCharacter.getIntMod());
|
|
|
|
// Handle Info
|
|
playerCharacter.title._serializeFirstName(writer, playerCharacter.firstName);
|
|
playerCharacter.title._serializeLastName(writer, playerCharacter.lastName, hideAsciiLastName, playerCharacter.asciiLastName);
|
|
|
|
// Unknown
|
|
writer.putInt(0);
|
|
|
|
writer.putString(ConfigManager.MB_WORLD_NAME.getValue());
|
|
writer.putInt(WorldServer.worldMapID);
|
|
|
|
writer.put((byte) 1); // End Datablock byte
|
|
writer.putInt(0); // Unsure, Pad?
|
|
writer.putInt(playerCharacter.getObjectType().ordinal());
|
|
writer.putInt(playerCharacter.getObjectUUID());
|
|
|
|
// Perhaps playerCharacter is loc and the next 3 are Facing dir?
|
|
writer.putFloat(1); // Unknown
|
|
writer.putFloat(playerCharacter.race.getRaceType().getScaleHeight()); // Unknown
|
|
writer.putFloat(1); // Unknown
|
|
|
|
writer.putVector3f(playerCharacter.getLoc());
|
|
writer.putFloat(playerCharacter.faceDir.getRotation()); // Rotation, direction
|
|
|
|
// facing
|
|
|
|
// Running trains.
|
|
|
|
if (otherPlayer) {
|
|
CharacterSkill runSkill = playerCharacter.skills.get("Running");
|
|
if (runSkill == null)
|
|
// Logger.log.log(
|
|
// LogEventType.WARNING,
|
|
// "Failed to find the 'Running Skill' when serializing PlayerCharacter '"
|
|
// + playerCharacter.getCombinedName() + "'");
|
|
// TODO put int=0 for now.
|
|
writer.putInt(0);
|
|
else
|
|
writer.putInt(runSkill.getNumTrains());
|
|
} else
|
|
writer.putInt(0);
|
|
|
|
writer.putInt(playerCharacter.charItemManager.equipped.size());
|
|
for (Item item : playerCharacter.charItemManager.equipped.values()) {
|
|
Item._serializeForClientMsg(item, writer);
|
|
}
|
|
writer.putInt(playerCharacter.getRank());
|
|
|
|
writer.putInt(playerCharacter.getLevel());
|
|
if (loginData)
|
|
writer.putInt(5);
|
|
else
|
|
writer.putInt(playerCharacter.getIsSittingAsInt()); // 5
|
|
writer.putInt(playerCharacter.getIsWalkingAsInt()); // 1
|
|
writer.putInt(playerCharacter.getIsCombatAsInt()); // 1
|
|
writer.putInt(playerCharacter.getIsFlightAsInt()); // 2 or 3
|
|
|
|
writer.putInt(playerCharacter.getIsLfGroupAsInt()); // 1
|
|
|
|
// if (loginData)
|
|
// writer.putInt(0);
|
|
// else
|
|
writer.putInt(playerCharacter.getHeadlightsAsInt());
|
|
|
|
|
|
if (playerCharacter.region != null && !loginData) {
|
|
Building building = Regions.GetBuildingForRegion(playerCharacter.region);
|
|
|
|
if (building == null) {
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
} else {
|
|
writer.putInt(GameObjectType.Building.ordinal());
|
|
writer.putInt(building.getObjectUUID());
|
|
}
|
|
|
|
} else {
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
}
|
|
|
|
|
|
writer.put((byte) 0);
|
|
writer.put((byte) 0);
|
|
writer.put((byte) 0);
|
|
writer.putInt(0);
|
|
writer.put((byte) 0);
|
|
writer.put((byte) 0);
|
|
writer.put((byte) 0);
|
|
|
|
// writer.putInt(0);
|
|
// writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
|
|
if (!playerCharacter.isAlive() && otherPlayer) {
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
}
|
|
|
|
|
|
//TODO FIGURE OUT THE REAL SEARLIZATION FOR NEXT 2 SHORTS?
|
|
writer.putInt(playerCharacter.skinColor); // Skin Color
|
|
writer.putFloat(20);
|
|
writer.put((byte) 0); //Unknown
|
|
|
|
//unknown object
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
|
|
//unknown type
|
|
writer.putInt(0);
|
|
//0x4080 should be the next short here, instead it wraps 0's down their in for loops.. seriously.. who wrote playerCharacter shit.
|
|
// playerCharacter aint right!
|
|
// ByteBufferUtils.putString(writer, playerCharacter.guild.getName());
|
|
// writer.putInt(playerCharacter.getGuild().getUUID());
|
|
// ByteBufferUtils.putString(writer, playerCharacter.guild.getNation().getName());
|
|
// writer.putInt(playerCharacter.getGuild().getNation().getUUID());
|
|
Guild.serializeForClientMsg(playerCharacter.getGuild(), writer, playerCharacter, false);
|
|
|
|
//Send Tokens for race/class/promotion (disciplines?)
|
|
if (playerCharacter.promotionClass != null)
|
|
writer.putInt(3);
|
|
else
|
|
writer.putInt(2);
|
|
writer.putInt(playerCharacter.race.getToken());
|
|
writer.putInt(playerCharacter.baseClass.getToken());
|
|
if (playerCharacter.promotionClass != null)
|
|
writer.putInt(playerCharacter.promotionClass.getToken());
|
|
// writer.putInt(2); // Unknown Counter
|
|
// writer.putInt(0x04C1BE88); // Unknown
|
|
// writer.putInt(0x0F651512); // Unknown
|
|
|
|
writer.putFloat(playerCharacter.altitude); // altitude?
|
|
writer.putFloat(playerCharacter.altitude); // altitude?
|
|
writer.put((byte) 0); // End Datablock byte
|
|
|
|
writer.putFloat(playerCharacter.healthMax);
|
|
writer.putFloat(playerCharacter.health.get());
|
|
|
|
writer.put((byte) 0); // End Datablock byte
|
|
//size
|
|
|
|
|
|
if (loginData) {
|
|
writer.putInt(0);
|
|
} else {
|
|
int indexPosition = writer.position();
|
|
writer.putInt(0); //placeholder for item cnt
|
|
int total = 0;
|
|
// Logger.info("",""+ playerCharacter.getEffects().size());
|
|
for (Effect eff : playerCharacter.getEffects().values()) {
|
|
if (eff.getPower() == null && otherPlayer)
|
|
continue;
|
|
if (eff.getPower() != null && eff.getPower().token == 429506619) // Oblivion's Caress
|
|
continue;
|
|
if (!eff.serializeForLoad(writer))
|
|
continue;
|
|
++total;
|
|
|
|
}
|
|
|
|
writer.putIntAt(total, indexPosition);
|
|
}
|
|
|
|
if (otherPlayer) {
|
|
writer.put((byte) 0); // End Datablock Byte
|
|
return;
|
|
|
|
}
|
|
|
|
//made up for sendalleffects
|
|
//writer.putInt(0); // Pad
|
|
//writer.put((byte) 0); // End Datablock byte
|
|
writer.putInt(playerCharacter.getUnusedStatPoints());
|
|
writer.putInt(playerCharacter.getLevel());
|
|
writer.putInt(playerCharacter.getExp() + playerCharacter.overFlowEXP);
|
|
writer.putFloat(playerCharacter.manaMax);
|
|
writer.putFloat(playerCharacter.mana.get());
|
|
writer.putFloat(playerCharacter.staminaMax);
|
|
writer.putFloat(playerCharacter.stamina.get());
|
|
writer.putInt(playerCharacter.getAtrHandOne());
|
|
writer.putInt(playerCharacter.getAtrHandTwo());
|
|
writer.putInt(playerCharacter.getDefenseRating());
|
|
|
|
if (MBServerStatics.POWERS_DEBUG) //debug mode, grant lots of trains
|
|
writer.putInt(1000);
|
|
else
|
|
writer.putInt(playerCharacter.trainsAvailable.get());
|
|
|
|
/*
|
|
* Skills
|
|
*/
|
|
if (loginData)
|
|
writer.putInt(0); // Skip skills
|
|
else {
|
|
writer.putInt(playerCharacter.skills.size());
|
|
Iterator<String> it = playerCharacter.skills.keySet().iterator();
|
|
while (it.hasNext()) {
|
|
String name = it.next();
|
|
CharacterSkill.serializeForClientMsg(playerCharacter.skills.get(name), writer);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Powers
|
|
*/
|
|
if (loginData)
|
|
writer.putInt(0); // Skip Powers
|
|
else if (MBServerStatics.POWERS_DEBUG) //debug mode, grant all powers
|
|
PowersManager.testPowers(writer);
|
|
else {
|
|
writer.putInt(playerCharacter.powers.size());
|
|
for (CharacterPower sp : playerCharacter.powers.values()) {
|
|
CharacterPower.serializeForClientMsg(sp, writer);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Inventory
|
|
*/
|
|
if (loginData) {
|
|
writer.putInt(0); // Skip Inventory
|
|
writer.putInt(playerCharacter.getInventoryCapacity()); // Inventory Capacity
|
|
|
|
} else {
|
|
ArrayList<Item> inv = playerCharacter.charItemManager.getInventory(true);
|
|
Item.putList(writer, inv, false, playerCharacter.getObjectUUID());
|
|
writer.putInt(playerCharacter.getInventoryCapacityRemaining());
|
|
}
|
|
|
|
/*
|
|
* Bank
|
|
*/
|
|
if (loginData) {
|
|
writer.putInt(0); // Skip Bank
|
|
writer.putInt(AbstractCharacter.getBankCapacity()); // Bank Capacity
|
|
|
|
} else {
|
|
ArrayList<Item> bank = playerCharacter.charItemManager.getBank();
|
|
|
|
Item.putList(writer, bank, false, playerCharacter.getObjectUUID());
|
|
writer.putInt(playerCharacter.getBankCapacityRemaining());
|
|
}
|
|
//load player friends.
|
|
if (loginData)
|
|
writer.putInt(0);
|
|
else {
|
|
HashSet<Integer> friendMap = PlayerFriends.PlayerFriendsMap.get(playerCharacter.getObjectUUID());
|
|
if (friendMap == null)
|
|
writer.putInt(0);
|
|
else {
|
|
writer.putInt(friendMap.size());
|
|
for (int friendID : friendMap) {
|
|
PlayerCharacter friend = PlayerCharacter.getFromCache(friendID);
|
|
//shouldn't get here, but if null serialize blank friend.
|
|
if (friend == null) {
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
} else {
|
|
writer.putInt(friend.getObjectType().ordinal());
|
|
writer.putInt(friend.getObjectUUID());
|
|
writer.putString(friend.getName());
|
|
boolean online = SessionManager.getPlayerCharacterByID(friend.getObjectUUID()) != null ? true : false;
|
|
writer.putInt(online ? 0 : 1);
|
|
writer.putInt(friend.friendStatus.ordinal());
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
|
|
writer.putShort((short) 0);
|
|
writer.put((byte) 0);
|
|
// playerCharacter is for send self in enter world (full character)
|
|
if (!loginData && !freshChar) {
|
|
int size = playerCharacter.getRecycleTimers().size();
|
|
writer.putInt(size);
|
|
if (size > 0)
|
|
for (int token : playerCharacter.getRecycleTimers().keySet()) {
|
|
|
|
JobContainer frtj = playerCharacter.getRecycleTimers().get(token);
|
|
long timeLeft = frtj.timeOfExection() - System.currentTimeMillis();
|
|
writer.putInt(token);
|
|
writer.putInt((int) timeLeft / 1000);
|
|
}
|
|
DateTime enterWorld = new DateTime(playerCharacter.timestamps.get("EnterWorld"));
|
|
writer.putDateTime(enterWorld);
|
|
|
|
writer.putInt(0x49EF1E98); //DUnno what playerCharacter is.
|
|
writer.putFloat(DateTime.now().hourOfDay().get()); //daylight in float.
|
|
writer.putFloat(6); //interval of light to change per game hour //float
|
|
//writer.putInt(1637194901); //playerCharacter is actually an opcode taht is in recordings, no clue what it is, dumped it and it changes nothing
|
|
} else {
|
|
writer.put((byte) 0); //added to compensate the cooldown check.
|
|
|
|
//add server up or down
|
|
int serverUp = LoginServer.worldServerRunning ? 1 : 0;
|
|
|
|
if (playerCharacter.account == null)
|
|
serverUp = 0;
|
|
|
|
if ((playerCharacter.account.status.equals(AccountStatus.ADMIN) == false) &&
|
|
(playerCharacter.account.status.equals(WorldServer.worldAccessLevel) == false))
|
|
serverUp = 0;
|
|
|
|
writer.putInt(serverUp);
|
|
writer.putInt(0); // effects, not sure used by players
|
|
writer.put((byte) 0); // End Player Datablock
|
|
}
|
|
|
|
}
|
|
|
|
public static PlayerCharacter generatePCFromCommitNewCharacterMsg(Account a, CommitNewCharacterMsg msg, ClientConnection clientConnection) {
|
|
|
|
String firstName = msg.getFirstName().trim();
|
|
String lastName = msg.getLastName().trim();
|
|
|
|
if (firstName.length() < 3) {
|
|
LoginServer.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTNAME_MUST_BE_LONGER,
|
|
clientConnection);
|
|
return null;
|
|
}
|
|
|
|
// Ensure names are below required length
|
|
if (firstName.length() > 15 || lastName.length() > 15) {
|
|
LoginServer.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTANDLAST_MUST_BE_SHORTER,
|
|
clientConnection);
|
|
return null;
|
|
}
|
|
|
|
// Check if firstname is valid
|
|
if (MiscUtils.checkIfFirstNameInvalid(firstName)) {
|
|
LoginServer.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_PLEASE_CHOOSE_ANOTHER_FIRSTNAME,
|
|
clientConnection);
|
|
return null;
|
|
}
|
|
|
|
// Check if last name is valid
|
|
if (MiscUtils.checkIfLastNameInvalid(lastName)) {
|
|
LoginServer.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_LASTNAME_UNAVAILABLE,
|
|
clientConnection);
|
|
return null;
|
|
}
|
|
|
|
// Verify Race
|
|
int raceID = msg.getRace();
|
|
|
|
Race race = Race.getRace(raceID);
|
|
|
|
if (race == null) {
|
|
Logger.info("Invalid RaceID: " + raceID);
|
|
return null;
|
|
}
|
|
|
|
// Verify BaseClass Object.
|
|
int baseClassID = msg.getBaseClass();
|
|
BaseClass baseClass = DbManager.BaseClassQueries.GET_BASE_CLASS(baseClassID);
|
|
|
|
if (baseClass == null) {
|
|
Logger.info("Invalid BaseClasID: " + baseClassID);
|
|
return null;
|
|
}
|
|
|
|
// Verify Race/baseClass combo.
|
|
boolean valid = false;
|
|
|
|
for (BaseClass bc : race.getValidBaseClasses()) {
|
|
|
|
if (bc.getObjectUUID() == baseClassID) {
|
|
valid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!valid) {
|
|
Logger.info("Invalid BaseClass/Race Combo");
|
|
return null;
|
|
}
|
|
|
|
// Verify HairStyle/BeardStyle/SkinColor/HairColor/BeardColor
|
|
int hairStyleID = msg.getHairStyle();
|
|
int beardStyleID = msg.getBeardStyle();
|
|
int skinColorID = msg.getSkinColor();
|
|
int hairColorID = msg.getHairColor();
|
|
int beardColorID = msg.getBeardColor();
|
|
|
|
if (!race.isValidHairStyle(hairStyleID)) {
|
|
Logger.info("Invalid HairStyleID: " + hairStyleID + " for race: " + race.getName());
|
|
return null;
|
|
}
|
|
|
|
if (!race.isValidSkinColor(skinColorID)) {
|
|
Logger.info("Invalid skinColorID: " + skinColorID + " for race: " + race.getName());
|
|
return null;
|
|
}
|
|
|
|
if (!race.isValidHairColor(hairColorID)) {
|
|
Logger.info("Invalid hairColorID: " + hairColorID + " for race: " + race.getName());
|
|
return null;
|
|
}
|
|
|
|
if (!race.isValidBeardColor(beardColorID)) {
|
|
Logger.info("Invalid beardColorID: " + beardColorID + " for race: " + race.getName());
|
|
return null;
|
|
}
|
|
|
|
// Get stat modifiers
|
|
int strMod = msg.getStrengthMod();
|
|
int dexMod = msg.getDexterityMod();
|
|
int conMod = msg.getConstitutionMod();
|
|
int intMod = msg.getIntelligenceMod();
|
|
int spiMod = msg.getSpiritMod();
|
|
|
|
|
|
if (intMod < -5 || dexMod < -5 || conMod < -5 || strMod < -5 || spiMod < -5) {
|
|
Logger.error("NEGATIVE STAT CHEAT ATTEMPTED! ACCOUNT: " + a.getUname() + "(" + a.getObjectUUID() + ") IP ADDRESS: " + clientConnection.getClientIpAddress());
|
|
return null;
|
|
}
|
|
|
|
// calculate current stats:
|
|
short strCur = (short) (race.getStrStart() + baseClass.getStrMod() + strMod);
|
|
short dexCur = (short) (race.getDexStart() + baseClass.getDexMod() + dexMod);
|
|
short conCur = (short) (race.getConStart() + baseClass.getConMod() + conMod);
|
|
short intCur = (short) (race.getIntStart() + baseClass.getIntMod() + intMod);
|
|
short spiCur = (short) (race.getSpiStart() + baseClass.getSpiMod() + spiMod);
|
|
|
|
// calculate max stats:
|
|
short strMax = race.getStrMax();
|
|
short dexMax = race.getDexMax();
|
|
short conMax = race.getConMax();
|
|
short intMax = race.getIntMax();
|
|
short spiMax = race.getSpiMax();
|
|
|
|
// Verify not too many runes applied
|
|
int numRunes = msg.getNumRunes();
|
|
|
|
if (numRunes > 16) {
|
|
Logger.info("Too many Runes applied");
|
|
return null;
|
|
}
|
|
|
|
// Get Runes
|
|
// ArrayList<RuneBase> characterRunesUsed = new ArrayList<RuneBase>();
|
|
// ArrayList<Byte> subtypesUsed = new ArrayList<Byte>();
|
|
int remainingPoints = race.getStartingPoints() - strMod - dexMod - conMod - intMod - spiMod;
|
|
|
|
int[] characterRunes = msg.getRunes();
|
|
|
|
HashSet<Byte> usedRunesSubType = new HashSet<>();
|
|
HashSet<RuneBase> usedRunes = new HashSet<>();
|
|
|
|
// So that all the penalties can be added at the end.
|
|
ConcurrentHashMap<String, Integer> penalties = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
|
|
|
|
penalties.put("StrCur", 0);
|
|
penalties.put("StrMax", 0);
|
|
penalties.put("DexCur", 0);
|
|
penalties.put("DexMax", 0);
|
|
penalties.put("ConCur", 0);
|
|
penalties.put("ConMax", 0);
|
|
penalties.put("IntCur", 0);
|
|
penalties.put("IntMax", 0);
|
|
penalties.put("SpiCur", 0);
|
|
penalties.put("SpiMax", 0);
|
|
|
|
PriorityQueue<Map.Entry<Integer, RuneBase>> orderedRunes = new PriorityQueue<>(14,
|
|
new Comparator<Map.Entry<Integer, RuneBase>>() {
|
|
|
|
@Override
|
|
public int compare(Entry<Integer, RuneBase> o1, Entry<Integer, RuneBase> o2) {
|
|
return o1.getKey() - o2.getKey();
|
|
}
|
|
});
|
|
|
|
// Figure out which Runes we are adding.
|
|
for (int i : characterRunes) {
|
|
// Zero skip
|
|
if (i == 0)
|
|
continue;
|
|
|
|
// Skip the Race and BaseClass runes... already dealt with.
|
|
if (i == raceID || i == baseClassID)
|
|
continue;
|
|
|
|
RuneBase runeBase = RuneBase.getRuneBase(i);
|
|
|
|
// Null check
|
|
if (runeBase == null) {
|
|
Logger.info("GOM returned NULL RuneBase");
|
|
return null;
|
|
}
|
|
|
|
// Validate Rune against Race
|
|
if (!race.isAllowedRune(runeBase)) {
|
|
Logger.info("Trait Not valid for Race");
|
|
return null;
|
|
}
|
|
|
|
// Validate BaseClass against Race
|
|
if (!baseClass.isAllowedRune(runeBase)) {
|
|
Logger.info("Trait Not valid for BaseClass");
|
|
return null;
|
|
}
|
|
|
|
int previous_size = usedRunes.size();
|
|
int previous_subtype = usedRunesSubType.size();
|
|
|
|
usedRunes.add(runeBase);
|
|
usedRunesSubType.add(runeBase.getSubtype());
|
|
|
|
// Duplicate Rune check
|
|
if (usedRunes.size() <= previous_size) {
|
|
Logger.info("Duplicate RuneBase");
|
|
return null;
|
|
}
|
|
|
|
// Duplicate Subtype check
|
|
if (runeBase.getSubtype() != 0 && usedRunesSubType.size() <= previous_subtype) {
|
|
Logger.info("Duplicate RuneBase Subtype");
|
|
return null;
|
|
}
|
|
|
|
int maxValue = 0;
|
|
|
|
// Every attempt is made to load MIN_NEEDED_ATTRIBUTES first.
|
|
|
|
if (runeBase.getAttrs() != null)
|
|
for (RuneBaseAttribute rba : runeBase.getAttrs()) {
|
|
if (rba.getAttributeID() == MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID
|
|
|| rba.getAttributeID() == MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID
|
|
|| rba.getAttributeID() == MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID
|
|
|| rba.getAttributeID() == MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID
|
|
|| rba.getAttributeID() == MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID) {
|
|
maxValue = rba.getModValue();
|
|
if (runeBase.getName().equals("Giant's Blood"))
|
|
maxValue = 45; // Take care of the Giant's Blood special
|
|
// case.
|
|
break;
|
|
}
|
|
}
|
|
|
|
orderedRunes.add(new AbstractMap.SimpleEntry<>(maxValue, runeBase));
|
|
}
|
|
|
|
while (orderedRunes.size() > 0) {
|
|
RuneBase rb = orderedRunes.remove().getValue();
|
|
ArrayList<RuneBaseAttribute> attrs = rb.getAttrs();
|
|
|
|
if (attrs != null)
|
|
for (RuneBaseAttribute abr : attrs) {
|
|
|
|
int attrID = abr.getAttributeID();
|
|
int value = abr.getModValue();
|
|
|
|
switch (attrID) {
|
|
case MBServerStatics.RUNE_COST_ATTRIBUTE_ID:
|
|
|
|
Logger.info("Bought " + rb.getName() + " for " + value + " points. "
|
|
+ (remainingPoints - value) + " left.");
|
|
|
|
if ((remainingPoints - value) >= 0) {
|
|
remainingPoints -= value;
|
|
continue;
|
|
}
|
|
Logger.info("Not enough points left");
|
|
return null;
|
|
case MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID:
|
|
|
|
if (strCur >= value)
|
|
continue;
|
|
|
|
Logger.info("STR fails to meet Rune Minimum --> " + rb.getName());
|
|
return null;
|
|
case MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID:
|
|
|
|
if (dexCur >= value)
|
|
continue;
|
|
|
|
Logger.info("DEX fails to meet Rune Minimum --> " + rb.getName());
|
|
return null;
|
|
case MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID:
|
|
|
|
if (conCur >= value)
|
|
continue;
|
|
|
|
Logger.info("CON fails to meet Rune Minimum --> " + rb.getName());
|
|
return null;
|
|
case MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID:
|
|
|
|
if (intCur >= value)
|
|
continue;
|
|
|
|
Logger.info("INT fails to meet Rune Minimum --> " + rb.getName());
|
|
return null;
|
|
case MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID:
|
|
|
|
if (spiCur >= value)
|
|
continue;
|
|
|
|
Logger.info("SPI fails to meet Rune Minimum --> " + rb.getName());
|
|
return null;
|
|
case MBServerStatics.RUNE_STR_ATTRIBUTE_ID:
|
|
|
|
if (value < 0)
|
|
penalties.put("StrCur", (penalties.get("StrCur") + value));
|
|
else
|
|
strCur += value;
|
|
continue;
|
|
|
|
case MBServerStatics.RUNE_DEX_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("DexCur", (penalties.get("DexCur") + value));
|
|
else
|
|
dexCur += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_CON_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("ConCur", (penalties.get("ConCur") + value));
|
|
else
|
|
conCur += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_INT_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("IntCur", (penalties.get("IntCur") + value));
|
|
else
|
|
intCur += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_SPI_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("SpiCur", (penalties.get("SpiCur") + value));
|
|
else
|
|
spiCur += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_STR_MAX_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("StrMax", (penalties.get("StrMax") + value));
|
|
else
|
|
strMax += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_DEX_MAX_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("DexMax", (penalties.get("DexMax") + value));
|
|
else
|
|
dexMax += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_CON_MAX_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("ConMax", (penalties.get("ConMax") + value));
|
|
else
|
|
conMax += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_INT_MAX_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("IntMax", (penalties.get("IntMax") + value));
|
|
else
|
|
intMax += value;
|
|
continue;
|
|
case MBServerStatics.RUNE_SPI_MAX_ATTRIBUTE_ID:
|
|
if (value < 0)
|
|
penalties.put("SpiMax", (penalties.get("SpiMax") + value));
|
|
else
|
|
spiMax += value;
|
|
continue;
|
|
|
|
default:
|
|
Logger.info("Unknown ATTRIBUTE_ID while checking RuneBaseAttributes: " + attrID);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add in all of the penalties.
|
|
strCur += penalties.get("StrCur");
|
|
strMax += penalties.get("StrMax");
|
|
dexCur += penalties.get("DexCur");
|
|
dexMax += penalties.get("DexMax");
|
|
conCur += penalties.get("ConCur");
|
|
conMax += penalties.get("ConMax");
|
|
intCur += penalties.get("IntCur");
|
|
intMax += penalties.get("IntMax");
|
|
spiCur += penalties.get("SpiCur");
|
|
spiMax += penalties.get("SpiMax");
|
|
|
|
int kitID = msg.getKit();
|
|
|
|
// get the correctKit
|
|
int raceClassID = Kit.GetKitIDByRaceClass(raceID, baseClassID);
|
|
ArrayList<Kit> allKits = Kit.RaceClassIDMap.get(raceClassID);
|
|
|
|
Kit kit = null;
|
|
|
|
for (Kit k : allKits) {
|
|
if (k.getKitNumber() == kitID) {
|
|
kit = k;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (kit == null) {
|
|
Logger.info("Unable to find matching kitID: " + kitID);
|
|
return null;
|
|
}
|
|
|
|
byte runningTrains = 0;
|
|
PlayerCharacter playerCharacter;
|
|
|
|
//Synchronized block to allow exclusive access when confirming
|
|
//uniqueness of FirstName and subsequently saving the new record
|
|
//to the database with that FirstName
|
|
synchronized (FirstNameLock) {
|
|
// Test if FirstName already exists.
|
|
// This must be the very last check before calling the
|
|
// DB to create the character record
|
|
if (DbManager.PlayerCharacterQueries.IS_CHARACTER_NAME_UNIQUE(firstName) == false) {
|
|
LoginServer.sendInvalidNameMsg(firstName, lastName, MBServerStatics.INVALIDNAME_FIRSTNAME_UNAVAILABLE,
|
|
clientConnection);
|
|
return null;
|
|
}
|
|
|
|
// Make PC
|
|
PlayerCharacter pcWithoutID = new PlayerCharacter(firstName, lastName, (short) strMod, (short) dexMod, (short) conMod,
|
|
(short) intMod, (short) spiMod, Guild.getErrantGuild(), runningTrains, a, race, baseClass, (byte) skinColorID, (byte) hairColorID,
|
|
(byte) beardColorID, (byte) beardStyleID, (byte) hairStyleID);
|
|
|
|
try {
|
|
playerCharacter = DbManager.PlayerCharacterQueries.ADD_PLAYER_CHARACTER(pcWithoutID);
|
|
} catch (Exception e) {
|
|
Logger.error("generatePCFromCommitNewCharacterMsg", "An error occurred while saving new PlayerCharacter to DB", e);
|
|
return null;
|
|
}
|
|
|
|
if (playerCharacter == null) {
|
|
Logger.info("GOM Failed to create PlayerCharacter");
|
|
return null;
|
|
}
|
|
|
|
} // END synchronized(FirstNameLock)
|
|
|
|
// Add creation runes
|
|
for (RuneBase rb : usedRunes) {
|
|
CharacterRune runeWithoutID = new CharacterRune(rb, playerCharacter.getObjectUUID());
|
|
CharacterRune characterRune;
|
|
try {
|
|
characterRune = DbManager.CharacterRuneQueries.ADD_CHARACTER_RUNE(runeWithoutID);
|
|
} catch (Exception e) {
|
|
characterRune = null;
|
|
}
|
|
|
|
if (characterRune == null) {
|
|
playerCharacter.deactivateCharacter();
|
|
Logger.info("GOM Failed to create CharacterRune");
|
|
return null;
|
|
}
|
|
|
|
playerCharacter.addRune(characterRune);
|
|
}
|
|
|
|
if (hairStyleID != 0) {
|
|
|
|
// Create Hair
|
|
Item hair = new Item(hairStyleID);
|
|
hair.ownerID = playerCharacter.getObjectUUID();
|
|
hair.ownerType = OwnerType.PlayerCharacter;
|
|
hair.containerType = ItemContainerType.EQUIPPED;
|
|
hair.equipSlot = EquipSlotType.HAIR;
|
|
|
|
|
|
try {
|
|
hair = DbManager.ItemQueries.PERSIST(hair);
|
|
} catch (Exception e) {
|
|
hair = null;
|
|
}
|
|
|
|
if (hair == null) {
|
|
playerCharacter.deactivateCharacter();
|
|
Logger.info("GameObjectManager failed to create Hair:" + hairStyleID + " in Slot:"
|
|
+ EquipSlotType.HAIR);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (beardStyleID != 0) {
|
|
// Create Beard
|
|
Item beard = new Item(beardStyleID);
|
|
beard.ownerID = playerCharacter.getObjectUUID();
|
|
beard.ownerType = OwnerType.PlayerCharacter;
|
|
beard.containerType = ItemContainerType.EQUIPPED;
|
|
beard.equipSlot = EquipSlotType.BEARD;
|
|
|
|
try {
|
|
beard = DbManager.ItemQueries.PERSIST(beard);
|
|
} catch (Exception e) {
|
|
beard = null;
|
|
}
|
|
|
|
if (beard == null) {
|
|
playerCharacter.deactivateCharacter();
|
|
Logger.info("GameObjectManager failed to create Beard:" + beardStyleID + " in Slot:"
|
|
+ EquipSlotType.BEARD);
|
|
return null;
|
|
}
|
|
}
|
|
// Create items from Kit and equip on character.
|
|
try {
|
|
kit.equipPCwithKit(playerCharacter);
|
|
} catch (Exception e) {
|
|
Logger.info("Unable to find KIT ID for Race: " + raceID + "||" + "Class:" + baseClassID);
|
|
playerCharacter.deactivateCharacter();
|
|
return null;
|
|
}
|
|
|
|
// Get any new skills that belong to the player
|
|
playerCharacter.calculateSkills();
|
|
|
|
a.setLastCharacter(playerCharacter.getObjectUUID());
|
|
playerCharacter.charItemManager.load();
|
|
|
|
playerCharacter.activateCharacter();
|
|
|
|
return playerCharacter;
|
|
}
|
|
|
|
public static boolean isIgnoreListFull() {
|
|
return false; //Why were we setting a limit on ignores? -
|
|
//return (ignoredPlayerIDs.size() >= MBServerStatics.IGNORE_LIST_MAX);
|
|
}
|
|
|
|
public static boolean isIgnorable() {
|
|
return true;
|
|
// // if (account == null) return false;
|
|
// if (account.getAccessLevel() > 0) {
|
|
// return false;
|
|
// }
|
|
// return true;
|
|
}
|
|
|
|
/**
|
|
* @ Initialize player upon creation
|
|
*/
|
|
public static void initializePlayer(PlayerCharacter player) {
|
|
|
|
if (player.initialized)
|
|
return;
|
|
// Logger.info("", " Initializing " + player.getCombinedName());
|
|
player.skills = DbManager.CharacterSkillQueries.GET_SKILLS_FOR_CHARACTER(player);
|
|
player.powers = player.initializePowers();
|
|
|
|
|
|
if (ConfigManager.serverType.equals(ServerType.WORLDSERVER))
|
|
player.setLoc(player.bindLoc);
|
|
player.endLoc = Vector3fImmutable.ZERO;
|
|
|
|
//get level based on experience
|
|
player.level = (short) Experience.getLevel(player.exp);
|
|
|
|
player.setHealth(999999f);
|
|
player.mana.set(999999f);
|
|
player.stamina.set(999999f);
|
|
player.bonuses = new PlayerBonuses(player);
|
|
PlayerBonuses.InitializeBonuses(player);
|
|
player.resists = new Resists(player);
|
|
player.charItemManager.load();
|
|
|
|
if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)) {
|
|
|
|
//CharacterSkill.updateAllBaseAmounts(this);
|
|
CharacterPower.grantTrains(player);
|
|
|
|
// calculate skills. Make sure none are missing.
|
|
AbstractCharacter.runBonusesOnLoad(player);
|
|
|
|
PlayerCharacter.InitializeSkillsOnLoad(player);
|
|
|
|
//apply all bonuses
|
|
player.recalculatePlayerStats(true);
|
|
player.trainsAvailable.set(CharacterSkill.getTrainsAvailable(player));
|
|
|
|
if (player.trainsAvailable.get() < 0)
|
|
player.recalculateTrains();
|
|
|
|
//this.resists.calculateResists(this);
|
|
player.newChar = true;
|
|
|
|
//check current guild valid for player
|
|
player.checkGuildStatus();
|
|
|
|
player.setHealth(player.getHealthMax());
|
|
player.mana.set(player.manaMax);
|
|
player.stamina.set(player.staminaMax);
|
|
} else
|
|
player.setBindLoc(Vector3fImmutable.ZERO);
|
|
|
|
player.initialized = true;
|
|
|
|
String lastAscii = player.lastName.replaceAll("[^\\p{ASCII}]", "");
|
|
player.asciiLastName = lastAscii.equals(player.lastName);
|
|
}
|
|
|
|
public static void recalculatePlayerStatsOnLoad(PlayerCharacter pc) {
|
|
|
|
//calculate base stats
|
|
pc.calculateBaseStats();
|
|
|
|
//calculate base skills
|
|
CharacterSkill.updateAllBaseAmounts(pc);
|
|
pc.calculateModifiedStats();
|
|
|
|
//calculate modified skills
|
|
CharacterSkill.updateAllModifiedAmounts(pc);
|
|
|
|
|
|
//calculate modified stats
|
|
|
|
|
|
//calculate ATR, damage and defense
|
|
AbstractCharacter.calculateAtrDefenseDamage(pc);
|
|
|
|
//calculate movement bonus
|
|
pc.calculateSpeedMod();
|
|
|
|
// recalculate Max Health/Mana/Stamina
|
|
pc.calculateMaxHealthManaStamina();
|
|
|
|
// recalculate Resists
|
|
Resists.calculateResists(pc);
|
|
|
|
}
|
|
|
|
public static boolean hideNonAscii() {
|
|
|
|
return false;
|
|
}
|
|
|
|
public static float getDexPenalty(Item armor) {
|
|
if (armor == null)
|
|
return 0f;
|
|
ItemTemplate ab = armor.template;
|
|
if (ab == null)
|
|
return 0f;
|
|
return ab.item_bulk_factor;
|
|
}
|
|
|
|
public static void InitializeSkillsOnLoad(PlayerCharacter pc) {
|
|
try {
|
|
{
|
|
|
|
//see if any new skills or powers granted
|
|
CharacterSkill.calculateSkills(pc);
|
|
|
|
// calculate granted Trains in powers.
|
|
CharacterPower.grantTrains(pc);
|
|
|
|
//see if any new powers unlocked from previous check
|
|
CharacterPower.calculatePowers(pc);
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.error(e.getMessage());
|
|
}
|
|
|
|
}
|
|
|
|
public static String getFirstName(int tableId) {
|
|
|
|
PlayerCharacter player;
|
|
|
|
if (tableId == 0)
|
|
return "";
|
|
|
|
player = (PlayerCharacter) DbManager.getObject(GameObjectType.PlayerCharacter, tableId);
|
|
|
|
return player.getFirstName();
|
|
}
|
|
|
|
public static PlayerCharacter getFromCache(int id) {
|
|
return (PlayerCharacter) DbManager.getFromCache(GameObjectType.PlayerCharacter, id);
|
|
}
|
|
|
|
public static PlayerCharacter getByFirstName(String name) {
|
|
|
|
PlayerCharacter returnPlayer = null;
|
|
for (AbstractGameObject ago : DbManager.getList(GameObjectType.PlayerCharacter)) {
|
|
PlayerCharacter cachePlayer = (PlayerCharacter) ago;
|
|
if (!name.equalsIgnoreCase(cachePlayer.getFirstName()))
|
|
continue;
|
|
if (cachePlayer.isDeleted())
|
|
continue;
|
|
returnPlayer = cachePlayer;
|
|
break;
|
|
}
|
|
|
|
return returnPlayer;
|
|
}
|
|
|
|
public static PlayerCharacter getPlayerCharacter(int uuid) {
|
|
|
|
PlayerCharacter outPlayer;
|
|
|
|
outPlayer = DbManager.PlayerCharacterQueries.GET_PLAYER_CHARACTER(uuid);
|
|
|
|
if (outPlayer != null)
|
|
return outPlayer;
|
|
|
|
return (PlayerCharacter) DbManager.getFromCache(GameObjectType.PlayerCharacter, uuid);
|
|
}
|
|
|
|
public static int GetPlayerRealmTitle(PlayerCharacter player) {
|
|
|
|
if (player.getGuild().isEmptyGuild())
|
|
return 0;
|
|
if (!player.getGuild().isGuildLeader(player.getObjectUUID()))
|
|
return 0;
|
|
if (player.getGuild().getOwnedCity() == null)
|
|
return 10;
|
|
City city5 = player.getGuild().getOwnedCity();
|
|
if (city5.realm == null)
|
|
return 10;
|
|
City city4 = player.getGuild().getOwnedCity();
|
|
if (city4.realm.getRulingCity() == null)
|
|
return 10;
|
|
|
|
City city3 = player.getGuild().getOwnedCity();
|
|
if (city3.realm.getRulingCity().getObjectUUID() != player.getGuild().getOwnedCity().getObjectUUID())
|
|
return 10;
|
|
int realmTitle = 1;
|
|
if (player.getGuild().getSubGuildList() == null || player.getGuild().getSubGuildList().isEmpty())
|
|
return 11;
|
|
for (Guild subGuild : player.getGuild().getSubGuildList()) {
|
|
if (subGuild.getOwnedCity() == null)
|
|
continue;
|
|
City city2 = subGuild.getOwnedCity();
|
|
if (city2.realm == null)
|
|
continue;
|
|
City city1 = subGuild.getOwnedCity();
|
|
if (city1.realm.getRulingCity() == null)
|
|
continue;
|
|
City city = subGuild.getOwnedCity();
|
|
if (city.realm.getRulingCity().getObjectUUID() != subGuild.getOwnedCity().getObjectUUID())
|
|
continue;
|
|
realmTitle++;
|
|
}
|
|
|
|
if (realmTitle < 3)
|
|
return 11;
|
|
else if (realmTitle < 5)
|
|
return 12;
|
|
else
|
|
return 13;
|
|
}
|
|
|
|
public static void UpdateClientPlayerRank(PlayerCharacter pc) {
|
|
if (pc == null)
|
|
return;
|
|
boolean disable = true;
|
|
|
|
if (disable)
|
|
return;
|
|
UpdateCharOrMobMessage ucm = new UpdateCharOrMobMessage(pc, 2, pc.getRank());
|
|
DispatchManager.sendToAllInRange(pc, ucm);
|
|
}
|
|
|
|
public static void GroundPlayer(PlayerCharacter groundee) {
|
|
if (groundee.getDesiredAltitude() == 0 && groundee.getAltitude() == 0)
|
|
return;
|
|
groundee.setAltitude(groundee.getAltitude());
|
|
groundee.setDesiredAltitude(0);
|
|
groundee.setTakeOffTime(System.currentTimeMillis());
|
|
|
|
ChangeAltitudeMsg msg = ChangeAltitudeMsg.GroundPlayerMsg(groundee);
|
|
// force a landing
|
|
DispatchManager.dispatchMsgToInterestArea(groundee, msg, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, true, false);
|
|
|
|
}
|
|
|
|
public static boolean CanBreathe(PlayerCharacter breather) {
|
|
try {
|
|
if (breather.isFlying())
|
|
return true;
|
|
Zone zone = ZoneManager.findSmallestZone(breather.getLoc());
|
|
|
|
if (zone.sea_level != 0) {
|
|
|
|
float localAltitude = breather.getLoc().y;
|
|
|
|
|
|
if (localAltitude + breather.characterHeight < zone.sea_level - 2)
|
|
return false;
|
|
|
|
if (breather.isMoving()) {
|
|
if (localAltitude + breather.characterHeight < zone.sea_level)
|
|
return false;
|
|
}
|
|
} else {
|
|
if (breather.getLoc().y + breather.characterHeight < -2)
|
|
return false;
|
|
|
|
if (breather.isMoving()) {
|
|
if (breather.getLoc().y + breather.characterHeight < 0)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
Logger.info(breather.getName() + e);
|
|
}
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
public static boolean enterWater(PlayerCharacter enterer) {
|
|
|
|
try {
|
|
if (enterer.isFlying())
|
|
return false;
|
|
|
|
|
|
Zone zone = ZoneManager.findSmallestZone(enterer.getLoc());
|
|
|
|
if (zone.sea_level != 0) {
|
|
|
|
float localAltitude = enterer.getLoc().y + enterer.characterHeight;
|
|
|
|
|
|
if (localAltitude < zone.sea_level)
|
|
return true;
|
|
} else {
|
|
if (enterer.getLoc().y + enterer.characterHeight < 0)
|
|
return true;
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.info(enterer.getName() + e);
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
public static boolean LeaveWater(PlayerCharacter leaver) {
|
|
|
|
try {
|
|
|
|
|
|
Zone zone = ZoneManager.findSmallestZone(leaver.getLoc());
|
|
|
|
float leaveWater = leaver.centerHeight;
|
|
|
|
if (leaver.isMoving())
|
|
leaveWater = 1f;
|
|
|
|
|
|
if (zone.sea_level != 0) {
|
|
|
|
float localAltitude = leaver.getLoc().y;
|
|
|
|
|
|
if (localAltitude + leaveWater < zone.sea_level)
|
|
return false;
|
|
} else {
|
|
if (leaver.getLoc().y + leaveWater < 0)
|
|
return false;
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.info(leaver.getName() + e);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static boolean CanBindToBuilding(PlayerCharacter player, int buildingID) {
|
|
if (buildingID == 0)
|
|
return false;
|
|
|
|
Building bindBuilding = BuildingManager.getBuildingFromCache(buildingID);
|
|
|
|
if (bindBuilding == null)
|
|
return false;
|
|
|
|
if (!BuildingManager.playerCanManage(player, bindBuilding))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public void setGuildTitle(int value) {
|
|
if (GuildStatusController.getTitle(this.guildStatus) == value)
|
|
return;
|
|
DbManager.PlayerCharacterQueries.SET_GUILD_TITLE(this, value);
|
|
GuildStatusController.setTitle(guildStatus, value);
|
|
}
|
|
|
|
public void setFullMember(boolean value) {
|
|
if (GuildStatusController.isFullMember(this.guildStatus) == value)
|
|
return;
|
|
DbManager.PlayerCharacterQueries.SET_FULL_MEMBER(this, value);
|
|
GuildStatusController.setFullMember(guildStatus, value);
|
|
}
|
|
|
|
public void setRecruiter(boolean value) {
|
|
if (GuildStatusController.isRecruiter(this.guildStatus) == value)
|
|
return;
|
|
DbManager.PlayerCharacterQueries.SET_RECRUITER(this, value);
|
|
GuildStatusController.setRecruiter(guildStatus, value);
|
|
}
|
|
|
|
public void setTaxCollector(boolean value) {
|
|
if (GuildStatusController.isTaxCollector(this.guildStatus) == value)
|
|
return;
|
|
DbManager.PlayerCharacterQueries.SET_TAX_COLLECTOR(this, value);
|
|
GuildStatusController.setTaxCollector(guildStatus, value);
|
|
}
|
|
|
|
public void setInnerCouncil(boolean value) {
|
|
|
|
// dont update if its the same.
|
|
if (GuildStatusController.isInnerCouncil(this.guildStatus) == value)
|
|
return;
|
|
|
|
DbManager.PlayerCharacterQueries.SET_INNERCOUNCIL(this, value);
|
|
GuildStatusController.setInnerCouncil(guildStatus, value);
|
|
}
|
|
|
|
public void setGuildLeader(boolean value) {
|
|
if (GuildStatusController.isGuildLeader(this.guildStatus) == value)
|
|
return;
|
|
|
|
GuildStatusController.setGuildLeader(guildStatus, value);
|
|
if (value == true) {
|
|
this.setInnerCouncil(true);
|
|
this.setFullMember(true);
|
|
}
|
|
}
|
|
|
|
//END -> Guild Status Interface
|
|
public void resetGuildStatuses() {
|
|
this.setInnerCouncil(false);
|
|
this.setFullMember(false);
|
|
this.setGuildTitle(0);
|
|
this.setTaxCollector(false);
|
|
this.setRecruiter(false);
|
|
this.setGuildLeader(false);
|
|
}
|
|
|
|
/*
|
|
* 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.peace_zone == (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.peace_zone == 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.peace_zone == 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.peace_zone == 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);
|
|
DispatchManager.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.charItemManager.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);
|
|
this.respawn(false, false, true);
|
|
} else {
|
|
TargetedActionMsg killmsg = new TargetedActionMsg(this, true);
|
|
DispatchManager.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;
|
|
|
|
}
|
|
|
|
public void respawn(boolean setAlive, boolean enterWorld, boolean makeCorpse) {
|
|
|
|
// Recalculate everything
|
|
|
|
|
|
this.recalculatePlayerStats(true);
|
|
this.setCombat(false);
|
|
|
|
// Set Health to 1/4 max
|
|
|
|
|
|
Corpse corpse = null;
|
|
|
|
if (makeCorpse) {
|
|
try {
|
|
corpse = Corpse.makeCorpse(this, enterWorld);
|
|
} catch (Exception e) {
|
|
Logger.error(e);
|
|
}
|
|
//if we're not making corpse, just purge inventory. used for characters dead while logged out.
|
|
}
|
|
|
|
if (!setAlive) {
|
|
if (corpse == null && makeCorpse) {
|
|
Logger.error("Corpse not created.");
|
|
} else {
|
|
if (makeCorpse && corpse != null) {
|
|
InterestManager.forceLoad(corpse);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
this.setHealth((float) (healthMax * .25));
|
|
this.isAlive.set(true);
|
|
|
|
|
|
// Put player in safe mode
|
|
// Teleport the player to his bind loc
|
|
// or to a ruin as apporpriate.
|
|
|
|
Building bindBuilding = BuildingManager.getBuildingFromCache(this.getBindBuildingID());
|
|
|
|
if (enterWorld) {
|
|
this.stopMovement(this.getBindLoc());
|
|
} else if (bindBuilding != null) {
|
|
if (bindBuilding.getParentZone().equals(ZoneManager.findSmallestZone(this.getLoc())))
|
|
this.teleport(Ruins.getRandomRuin().getLocation());
|
|
else
|
|
this.teleport(this.getBindLoc());
|
|
} else // no bind building found for player, teleport to ruins.
|
|
this.teleport(Ruins.getRandomRuin().getLocation());
|
|
|
|
this.lastUpdateTime = System.currentTimeMillis();
|
|
this.lastStamUpdateTime = System.currentTimeMillis();
|
|
|
|
this.update();
|
|
|
|
PowersManager.applyPower(this, this, Vector3fImmutable.ZERO, -1661758934, 40, false);
|
|
|
|
if (corpse == null && makeCorpse) {
|
|
Logger.error("Corpse not created.");
|
|
} else {
|
|
if (makeCorpse && corpse != null) {
|
|
InterestManager.forceLoad(corpse);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Effect addCityEffect(String name, EffectsBase eb, int trains, int duration, boolean onEnter, City city) {
|
|
JobContainer jc = null;
|
|
if (onEnter) {
|
|
NoTimeJob ntj = new NoTimeJob(this, name, eb, trains); //infinite timer
|
|
ntj.setEffectSourceType(city.getObjectType().ordinal());
|
|
ntj.setEffectSourceID(city.getObjectUUID());
|
|
jc = new JobContainer(ntj);
|
|
} else {
|
|
FinishSpireEffectJob fsej = new FinishSpireEffectJob(this, name, eb, trains);
|
|
fsej.setEffectSourceType(city.getObjectType().ordinal());
|
|
fsej.setEffectSourceID(city.getObjectUUID());
|
|
jc = JobScheduler.getInstance().scheduleJob(fsej, duration);
|
|
}
|
|
|
|
if (this.effects.get(name) != null)
|
|
this.effects.get(name).cancelJob();
|
|
|
|
Effect eff = new Effect(jc, eb, trains);
|
|
this.effects.put(name, eff);
|
|
applyAllBonuses();
|
|
eff.sendSpireEffect(this.getClientConnection(), onEnter);
|
|
return eff;
|
|
}
|
|
|
|
/**
|
|
* @return the race
|
|
*/
|
|
|
|
|
|
public int getRaceID() {
|
|
if (race != null)
|
|
return race.getRaceRuneID();
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @return the 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 = PlayerCharacter.getUpdatedBindBuilding(this);
|
|
|
|
//handle rented room binds.
|
|
|
|
if (bindBuilding == null) {
|
|
bindLocation = mbEnums.Ruins.getRandomRuin().getLocation();
|
|
return bindLocation;
|
|
}
|
|
|
|
bindLocation = BuildingManager.GetBindLocationForBuilding(bindBuilding);
|
|
|
|
if (bindLocation == null)
|
|
bindLocation = mbEnums.Ruins.getRandomRuin().getLocation();
|
|
|
|
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);
|
|
this.absPromotionClass = mbEnums.ClassType.valueOf(promotionClass.getName());
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @return the lfGroup
|
|
*/
|
|
public boolean isLfGroup() {
|
|
return lfGroup;
|
|
}
|
|
|
|
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 void setLFGroup(final boolean value) {
|
|
this.lfGroup = value;
|
|
}
|
|
|
|
public final boolean isLFGuild() {
|
|
return this.lfGuild;
|
|
}
|
|
|
|
public final void setLFGuild(final boolean value) {
|
|
this.lfGuild = value;
|
|
}
|
|
|
|
public final boolean isRecruiting() {
|
|
return this.recruiting;
|
|
}
|
|
|
|
public final void setRecruiting(final boolean value) {
|
|
this.recruiting = value;
|
|
}
|
|
|
|
/**
|
|
* @return the lfGuild
|
|
*/
|
|
public boolean isLfGuild() {
|
|
return lfGuild;
|
|
}
|
|
|
|
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 getIsLfGuildAsInt() {
|
|
if (lfGuild)
|
|
return 2;
|
|
return 1;
|
|
}
|
|
|
|
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 addStr(int amount) {
|
|
|
|
boolean worked = false;
|
|
short newStr = (short) 0;
|
|
while (!worked) {
|
|
|
|
if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
|
|
return;
|
|
|
|
newStr = (short) (this.statStrBase + amount);
|
|
short mod = (short) this.strMod.get();
|
|
short newStrMod = (short) (mod + amount);
|
|
|
|
if (newStr > this.statStrMax) {
|
|
newStrMod += (this.statStrMax - newStr);
|
|
newStr = this.statStrMax;
|
|
}
|
|
worked = this.strMod.compareAndSet(mod, newStrMod);
|
|
}
|
|
this.trainedStatPoints++;
|
|
this.statStrBase = newStr;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
}
|
|
|
|
public void addDex(int amount) {
|
|
|
|
boolean worked = false;
|
|
short newDex = (short) 0;
|
|
|
|
while (!worked) {
|
|
|
|
if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
|
|
return;
|
|
|
|
newDex = (short) (this.statDexBase + amount);
|
|
short mod = (short) this.dexMod.get();
|
|
short newDexMod = (short) (mod + amount);
|
|
|
|
if (newDex > this.statDexMax) {
|
|
newDexMod += (this.statDexMax - newDex);
|
|
newDex = this.statDexMax;
|
|
}
|
|
|
|
worked = this.dexMod.compareAndSet(mod, newDexMod);
|
|
}
|
|
this.trainedStatPoints++;
|
|
this.statDexBase = newDex;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
}
|
|
|
|
public void addCon(int amount) {
|
|
boolean worked = false;
|
|
short newCon = (short) 0;
|
|
while (!worked) {
|
|
|
|
if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
|
|
return;
|
|
|
|
newCon = (short) (this.statConBase + amount);
|
|
short mod = (short) this.conMod.get();
|
|
short newConMod = (short) (mod + amount);
|
|
|
|
if (newCon > this.statConMax) {
|
|
newConMod += (this.statConMax - newCon);
|
|
newCon = this.statConMax;
|
|
}
|
|
worked = this.conMod.compareAndSet(mod, newConMod);
|
|
}
|
|
this.trainedStatPoints++;
|
|
this.statConBase = newCon;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
}
|
|
|
|
public void addInt(int amount) {
|
|
boolean worked = false;
|
|
short newInt = (short) 0;
|
|
while (!worked) {
|
|
|
|
if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
|
|
return;
|
|
|
|
newInt = (short) (this.statIntBase + amount);
|
|
short mod = (short) this.intMod.get();
|
|
short newIntMod = (short) (mod + amount);
|
|
|
|
if (newInt > this.statIntMax) {
|
|
newIntMod += (this.statIntMax - newInt);
|
|
newInt = this.statIntMax;
|
|
}
|
|
worked = this.intMod.compareAndSet(mod, newIntMod);
|
|
}
|
|
this.trainedStatPoints++;
|
|
this.statIntBase = newInt;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
}
|
|
|
|
public void addSpi(int amount) {
|
|
boolean worked = false;
|
|
short newSpi = (short) 0;
|
|
|
|
while (!worked) {
|
|
|
|
if ((this.unusedStatPoints - this.trainedStatPoints) <= 0)
|
|
return;
|
|
|
|
newSpi = (short) (this.statSpiBase + amount);
|
|
short mod = (short) this.spiMod.get();
|
|
short newSpiMod = (short) (mod + amount);
|
|
|
|
if (newSpi > this.statSpiMax) {
|
|
newSpiMod += (this.statSpiMax - newSpi);
|
|
newSpi = this.statSpiMax;
|
|
}
|
|
worked = this.spiMod.compareAndSet(mod, newSpiMod);
|
|
}
|
|
this.trainedStatPoints++;
|
|
this.statSpiBase = newSpi;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
}
|
|
|
|
public boolean refineStr() {
|
|
boolean worked = false;
|
|
short newStr = (short) 0;
|
|
|
|
while (!worked) {
|
|
|
|
newStr = (short) (this.statStrBase - 1);
|
|
short mod = (short) this.strMod.get();
|
|
|
|
if (mod == 0)
|
|
return false;
|
|
|
|
short newStrMod = (short) (mod - 1);
|
|
|
|
if (newStr < this.statStrMin)
|
|
return false;
|
|
|
|
if (!canRefineLower(MBServerStatics.STAT_STR_ID))
|
|
return false;
|
|
|
|
worked = this.strMod.compareAndSet(mod, newStrMod);
|
|
}
|
|
this.trainedStatPoints--;
|
|
this.statStrBase = newStr;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
return true;
|
|
}
|
|
|
|
public boolean refineDex() {
|
|
boolean worked = false;
|
|
short newDex = (short) 0;
|
|
|
|
while (!worked) {
|
|
newDex = (short) (this.statDexBase - 1);
|
|
short mod = (short) this.dexMod.get();
|
|
|
|
if (mod == 0)
|
|
return false;
|
|
|
|
short newDexMod = (short) (mod - 1);
|
|
|
|
if (newDex < this.statDexMin)
|
|
return false;
|
|
|
|
if (!canRefineLower(MBServerStatics.STAT_DEX_ID))
|
|
return false;
|
|
|
|
worked = this.dexMod.compareAndSet(mod, newDexMod);
|
|
}
|
|
this.trainedStatPoints--;
|
|
this.statDexBase = newDex;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
return true;
|
|
}
|
|
|
|
public boolean refineCon() {
|
|
boolean worked = false;
|
|
short newCon = (short) 0;
|
|
|
|
while (!worked) {
|
|
newCon = (short) (this.statConBase - 1);
|
|
short mod = (short) this.conMod.get();
|
|
|
|
if (mod == 0)
|
|
return false;
|
|
|
|
short newConMod = (short) (mod - 1);
|
|
|
|
if (newCon < this.statConMin)
|
|
return false;
|
|
|
|
if (!canRefineLower(MBServerStatics.STAT_CON_ID))
|
|
return false;
|
|
|
|
worked = this.conMod.compareAndSet(mod, newConMod);
|
|
}
|
|
this.trainedStatPoints--;
|
|
this.statConBase = newCon;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
return true;
|
|
}
|
|
|
|
public boolean refineInt(RefineMsg msg) {
|
|
boolean worked = false;
|
|
short newInt = (short) 0;
|
|
|
|
while (!worked) {
|
|
newInt = (short) (this.statIntBase - 1);
|
|
short mod = (short) this.intMod.get();
|
|
|
|
if (mod == 0)
|
|
return false;
|
|
short newIntMod = (short) (mod
|
|
- 1);
|
|
|
|
if (newInt < this.statIntMin)
|
|
return false;
|
|
|
|
if (!canRefineLower(MBServerStatics.STAT_INT_ID))
|
|
return false;
|
|
|
|
worked = this.intMod.compareAndSet(mod, newIntMod);
|
|
}
|
|
this.trainedStatPoints--;
|
|
this.statIntBase = newInt;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
|
|
verifySkillMax(msg);
|
|
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
return true;
|
|
}
|
|
|
|
public boolean refineSpi() {
|
|
boolean worked = false;
|
|
short newSpi = (short) 0;
|
|
while (!worked) {
|
|
newSpi = (short) (this.statSpiBase - 1);
|
|
short mod = (short) this.spiMod.get();
|
|
if (mod == 0)
|
|
return false;
|
|
short newSpiMod = (short) (mod - 1);
|
|
if (newSpi < this.statSpiMin)
|
|
return false;
|
|
if (!canRefineLower(MBServerStatics.STAT_SPI_ID))
|
|
return false;
|
|
worked = this.spiMod.compareAndSet(mod, newSpiMod);
|
|
}
|
|
this.trainedStatPoints--;
|
|
this.statSpiBase = newSpi;
|
|
this.addDatabaseJob("Stats", MBServerStatics.THIRTY_SECONDS);
|
|
this.applyBonuses();
|
|
this.calculateSkills();
|
|
return true;
|
|
}
|
|
|
|
//this verifies stat doesn't fall too low to keep runes applied while refining
|
|
private boolean canRefineLower(int stat) {
|
|
for (CharacterRune cr : this.runes) {
|
|
if (cr != null) {
|
|
RuneBase rb = cr.getRuneBase();
|
|
if (rb != null) {
|
|
ArrayList<RuneBaseAttribute> attrs = rb.getAttrs();
|
|
|
|
if (attrs != null)
|
|
for (RuneBaseAttribute rba : attrs) {
|
|
int attrID = rba.getAttributeID();
|
|
int mod = rba.getModValue();
|
|
if (stat == MBServerStatics.STAT_STR_ID) {
|
|
if (attrID == MBServerStatics.RUNE_STR_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statStrBase <= mod))
|
|
return false;
|
|
} else if (stat == MBServerStatics.STAT_DEX_ID) {
|
|
if (attrID == MBServerStatics.RUNE_DEX_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statDexBase <= mod))
|
|
return false;
|
|
} else if (stat == MBServerStatics.STAT_CON_ID) {
|
|
if (attrID == MBServerStatics.RUNE_CON_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statConBase <= mod))
|
|
return false;
|
|
} else if (stat == MBServerStatics.STAT_INT_ID) {
|
|
if (attrID == MBServerStatics.RUNE_INT_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statIntBase <= mod))
|
|
return false;
|
|
} else if (stat == MBServerStatics.STAT_SPI_ID)
|
|
if (attrID == MBServerStatics.RUNE_SPI_MIN_NEEDED_ATTRIBUTE_ID && ((int) this.statSpiBase <= mod))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//checked on refining int to see if skills need refined also.
|
|
private void verifySkillMax(RefineMsg msg) {
|
|
|
|
ConcurrentHashMap<String, CharacterSkill> skills = getSkills();
|
|
|
|
//make sure no skills are over the max number of trains
|
|
int maxTrains = CharacterSkill.getMaxTrains((int) this.statIntBase);
|
|
|
|
RefineMsg rm = new RefineMsg(msg.getNpcType(), msg.getNpcID(), 0, 0);
|
|
|
|
for (CharacterSkill skill : skills.values()) {
|
|
|
|
while (skill.getNumTrains() > maxTrains) {
|
|
boolean worked = skill.refine(this, false); //refine skill, do not recalculate everything
|
|
if (worked) {
|
|
rm.setToken(skill.getToken());
|
|
|
|
Dispatch dispatch = Dispatch.borrow(this, rm);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
|
|
} else {
|
|
Logger.error("Failed to force refine of skill " + skill.getObjectUUID() + " by character " + this.getObjectUUID());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int getClassToken() {
|
|
if (this.promotionClass != null)
|
|
return this.promotionClass.getToken();
|
|
else if (this.baseClass != null)
|
|
return this.baseClass.getToken();
|
|
return 0;
|
|
}
|
|
|
|
public int getRaceToken() {
|
|
|
|
if (this.race == null)
|
|
return 0;
|
|
|
|
return this.race.getToken();
|
|
}
|
|
|
|
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.buildingUUID;
|
|
}
|
|
|
|
public synchronized void setBindBuildingID(int value) {
|
|
DbManager.PlayerCharacterQueries.SET_BIND_BUILDING(this, value);
|
|
this.buildingUUID = 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 void setHidden(int value) {
|
|
this.hidden = value;
|
|
}
|
|
|
|
public int getSeeInvis() {
|
|
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(EquipSlotType slot, String type) {
|
|
|
|
if (this.charItemManager == null)
|
|
return false;
|
|
|
|
Item item = this.charItemManager.getEquipped(slot);
|
|
|
|
if (item == null)
|
|
return false;
|
|
|
|
ItemTemplate ib = item.template;
|
|
if (ib != null) {
|
|
|
|
if ((item.template.item_type.equals(ItemType.WEAPON))
|
|
&& (item.template.item_skill_used.equals(type) || ib.item_skill_mastery_used.equals(type)))
|
|
return true;
|
|
|
|
return (item.template.item_type.equals(ItemType.ARMOR))
|
|
&& (item.template.item_skill_used.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 (this.getAltitude() > 0)
|
|
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 endSpeed = speed * this.speedMod;
|
|
|
|
if (endSpeed > 41 && !this.isCSR)
|
|
endSpeed = 41;
|
|
|
|
return endSpeed;
|
|
}
|
|
|
|
public synchronized void grantXP(int xp) {
|
|
// Stop players from getting experience past the cap
|
|
if (this.exp + xp >= Experience.getBaseExperience(MBServerStatics.LEVELCAP))
|
|
xp = Experience.getBaseExperience(MBServerStatics.LEVELCAP) - this.exp + 1;
|
|
|
|
if (xp == 0)
|
|
xp = 1;
|
|
|
|
boolean isNewLevel = false;
|
|
boolean charReloadRequired = false;
|
|
int remainingXP = xp;
|
|
int neededXP = 0;
|
|
|
|
// handle players that have not yet promoted.
|
|
ClientConnection origin = this.getClientConnection();
|
|
|
|
//not promoted at level 10, start checking for negative EXP
|
|
if (this.promotionClass == null && this.getLevel() == 10) {
|
|
|
|
if (this.getExp() == Experience.getBaseExperience(11)) {
|
|
if (this.overFlowEXP == 110000)
|
|
return;
|
|
|
|
if (this.overFlowEXP + xp > 110000) {
|
|
remainingXP = 110000 - this.overFlowEXP;
|
|
this.overFlowEXP = 110000;
|
|
|
|
|
|
} else {
|
|
this.overFlowEXP += remainingXP;
|
|
}
|
|
|
|
GrantExperienceMsg gem = new GrantExperienceMsg(this, remainingXP);
|
|
Dispatch dispatch = Dispatch.borrow(this, gem);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
|
|
this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
|
|
return;
|
|
//didnt reach level 11 EXP to start overflow, add exp normally till we get here;
|
|
} else {
|
|
|
|
//Player exp begins negative exp, add remaing exp after level 11 to overflow
|
|
if (this.getExp() + remainingXP >= Experience.getBaseExperience(11)) {
|
|
|
|
this.overFlowEXP = remainingXP - (Experience.getBaseExperience(11) - this.getExp());
|
|
this.exp = Experience.getBaseExperience(11);
|
|
|
|
GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP);
|
|
Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
|
|
this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
|
|
return;
|
|
|
|
//didnt reach negative exp yet, just do normal exp gain.
|
|
} else {
|
|
this.exp += remainingXP;
|
|
GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP);
|
|
remainingXP = 0;
|
|
Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
|
|
this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.overFlowEXP > 0) {
|
|
|
|
int nextLevel;
|
|
|
|
if (level == 10)
|
|
nextLevel = 12;
|
|
else
|
|
nextLevel = level + 2;
|
|
|
|
int nextLevelEXP = Experience.getBaseExperience(nextLevel);
|
|
|
|
// if overflow > 0, u have level 11 experience + overflow, but level is still 10 due to just promoting.
|
|
//Use level + 2 experience for next level.
|
|
this.overFlowEXP += 1;
|
|
|
|
if (this.getExp() + this.overFlowEXP >= nextLevelEXP) {
|
|
|
|
int expToNextLevel = nextLevelEXP - this.getExp();
|
|
this.overFlowEXP -= expToNextLevel;
|
|
this.exp += expToNextLevel;
|
|
this.level++;
|
|
charReloadRequired = true;
|
|
|
|
GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, 1);
|
|
Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
|
|
SetObjectValueMsg upm = new SetObjectValueMsg(this, 9);
|
|
DispatchManager.dispatchMsgToInterestArea(this, upm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
|
|
checkGuildStatus();
|
|
this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
|
|
// double overflow exp used up, remaining overflow will just add to level + 1.
|
|
} else if (this.getExp() + this.overFlowEXP >= Experience.getBaseExperience(level + 1)) {
|
|
int nextExperience = Experience.getBaseExperience(level + 1) + this.overFlowEXP;
|
|
this.exp = nextExperience;
|
|
this.level++;
|
|
charReloadRequired = true;
|
|
this.overFlowEXP = 0;
|
|
GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, 1);
|
|
Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
SetObjectValueMsg upm = new SetObjectValueMsg(this, 9);
|
|
DispatchManager.dispatchMsgToInterestArea(this, upm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
|
|
checkGuildStatus();
|
|
this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
|
|
}
|
|
|
|
} else {
|
|
// Hand out each Level one at a time.
|
|
isNewLevel = Experience.getLevel(exp + remainingXP) > this.getLevel();
|
|
|
|
if (isNewLevel) {
|
|
neededXP = Experience.getBaseExperience(this.getLevel() + 1) - this.exp;
|
|
|
|
charReloadRequired = true;
|
|
this.exp += neededXP;
|
|
this.level++;
|
|
|
|
GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, neededXP);
|
|
Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
|
|
remainingXP -= neededXP;
|
|
|
|
//Send newLevel.
|
|
SetObjectValueMsg upm = new SetObjectValueMsg(this, 9);
|
|
DispatchManager.dispatchMsgToInterestArea(this, upm, DispatchChannel.PRIMARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false);
|
|
checkGuildStatus();
|
|
} else {
|
|
|
|
this.exp += remainingXP;
|
|
GrantExperienceMsg grantExperienceMsg = new GrantExperienceMsg(this, remainingXP);
|
|
remainingXP = 0;
|
|
Dispatch dispatch = Dispatch.borrow(this, grantExperienceMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.SECONDARY);
|
|
|
|
this.addDatabaseJob("EXP", MBServerStatics.FIVE_MINUTES);
|
|
}
|
|
}
|
|
|
|
if (charReloadRequired) {
|
|
this.update();
|
|
this.incVer();
|
|
this.recalculate();
|
|
this.calculateMaxHealthManaStamina();
|
|
this.setHealth(this.healthMax);
|
|
this.mana.set(this.manaMax);
|
|
this.stamina.set(this.staminaMax);
|
|
//LoadJob.reloadCharacter(this);
|
|
DbManager.PlayerCharacterQueries.SET_PROPERTY(this, "char_experience", this.exp);
|
|
// updateDatabase();
|
|
DbManager.AccountQueries.INVALIDATE_LOGIN_CACHE(this.getObjectUUID(), "character");
|
|
}
|
|
}
|
|
|
|
//This checks if a player meets the requirements to be in current guild.
|
|
public void checkGuildStatus() {
|
|
|
|
Guild g = this.guild;
|
|
|
|
if (g == null || g.isEmptyGuild() || GuildStatusController.isGuildLeader(guildStatus))
|
|
return;
|
|
|
|
//check level
|
|
int curLevel = (int) getPCLevel();
|
|
if (curLevel < g.getRepledgeMin() || curLevel >= g.getRepledgeKick()) {
|
|
//TODO kick from guild
|
|
g.removePlayer(this, GuildHistoryType.LEAVE);
|
|
ChatManager.chatGuildInfo(this, "You no longer meet the level requirements for the guild.");
|
|
}
|
|
}
|
|
|
|
public void calculateSpeedMod() {
|
|
// get base race speed modifer
|
|
|
|
|
|
//this is retarded. *** Refactor
|
|
// if (this.race != null) {
|
|
// int ID = this.race.getObjectUUID();
|
|
// if (ID == 2004 || ID == 2005)
|
|
// this.raceRunMod = 1.21f; // centaur run bonus 22%
|
|
//// else if (ID == 2017)
|
|
//// this.raceRunMod = 1.14f; // mino run bonus 15%
|
|
// else
|
|
// this.raceRunMod = 1;
|
|
// } else
|
|
// this.raceRunMod = 1;
|
|
|
|
|
|
float bonus = 1f;
|
|
|
|
// // TODO: hardcoded, as didnt have time to introduce DB column to base object
|
|
// if (baseClass.getName().equals("Fighter") || baseClass.getName().equals("Rogue"))
|
|
// bonus += .05f;
|
|
|
|
// get running skill
|
|
if (this.skills != null) {
|
|
CharacterSkill running = this.skills.get("Running");
|
|
if (running != null) {
|
|
|
|
float runningBonus = (float) (Math.log(Math.round(running.getModifiedAmount()) * .01f) / Math.log(2) * .50f);
|
|
runningBonus = (float) (Math.pow(2, runningBonus) - 1);
|
|
runningBonus += 1;
|
|
runningBonus *= .25f;
|
|
bonus += runningBonus;
|
|
|
|
}
|
|
}
|
|
|
|
if (this.bonuses != null)
|
|
// get rune and effect bonuses
|
|
bonus += this.bonuses.getFloatPercentNullZero(ModType.Speed, SourceType.None);
|
|
|
|
// TODO get equip bonus
|
|
this.update();
|
|
this.speedMod = bonus;
|
|
}
|
|
|
|
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 void setLoadedStaticObjects(HashSet<AbstractWorldObject> value) {
|
|
this.loadedStaticObjects = value;
|
|
}
|
|
|
|
public boolean isTeleportMode() {
|
|
return teleportMode;
|
|
}
|
|
|
|
public void setTeleportMode(boolean teleportMode) {
|
|
this.teleportMode = teleportMode;
|
|
}
|
|
|
|
// public ConcurrentHashMap<Integer, FinishRecycleTimeJob>
|
|
// getRecycleTimers() {
|
|
// return this.recycleTimers;
|
|
// }
|
|
// public UsePowerJob getLastPower() {
|
|
// return this.lastPower;
|
|
// }
|
|
// public void setLastPower(UsePowerJob value) {
|
|
// this.lastPower = value;
|
|
// }
|
|
// public void clearLastPower() {
|
|
// this.lastPower = null;
|
|
// }
|
|
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
|
|
calculateBaseStats();
|
|
|
|
//calculate base skills
|
|
CharacterSkill.updateAllBaseAmounts(this);
|
|
calculateModifiedStats();
|
|
|
|
//calculate modified skills
|
|
CharacterSkill.updateAllModifiedAmounts(this);
|
|
this.updateScaleHeight();
|
|
|
|
//calculate modified stats
|
|
|
|
|
|
//calculate ATR, damage and defense
|
|
calculateAtrDefenseDamage(this);
|
|
|
|
//calculate movement bonus
|
|
calculateSpeedMod();
|
|
|
|
// recalculate Max Health/Mana/Stamina
|
|
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.
|
|
private 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;
|
|
|
|
this.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 *= this.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 *= this.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<EquipSlotType, Item> equipped = this.charItemManager.getEquipped();
|
|
float dexPenalty = 0f;
|
|
dexPenalty += getDexPenalty(equipped.get(EquipSlotType.HELM));
|
|
dexPenalty += getDexPenalty(equipped.get(EquipSlotType.CHEST));
|
|
dexPenalty += getDexPenalty(equipped.get(EquipSlotType.UPARM));
|
|
dexPenalty += getDexPenalty(equipped.get(EquipSlotType.HANDS));
|
|
dexPenalty += getDexPenalty(equipped.get(EquipSlotType.LEGS));
|
|
dexPenalty += getDexPenalty(equipped.get(EquipSlotType.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;
|
|
}
|
|
|
|
public void setPassives() {
|
|
if (this.bonuses != null) {
|
|
ConcurrentHashMap<EquipSlotType, Item> equipped = this.charItemManager.getEquipped();
|
|
Item off = equipped.get(EquipSlotType.LHELD);
|
|
Item main = equipped.get(EquipSlotType.RHELD);
|
|
ItemTemplate wbMain = null;
|
|
ItemTemplate wbOff = null;
|
|
if (main != null)
|
|
wbMain = main.template;
|
|
if (off != null)
|
|
wbOff = off.template;
|
|
|
|
//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.template != null && ItemManager.isShield(off))
|
|
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.item_weapon_max_range < MBServerStatics.RANGED_WEAPON_RANGE)
|
|
if (wbOff == null || wbOff.item_weapon_max_range < MBServerStatics.RANGED_WEAPON_RANGE)
|
|
this.bonuses.setBool(ModType.Parry, SourceType.None, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//Call this function to recalculate granted skills and powers for player
|
|
public synchronized void calculateSkills() {
|
|
//tell the player to applyBonuses because something has changed
|
|
|
|
AbstractCharacter.runSkillCalc(this);
|
|
|
|
//start running the skill/power calculations
|
|
}
|
|
|
|
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 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();
|
|
}
|
|
|
|
public void updateSkillsAndPowersToDatabase() {
|
|
if (this.skills != null)
|
|
for (CharacterSkill skill : this.skills.values()) {
|
|
DbManager.CharacterSkillQueries.UPDATE_TRAINS(skill);
|
|
if (this.powers != null)
|
|
for (CharacterPower power : this.powers.values()) {
|
|
DbManager.CharacterPowerQueries.UPDATE_TRAINS(power);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void updateDatabase() {
|
|
}
|
|
|
|
@Override
|
|
public void runAfterLoad() {
|
|
|
|
Bounds playerBounds = Bounds.borrow();
|
|
playerBounds.setBounds(this.getLoc());
|
|
this.setBounds(playerBounds);
|
|
|
|
//assign enum values for restrictions
|
|
String race = this.race.getName().replace("-", "").replace(", Male", "").replace(", Female", "");
|
|
this.absRace = mbEnums.MonsterType.valueOf(race);
|
|
|
|
if (this.baseClass != null)
|
|
this.absBaseClass = ClassType.valueOf(this.baseClass.getName());
|
|
|
|
if (this.promotionClass != null)
|
|
this.absPromotionClass = ClassType.valueOf(this.getPromotionClass().getName());
|
|
|
|
if (this.isMale())
|
|
this.absGender = SexType.MALE;
|
|
else
|
|
this.absGender = SexType.FEMALE;
|
|
|
|
|
|
for (CharacterRune rune : this.runes) {
|
|
try {
|
|
DisciplineType disc = DisciplineType.valueOf(RuneBase.getRuneBase(rune.getRuneBaseID()).getName().replace("-", "").replace(" ", ""));
|
|
if (disc != null) {
|
|
if (this.absDisciplines == null)
|
|
this.absDisciplines = EnumSet.of(disc);
|
|
else
|
|
this.absDisciplines.add(disc);
|
|
}
|
|
} catch (Exception e) {
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected 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) {
|
|
grantXP(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;
|
|
PlayerCharacter.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);
|
|
}
|
|
}
|
|
|
|
public boolean commandSiegeMinion(Mob toCommand) {
|
|
if (!toCommand.isSiege())
|
|
return false;
|
|
if (toCommand.isPet() || !toCommand.isAlive())
|
|
return false;
|
|
|
|
if (toCommand.getGuild().getNation() != this.getGuild().getNation())
|
|
return false;
|
|
|
|
if (this.pet != null) {
|
|
Mob currentPet = this.pet;
|
|
if (!currentPet.isSiege()) {
|
|
|
|
currentPet.setCombatTarget(null);
|
|
|
|
if (currentPet.parentZone != null) {
|
|
currentPet.parentZone.zoneMobSet.remove(currentPet);
|
|
}
|
|
|
|
try {
|
|
currentPet.clearEffects();
|
|
} catch (Exception e) {
|
|
Logger.error(e.getMessage());
|
|
}
|
|
currentPet.playerAgroMap.clear();
|
|
WorldGrid.RemoveWorldObject(currentPet);
|
|
DbManager.removeFromCache(currentPet);
|
|
|
|
} else if (currentPet.isSiege()) {
|
|
currentPet.agentType = AIAgentType.MOBILE;
|
|
|
|
// released siege engine keeps its current target
|
|
|
|
currentPet.guardCaptain = null;
|
|
if (currentPet.isAlive())
|
|
WorldGrid.updateObject(currentPet);
|
|
}
|
|
}
|
|
|
|
toCommand.setPet(this, false);
|
|
this.setPet(toCommand);
|
|
PetMsg petMsg = new PetMsg(6, toCommand);
|
|
Dispatch dispatch = Dispatch.borrow(this, petMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
|
|
|
|
if (toCommand.isAlive())
|
|
WorldGrid.updateObject(toCommand);
|
|
return true;
|
|
}
|
|
|
|
// Method is called by Server Heartbeat simulation tick.
|
|
// Stat regen and transform updates should go in here.
|
|
|
|
public boolean isNoTeleScreen() {
|
|
return noTeleScreen;
|
|
}
|
|
|
|
public void setNoTeleScreen(boolean noTeleScreen) {
|
|
this.noTeleScreen = noTeleScreen;
|
|
}
|
|
|
|
private double getDeltaTime() {
|
|
|
|
return (System.currentTimeMillis() - lastUpdateTime) * .001f;
|
|
}
|
|
|
|
private double getStamDeltaTime() {
|
|
|
|
return (System.currentTimeMillis() - lastStamUpdateTime) * .001f;
|
|
}
|
|
|
|
public boolean isFlying() {
|
|
|
|
return this.getAltitude() > 0;
|
|
|
|
}
|
|
|
|
public boolean isSwimming() {
|
|
|
|
// If char is flying they aren't quite swimming
|
|
try {
|
|
if (this.isFlying())
|
|
return false;
|
|
|
|
Zone zone = ZoneManager.findSmallestZone(this.getLoc());
|
|
|
|
if (zone.sea_level != 0) {
|
|
|
|
float localAltitude = this.getLoc().y + this.centerHeight;
|
|
if (localAltitude < zone.sea_level)
|
|
return true;
|
|
} else {
|
|
if (this.getLoc().y + this.centerHeight < 0)
|
|
return true;
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.info(this.getName() + e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public boolean isSwimming(Vector3fImmutable currentLoc) {
|
|
|
|
// If char is flying they aren't quite swimming
|
|
try {
|
|
|
|
float localAltitude = Terrain.getWorldHeight(currentLoc);
|
|
|
|
Zone zone = ZoneManager.findSmallestZone(currentLoc);
|
|
|
|
if (zone.sea_level != 0) {
|
|
|
|
if (localAltitude < zone.sea_level)
|
|
return true;
|
|
} else {
|
|
if (localAltitude < 0)
|
|
return true;
|
|
}
|
|
} catch (Exception e) {
|
|
Logger.info(this.getName() + e);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void update() {
|
|
|
|
if (this.updateLock.writeLock().tryLock()) {
|
|
try {
|
|
|
|
if (!this.isAlive())
|
|
return;
|
|
|
|
updateLocation();
|
|
updateMovementState();
|
|
updateRegen();
|
|
|
|
if (this.getStamina() < 10) {
|
|
if (this.getAltitude() > 0 || this.getDesiredAltitude() > 0) {
|
|
PlayerCharacter.GroundPlayer(this);
|
|
updateRegen();
|
|
}
|
|
}
|
|
|
|
RealmMap.updateRealm(this);
|
|
updateBlessingMessage();
|
|
|
|
this.safeZone = this.isInSafeZone();
|
|
|
|
} 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(Terrain.getWorldHeight(this.getLoc()) + this.getAltitude());
|
|
|
|
this.setTakeOffTime(0);
|
|
MovementManager.finishChangeAltitude(this, this.getDesiredAltitude());
|
|
|
|
return;
|
|
}
|
|
|
|
this.loc = this.loc.setY(Terrain.getWorldHeight(this.getLoc()) + this.getAltitude());
|
|
}
|
|
|
|
public boolean hasBoon() {
|
|
for (Effect eff : this.getEffects().values()) {
|
|
if (eff.getPowerToken() == -587743986 || eff.getPowerToken() == -1660519801 || eff.getPowerToken() == -1854683250)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void updateBlessingMessage() {
|
|
|
|
if (this.getTimeStamp("RealmClaim") > System.currentTimeMillis())
|
|
return;
|
|
|
|
int count = 0;
|
|
|
|
for (Effect eff : this.getEffects().values()) {
|
|
if (eff.getPowerToken() == -587743986 || eff.getPowerToken() == -1660519801 || eff.getPowerToken() == -1854683250)
|
|
count++;
|
|
}
|
|
|
|
if (count > 0) {
|
|
this.timestamps.put("RealmClaim", DateTime.now().plusMinutes(3).getMillis());
|
|
for (PlayerCharacter toSend : SessionManager.getAllActivePlayerCharacters()) {
|
|
ChatManager.chatSystemInfo(toSend, this.getCombinedName() + " is seeking to claim a realm and already has " + count + " blessngs!");
|
|
}
|
|
}
|
|
}
|
|
|
|
@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);
|
|
return;
|
|
}
|
|
|
|
if (newLoc.equals(this.getEndLoc())) {
|
|
this.stopMovement(newLoc);
|
|
return;
|
|
}
|
|
|
|
setLoc(newLoc);
|
|
|
|
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 = PlayerCharacter.enterWater(this);
|
|
|
|
if (enterWater) {
|
|
this.lastSwimming = enterWater;
|
|
MovementManager.sendRWSSMsg(this);
|
|
|
|
}
|
|
} else {
|
|
if (PlayerCharacter.LeaveWater(this)) {
|
|
this.lastSwimming = false;
|
|
if (!this.isMoving())
|
|
MovementManager.sendRWSSMsg(this);
|
|
}
|
|
|
|
}
|
|
|
|
boolean breathe = PlayerCharacter.CanBreathe(this);
|
|
|
|
if (breathe != this.canBreathe) {
|
|
this.canBreathe = breathe;
|
|
// ChatManager.chatSystemInfo(this, "Breathe : " + this.canBreathe);
|
|
this.syncClient();
|
|
}
|
|
}
|
|
|
|
//char is flying
|
|
if (this.isFlying() == 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 (!PlayerCharacter.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)
|
|
&& (PlayerCharacter.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 (!PlayerCharacter.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);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
|
|
|
|
}
|
|
|
|
public MovementState getMovementState() {
|
|
return movementState;
|
|
}
|
|
|
|
public boolean isHasAnniversery() {
|
|
return hasAnniversery;
|
|
}
|
|
|
|
public void setHasAnniversery(boolean hasAnniversery) {
|
|
DbManager.PlayerCharacterQueries.SET_ANNIVERSERY(this, hasAnniversery);
|
|
this.hasAnniversery = hasAnniversery;
|
|
}
|
|
|
|
public int getSpamCount() {
|
|
return spamCount;
|
|
}
|
|
|
|
public void setSpamCount(int spamCount) {
|
|
this.spamCount = spamCount;
|
|
}
|
|
|
|
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 moveTo(Vector3fImmutable endLoc) {
|
|
this.setInBuilding(-1);
|
|
this.setInFloorID(-1);
|
|
MoveToPointMsg moveToMsg = new MoveToPointMsg();
|
|
moveToMsg.setStartCoord(this.getLoc());
|
|
moveToMsg.setEndCoord(endLoc);
|
|
moveToMsg.setInBuilding(-1);
|
|
moveToMsg.setInBuildingFloor(-1);
|
|
moveToMsg.setSourceType(GameObjectType.PlayerCharacter.ordinal());
|
|
moveToMsg.setSourceID(this.getObjectUUID());
|
|
|
|
Dispatch dispatch = Dispatch.borrow(this, moveToMsg);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, DispatchChannel.PRIMARY);
|
|
|
|
try {
|
|
MovementManager.movement(moveToMsg, this);
|
|
} catch (Exception e) {
|
|
// TODO Auto-generated catch block
|
|
Logger.error("Player.MoveTo", this.getName() + " tripped error " + e.getMessage());
|
|
}
|
|
|
|
}
|
|
|
|
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 MovementState getLastMovementState() {
|
|
return lastMovementState;
|
|
}
|
|
|
|
public void setLastMovementState(MovementState lastMovementState) {
|
|
this.lastMovementState = lastMovementState;
|
|
}
|
|
|
|
@Override
|
|
public final void setIsCasting(final boolean isCasting) {
|
|
if (this.isCasting != isCasting)
|
|
this.update();
|
|
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 void setEnteredWorld(boolean enteredWorld) {
|
|
this.enteredWorld = enteredWorld;
|
|
}
|
|
|
|
public boolean isLastSwimming() {
|
|
return lastSwimming;
|
|
}
|
|
|
|
|
|
public ReadWriteLock getTeleportLock() {
|
|
return teleportLock;
|
|
}
|
|
|
|
public float getBargain() {
|
|
float bargain = 0;
|
|
|
|
CharacterSkill bargainSkill = this.getSkills().get("Bargaining");
|
|
|
|
if (bargainSkill != null)
|
|
bargain = bargainSkill.getModifiedAmountBeforeMods();
|
|
|
|
if (bargain > 100)
|
|
bargain = 100;
|
|
|
|
bargain *= .01f;
|
|
|
|
return bargain;
|
|
}
|
|
|
|
public boolean isDirtyLoad() {
|
|
boolean dirtyValue;
|
|
dirtyLock.readLock().lock();
|
|
dirtyValue = dirtyLoad;
|
|
dirtyLock.readLock().unlock();
|
|
return dirtyValue;
|
|
}
|
|
|
|
public void setDirtyLoad(boolean dirtyLoad) {
|
|
dirtyLock.writeLock().lock();
|
|
this.dirtyLoad = dirtyLoad;
|
|
dirtyLock.writeLock().unlock();
|
|
}
|
|
}
|