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

1490 lines
46 KiB

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2022
// www.magicbane.com
package engine.objects;
import ch.claude_martin.enumbitset.EnumBitSet;
import engine.Enum;
import engine.Enum.*;
import engine.InterestManagement.WorldGrid;
import engine.exception.SerializationException;
import engine.gameManager.*;
import engine.job.JobScheduler;
import engine.jobs.DeferredPowerJob;
import engine.jobs.UpgradeNPCJob;
import engine.math.Bounds;
import engine.math.Vector3fImmutable;
import engine.net.ByteBufferWriter;
import engine.net.Dispatch;
import engine.net.DispatchMessage;
import engine.net.client.msg.PetMsg;
import engine.net.client.msg.PlaceAssetMsg;
import engine.server.MBServerStatics;
import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.EnumSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup;
import static java.lang.Math.toIntExact;
public class Mob extends AbstractIntelligenceAgent implements Delayed {
private static int staticID = 0;
//mob specific
1 year ago
public final ConcurrentHashMap<Integer, Float> playerAgroMap = new ConcurrentHashMap<>(); //key = Player value = hate value
public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public long nextCastTime = 0;
public long nextCallForHelp = 0;
public ReentrantReadWriteLock minionLock = new ReentrantReadWriteLock();
public boolean despawned = false;
public Vector3fImmutable destination = Vector3fImmutable.ZERO;
public MobBase mobBase;
public int spawnDelay;
public Zone parentZone;
public boolean hasLoot = false;
public long deathTime = 0;
public long respawnTime = 0;
public int equipmentSetID = 0;
public int runeSet = 0;
public int bootySet = 0;
public int loadID;
public float spawnRadius;
//used by static mobs
public int parentZoneUUID;
//TODO implement feared object system
public AbstractWorldObject fearedObject = null;
protected int dbID; //the database ID
private int currentID;
private long lastAttackTime = 0;
private int lastMobPowerToken = 0;
private DeferredPowerJob weaponPower;
private DateTime upgradeDateTime = null;
private boolean lootSync = false;
// New Mobile constructor. Fill in the blanks and then call
// PERSIST.
public Mob() {
super();
this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
this.bindLoc = Vector3fImmutable.ZERO;
this.gridObjectType = GridObjectType.DYNAMIC;
this.agentType = AIAgentType.MOBILE;
}
/**
* ResultSet Constructor
*/
public Mob(ResultSet rs) throws SQLException {
super(rs);
float statLat;
float statAlt;
float statLon;
try {
this.dbID = rs.getInt(1);
this.loadID = rs.getInt("mob_mobbaseID");
this.gridObjectType = GridObjectType.DYNAMIC;
this.agentType = AIAgentType.MOBILE;
this.spawnRadius = rs.getFloat("mob_spawnRadius");
this.spawnDelay = rs.getInt("mob_spawnTime");
statLat = rs.getFloat("mob_spawnX");
statAlt = rs.getFloat("mob_spawnY");
statLon = rs.getFloat("mob_spawnZ");
this.bindLoc = new Vector3fImmutable(statLat, statAlt, statLon);
this.parentZoneUUID = rs.getInt("parent");
this.level = (short) rs.getInt("mob_level");
this.buildingUUID = rs.getInt("mob_buildingID");
this.contractUUID = rs.getInt("mob_contractID");
this.guildUUID = rs.getInt("mob_guildUID");
this.equipmentSetID = rs.getInt("equipmentSet");
java.util.Date sqlDateTime;
sqlDateTime = rs.getTimestamp("upgradeDate");
2 years ago
if (sqlDateTime != null)
upgradeDateTime = new DateTime(sqlDateTime);
else
upgradeDateTime = null;
// Submit upgrade job if NPC is currently set to rank.
if (this.upgradeDateTime != null)
Mob.submitUpgradeJob(this);
if (this.mobBase != null && this.spawnDelay == 0)
this.spawnDelay = this.mobBase.getSpawnTime();
this.runeSet = rs.getInt("runeSet");
this.bootySet = rs.getInt("bootySet");
this.notEnemy = EnumBitSet.asEnumBitSet(rs.getLong("notEnemy"), Enum.MonsterType.class);
this.enemy = EnumBitSet.asEnumBitSet(rs.getLong("enemy"), Enum.MonsterType.class);
this.firstName = rs.getString("mob_name");
if (rs.getString("fsm").length() > 1)
this.behaviourType = MobBehaviourType.valueOf(rs.getString("fsm"));
this.currentID = this.dbID;
} catch (Exception e) {
Logger.error(e + " " + this.dbID);
}
}
public static void serializeMobForClientMsgOtherPlayer(Mob mob, ByteBufferWriter writer) throws SerializationException {
Mob.serializeForClientMsgOtherPlayer(mob, writer);
}
public static void serializeForClientMsgOtherPlayer(Mob mob, ByteBufferWriter writer) throws SerializationException {
writer.putInt(0);
writer.putInt(0);
int tid = (mob.mobBase != null) ? mob.mobBase.getLoadID() : 0;
if (mob.isPet()) {
writer.putInt(2);
writer.putInt(3);
writer.putInt(0);
writer.putInt(2522);
writer.putInt(GameObjectType.NPCClassRune.ordinal());
writer.putInt(mob.currentID);
} else if (tid == 100570) { //kur'adar
writer.putInt(3);
Mob.serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), 2518); //warrior class
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune
} else if (tid == 100962 || tid == 100965) { //Spydraxxx the Mighty, Denigo Tantric
writer.putInt(2);
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneTwo.ordinal(), 252621); //guard rune
} else if (mob.contract != null || mob.isPlayerGuard()) {
writer.putInt(3);
serializeRune(mob, writer, 3, GameObjectType.NPCClassRuneTwo.ordinal(), MobBase.GetClassType(mob.getMobBaseID())); //warrior class
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), 252621); //guard rune
} else
writer.putInt(1);
//Generate Race Rune
writer.putInt(1);
writer.putInt(0);
if (mob.mobBase != null)
writer.putInt(mob.mobBase.getLoadID());
else
writer.putInt(mob.loadID);
writer.putInt(mob.getObjectType().ordinal());
writer.putInt(mob.currentID);
//Send Stats
writer.putInt(5);
writer.putInt(0x8AC3C0E6); //Str
writer.putInt(0);
writer.putInt(0xACB82E33); //Dex
writer.putInt(0);
writer.putInt(0xB15DC77E); //Con
writer.putInt(0);
writer.putInt(0xE07B3336); //Int
writer.putInt(0);
writer.putInt(0xFF665EC3); //Spi
writer.putInt(0);
writer.putString(mob.firstName);
writer.putString(mob.lastName);
writer.putInt(0);
writer.putInt(0);
writer.putInt(0);
writer.putInt(0);
writer.put((byte) 0);
writer.putInt(mob.getObjectType().ordinal());
writer.putInt(mob.currentID);
if (mob.mobBase != null) {
writer.putFloat(mob.mobBase.getScale());
writer.putFloat(mob.mobBase.getScale());
writer.putFloat(mob.mobBase.getScale());
} else {
writer.putFloat(1.0f);
writer.putFloat(1.0f);
writer.putFloat(1.0f);
}
writer.putVector3f(mob.getLoc());
//Rotation
2 years ago
float radians = (float) Math.acos(mob.getRot().y) * 2;
if (mob.building != null)
if (mob.building.getBounds() != null && mob.building.getBounds().getQuaternion() != null)
radians += (mob.building.getBounds().getQuaternion()).angleY;
writer.putFloat(radians);
//Inventory Stuff
writer.putInt(0);
// get a copy of the equipped items.
if (!mob.charItemManager.equipped.isEmpty()) {
writer.putInt(mob.charItemManager.equipped.size());
for (Item me : mob.charItemManager.equipped.values())
Item._serializeForClientMsg(me, writer);
} else
writer.putInt(0);
writer.putInt(mob.getRank());
writer.putInt(mob.getLevel());
writer.putInt(mob.getIsSittingAsInt()); //Standing
writer.putInt(mob.getIsWalkingAsInt()); //Walking
writer.putInt(mob.getIsCombatAsInt()); //Combat
writer.putInt(2); //Unknown
writer.putInt(1); //Unknown - Headlights?
writer.putInt(0);
if (mob.building != null && mob.region != null) {
writer.putInt(mob.building.getObjectType().ordinal());
writer.putInt(mob.building.getObjectUUID());
} else {
writer.putInt(0); //<-Building Object Type
writer.putInt(0); //<-Building Object ID
}
writer.put((byte) 0);
writer.put((byte) 0);
writer.put((byte) 0);
writer.putInt(0); // NPC menu options
if (mob.contract != null && mob.guardCaptain == null) {
writer.put((byte) 1);
writer.putLong(0);
writer.putLong(0);
if (mob.contract != null)
writer.putInt(mob.contract.getIconID());
else
writer.putInt(0); //npc icon ID
} else
writer.put((byte) 0);
if (mob.guardCaptain != null) {
writer.put((byte) 1);
writer.putInt(GameObjectType.PlayerCharacter.ordinal());
writer.putInt(131117009);
writer.putInt(mob.guardCaptain.getObjectType().ordinal());
writer.putInt(mob.guardCaptain.getObjectUUID());
writer.putInt(8);
} else
writer.put((byte) 0);
if (mob.isPet()) {
writer.put((byte) 1);
if (mob.guardCaptain != null) {
writer.putInt(mob.guardCaptain.getObjectType().ordinal());
writer.putInt(mob.guardCaptain.getObjectUUID());
} else {
writer.putInt(0); //ownerType
writer.putInt(0); //ownerID
}
} else
writer.put((byte) 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);
if (!mob.isAlive() && !mob.isPet() && !mob.isNecroPet() && !mob.behaviourType.equals(MobBehaviourType.SiegeEngine) && !mob.isPlayerGuard()) {
writer.putInt(0);
writer.putInt(0);
}
writer.put((byte) 0);
Guild._serializeForClientMsg(mob.getGuild(), writer);
if (mob.mobBase != null && mob.mobBase.getObjectUUID() == 100570) {
writer.putInt(2);
writer.putInt(0x00008A2E);
writer.putInt(0x1AB84003);
} else if (mob.behaviourType.equals(MobBehaviourType.SiegeEngine)) {
writer.putInt(1);
writer.putInt(74620179);
} else
writer.putInt(0);
writer.putInt(0); //0xB8400300
writer.putInt(0);
//TODO Guard
writer.put((byte) 0);
writer.putFloat(mob.healthMax);
writer.putFloat(mob.health.get());
//TODO Peace Zone
writer.put((byte) 1); //0=show tags, 1=don't
//DON't LOAD EFFECTS FOR DEAD MOBS.
if (!mob.isAlive())
writer.putInt(0);
else {
int indexPosition = writer.position();
writer.putInt(0); //placeholder for item cnt
int total = 0;
for (Effect eff : mob.getEffects().values()) {
if (eff.isStatic())
continue;
if (!eff.serializeForLoad(writer))
continue;
++total;
}
writer.putIntAt(total, indexPosition);
}
// Effects
writer.put((byte) 0);
}
private static void serializeRune(Mob mob, ByteBufferWriter writer, int type, int objectType, int runeID) {
writer.putInt(type);
writer.putInt(0);
writer.putInt(runeID);
writer.putInt(objectType);
writer.putInt(mob.currentID);
}
public static Mob createMob(int loadID, Vector3fImmutable spawn, Guild guild, Zone parent, Building building, Contract contract, String pirateName, int level, AIAgentType mobType) {
Mob mobile = new Mob();
mobile.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET;
mobile.agentType = mobType;
mobile.behaviourType = MobBehaviourType.None;
mobile.loadID = loadID;
mobile.level = (short) level;
if (guild == null || guild.isEmptyGuild())
mobile.guildUUID = 0;
else
mobile.guildUUID = guild.getObjectUUID();
mobile.parentZoneUUID = parent.getObjectUUID();
if (building == null)
mobile.buildingUUID = 0;
else
mobile.buildingUUID = building.getObjectUUID();
if (mobile.buildingUUID != 0)
mobile.bindLoc = Vector3fImmutable.ZERO;
else
mobile.bindLoc = ZoneManager.worldToLocal(spawn, parent);
;
mobile.firstName = pirateName;
if (contract == null)
mobile.contractUUID = 0;
else
mobile.contractUUID = contract.getContractID();
return mobile;
}
public static synchronized Mob createGuardMinion(Mob guardCaptain, short level, String minionName) {
Mob minionMobile;
minionMobile = new Mob();
minionMobile.currentID = (--Mob.staticID);
minionMobile.level = level;
minionMobile.loadID = guardCaptain.loadID;
minionMobile.firstName = minionName;
minionMobile.equipmentSetID = guardCaptain.equipmentSetID;
minionMobile.buildingUUID = guardCaptain.building.getObjectUUID();
minionMobile.guildUUID = guardCaptain.guildUUID;
minionMobile.runeSet = guardCaptain.runeSet;
minionMobile.enemy = guardCaptain.enemy;
minionMobile.notEnemy = guardCaptain.notEnemy;
minionMobile.deathTime = System.currentTimeMillis();
minionMobile.guardCaptain = guardCaptain;
minionMobile.spawnDelay = (int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60;
minionMobile.behaviourType = Enum.MobBehaviourType.GuardMinion;
minionMobile.agentType = AIAgentType.GUARDMINION;
minionMobile.guardedCity = guardCaptain.guardedCity;
minionMobile.patrolPoints = guardCaptain.building.patrolPoints;
minionMobile.parentZoneUUID = guardCaptain.parentZoneUUID;
minionMobile.bindLoc = Vector3fImmutable.ZERO;
//grab name from minionbase.
Enum.MinionType minionType = Enum.MinionType.ContractToMinionMap.get(guardCaptain.contract.getContractID());
if (minionType != null) {
String rank;
if (guardCaptain.getRank() < 3)
rank = MBServerStatics.JUNIOR;
else if (guardCaptain.getRank() < 6)
rank = "";
else if (guardCaptain.getRank() == 6)
rank = MBServerStatics.VETERAN;
else
rank = MBServerStatics.ELITE;
minionMobile.lastName = rank + " " + minionType.getRace() + " " + minionType.getName();
}
// Configure and spawn minion
minionMobile.runAfterLoad();
DbManager.addToCache(minionMobile);
minionMobile.setLoc(minionMobile.bindLoc);
minionMobile.despawn();
guardCaptain.minions.add(minionMobile.getObjectUUID());
return minionMobile;
}
public static synchronized Mob createSiegeMinion(NPC artyCaptain, int loadID) {
Mob siegeMinion;
siegeMinion = new Mob();
siegeMinion.currentID = (--Mob.staticID);
siegeMinion.level = 1;
siegeMinion.loadID = loadID;
siegeMinion.guildUUID = artyCaptain.guildUUID;
siegeMinion.equipmentSetID = 0;
siegeMinion.buildingUUID = artyCaptain.buildingUUID;
siegeMinion.guardCaptain = artyCaptain;
siegeMinion.parentZoneUUID = artyCaptain.parentZoneUUID;
siegeMinion.behaviourType = MobBehaviourType.SiegeEngine;
siegeMinion.agentType = AIAgentType.SIEGEENGINE;
siegeMinion.bindLoc = Vector3fImmutable.ZERO;
siegeMinion.spawnDelay = (60 * 15);
siegeMinion.runAfterLoad();
DbManager.addToCache(siegeMinion);
siegeMinion.setLoc(siegeMinion.bindLoc);
siegeMinion.despawn();
artyCaptain.minions.add(siegeMinion.getObjectUUID());
return siegeMinion;
}
public static synchronized Mob createPetMinion(int loadID, Zone parent, PlayerCharacter petOwner, short level) {
if (petOwner == null)
return null;
Mob petMinion = new Mob();
petMinion.currentID = (--Mob.staticID);
petMinion.level = (short) (level + 20);
petMinion.loadID = loadID;
petMinion.bindLoc = petOwner.getLoc();
petMinion.loc = petOwner.getLoc();
petMinion.guardCaptain = petOwner;
petMinion.parentZoneUUID = parent.getObjectUUID();
petMinion.walkMode = false;
petMinion.healthMax = MobBase.getMobBase(loadID).getHealthMax() * (petMinion.level * 0.5f);
petMinion.health.set(petMinion.healthMax);
petMinion.behaviourType = MobBehaviourType.Pet1;
petMinion.agentType = AIAgentType.PET;
petMinion.firstName = "";
petMinion.lastName = "";
petMinion.despawned = false;
petMinion.runAfterLoad();
DbManager.addToCache(petMinion);
petMinion.setLoc(petMinion.bindLoc);
return petMinion;
}
public static Mob getMob(int id) {
if (id == 0)
return null;
Mob mob = (Mob) DbManager.getFromCache(GameObjectType.Mob, id);
if (mob != null)
return mob;
return DbManager.MobQueries.GET_MOB(id);
}
public static Mob getFromCache(int id) {
return (Mob) DbManager.getFromCache(GameObjectType.Mob, id);
}
private static float getModifiedAmount(CharacterSkill skill) {
if (skill == null)
return 0f;
return skill.getModifiedAmount();
}
public static void submitUpgradeJob(Mob mob) {
if (mob.getUpgradeDateTime() == null) {
Logger.error("Failed to get Upgrade Date");
return;
}
// Submit upgrade job for future date or current instant
if (mob.getUpgradeDateTime().isAfter(DateTime.now()))
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), mob.getUpgradeDateTime().getMillis());
else
JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(mob), 0);
}
public static int getUpgradeTime(Mob mob) {
if (mob.getRank() < 7)
return (mob.getRank() * 8);
return 0;
}
public static int getUpgradeCost(Mob mob) {
int upgradeCost;
upgradeCost = Integer.MAX_VALUE;
if (mob.getRank() < 7)
return (mob.getRank() * 100650) + 21450;
return upgradeCost;
}
public static void setUpgradeDateTime(Mob mob, DateTime upgradeDateTime) {
if (!DbManager.MobQueries.updateUpgradeTime(mob, upgradeDateTime)) {
Logger.error("Failed to set upgradeTime for building " + mob.currentID);
return;
}
mob.upgradeDateTime = upgradeDateTime;
}
/*
* Getters
*/
@Override
public int getDBID() {
return this.dbID;
}
public int getLoadID() {
return loadID;
}
/*
* Serialization
*/
@Override
public int getObjectUUID() {
return currentID;
}
public float getSpawnRadius() {
return this.spawnRadius;
}
public String getSpawnTimeAsString() {
if (this.spawnDelay == 0)
return MBServerStatics.DEFAULT_SPAWN_TIME_MS / 1000 + " seconds (Default)";
else
return this.spawnDelay + " seconds";
}
@Override
public MobBase getMobBase() {
return this.mobBase;
}
public int getMobBaseID() {
return this.mobBase.getObjectUUID();
}
public Vector3fImmutable getTrueBindLoc() {
return this.bindLoc;
}
public Zone getParentZone() {
return this.parentZone;
}
@Override
public int getGuildUUID() {
if (this.guild == null)
return 0;
return this.guild.getObjectUUID();
}
@Override
public Vector3fImmutable getBindLoc() {
if (this.isPet() && !this.behaviourType.equals(MobBehaviourType.SiegeEngine))
return this.guardCaptain != null ? this.guardCaptain.getLoc() : this.getLoc();
else
return this.bindLoc;
}
public void calculateModifiedStats() {
float strVal = this.mobBase.getMobBaseStats().getBaseStr();
float dexVal = this.mobBase.getMobBaseStats().getBaseDex();
float conVal = 0; // I believe this will desync the Mobs Health if we call it.
float intVal = this.mobBase.getMobBaseStats().getBaseInt();
float spiVal = this.mobBase.getMobBaseStats().getBaseSpi();
// TODO modify for equipment
if (this.bonuses != null) {
// modify for effects
9 months ago
strVal += this.bonuses.getFloat(ModType.Attr, SourceType.STRENGTH);
dexVal += this.bonuses.getFloat(ModType.Attr, SourceType.DEXTERITY);
conVal += this.bonuses.getFloat(ModType.Attr, SourceType.CONSTITUTION);
intVal += this.bonuses.getFloat(ModType.Attr, SourceType.INTELLIGENCE);
spiVal += this.bonuses.getFloat(ModType.Attr, SourceType.SPIRIT);
// apply dex penalty for armor
// modify percent amounts. DO THIS LAST!
9 months ago
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));
}
// 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;
}
@Override
public float getSpeed() {
float bonus = 1;
if (this.bonuses != null)
// get rune and effect bonuses
9 months ago
bonus *= (1 + this.bonuses.getFloatPercentAll(ModType.Speed, SourceType.NONE));
if (this.isPlayerGuard())
switch (this.mobBase.getLoadID()) {
case 2111:
if (this.isWalk())
if (this.isCombat())
return Guards.HumanArcher.getWalkCombatSpeed() * bonus;
else
return Guards.HumanArcher.getWalkSpeed() * bonus;
else
return Guards.HumanArcher.getRunSpeed() * bonus;
case 14103:
if (this.isWalk())
if (this.isCombat())
return Guards.UndeadArcher.getWalkCombatSpeed() * bonus;
else
return Guards.UndeadArcher.getWalkSpeed() * bonus;
else
return Guards.UndeadArcher.getRunSpeed() * bonus;
}
//return combat speeds
//not combat return normal speeds
if (this.isCombat())
if (this.isWalk()) {
if (this.mobBase.getWalkCombat() <= 0)
return MBServerStatics.MOB_SPEED_WALKCOMBAT * bonus;
return this.mobBase.getWalkCombat() * bonus;
} else {
if (this.mobBase.getRunCombat() <= 0)
return MBServerStatics.MOB_SPEED_RUNCOMBAT * bonus;
return this.mobBase.getRunCombat() * bonus;
}
else if (this.isWalk()) {
if (this.mobBase.getWalk() <= 0)
return MBServerStatics.MOB_SPEED_WALK * bonus;
return this.mobBase.getWalk() * bonus;
} else {
if (this.mobBase.getRun() <= 0)
return MBServerStatics.MOB_SPEED_RUN * bonus;
return this.mobBase.getRun() * bonus;
}
}
@Override
public float getPassiveChance(String type, int AttackerLevel, boolean fromCombat) {
//TODO add this later for dodge
return 0f;
}
/*
* Database
*/
/**
* @ Kill this Character
*/
@Override
public void killCharacter(AbstractCharacter attacker) {
this.stopMovement(this.getMovementLoc());
if (attacker != null)
if (attacker.getObjectType() == GameObjectType.PlayerCharacter) {
Group g = GroupManager.getGroup((PlayerCharacter) attacker);
// Give XP, now handled inside the Experience Object
if (!this.isPet() && !this.isNecroPet() && !(this.agentType.equals(AIAgentType.PET)) && !this.isPlayerGuard())
Experience.doExperience((PlayerCharacter) attacker, this, g);
} else if (attacker.getObjectType().equals(GameObjectType.Mob)) {
Mob mobAttacker = (Mob) attacker;
if (mobAttacker.isPet()) {
PlayerCharacter owner = (PlayerCharacter) mobAttacker.guardCaptain;
if (owner != null)
if (!this.isPet() && !this.isNecroPet() && !(this.agentType.equals(AIAgentType.PET)) && !this.isPlayerGuard()) {
Group g = GroupManager.getGroup(owner);
// Give XP, now handled inside the Experience Object
Experience.doExperience(owner, this, g);
}
}
}
killCleanup();
}
public void updateLocation() {
if (!this.isMoving())
return;
9 months ago
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(this.getMovementLoc());
return;
}
Vector3fImmutable newLoc = this.getMovementLoc();
if (newLoc.equals(this.getEndLoc())) {
this.stopMovement(newLoc);
return;
//Next upda
}
setLoc(newLoc);
//Next update will be end Loc, lets stop him here.
}
@Override
public void killCharacter(String reason) {
killCleanup();
}
private void killCleanup() {
Dispatch dispatch;
try {
//resync corpses
if (this.behaviourType.equals(MobBehaviourType.SiegeEngine)) {
this.deathTime = System.currentTimeMillis();
try {
this.clearEffects();
} catch (Exception e) {
Logger.error(e.getMessage());
}
this.setCombatTarget(null);
this.hasLoot = false;
this.playerAgroMap.clear();
if (this.behaviourType.ordinal() == Enum.MobBehaviourType.GuardMinion.ordinal())
this.spawnDelay = (int) (-2.500 * this.guardCaptain.building.getRank() + 22.5) * 60;
if (this.isPet()) {
PlayerCharacter petOwner = (PlayerCharacter) this.guardCaptain;
if (petOwner != null) {
this.guardCaptain = null;
petOwner.setPet(null);
PetMsg petMsg = new PetMsg(5, null);
dispatch = Dispatch.borrow((PlayerCharacter) this.guardCaptain, petMsg);
DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
}
}
} else if (this.isPet() || this.isNecroPet()) {
this.setCombatTarget(null);
this.hasLoot = false;
1 year ago
ZoneManager.seaFloor.zoneMobSet.remove(this);
try {
this.clearEffects();
} catch (Exception e) {
Logger.error(e.getMessage());
}
this.playerAgroMap.clear();
WorldGrid.RemoveWorldObject(this);
DbManager.removeFromCache(this);
PlayerCharacter petOwner = (PlayerCharacter) this.guardCaptain;
if (petOwner != null) {
this.guardCaptain = null;
petOwner.setPet(null);
PetMsg petMsg = new PetMsg(5, null);
dispatch = Dispatch.borrow(petOwner, petMsg);
DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY);
}
} else {
//cleanup effects
playerAgroMap.clear();
if (!this.isPlayerGuard() && this.charItemManager.equipped != null)
LootManager.GenerateEquipmentDrop(this);
}
try {
this.clearEffects();
} catch (Exception e) {
Logger.error(e.getMessage());
}
this.combat = false;
this.walkMode = true;
this.setCombatTarget(null);
this.hasLoot = this.charItemManager.getInventoryCount() > 0;
} catch (Exception e) {
Logger.error(e);
}
this.updateLocation();
}
public void respawn() {
this.despawned = false;
this.setCombatTarget(null);
this.setHealth(this.healthMax);
this.stamina.set(this.staminaMax);
this.mana.set(this.manaMax);
this.combat = false;
this.walkMode = true;
this.setCombatTarget(null);
this.isAlive.set(true);
this.deathTime = 0;
this.lastBindLoc = this.bindLoc;
this.setLoc(this.lastBindLoc);
this.stopMovement(this.lastBindLoc);
NPCManager.applyMobbaseEffects(this);
this.recalculateStats();
this.setHealth(this.healthMax);
if (this.building == null && this.guardCaptain != null && ((Mob) this.guardCaptain).behaviourType.equals(MobBehaviourType.GuardCaptain))
this.building = this.guardCaptain.building;
this.loadInventory();
this.updateLocation();
}
public void despawn() {
this.despawned = true;
WorldGrid.RemoveWorldObject(this);
this.charItemManager.clearInventory();
}
@Override
public boolean canBeLooted() {
return !this.isAlive();
}
public int getTypeMasks() {
if (this.mobBase == null)
return 0;
return this.mobBase.getTypeMasks();
}
/**
* Clears and sets the inventory of the Mob. Must be called every time the
* mob is spawned or respawned.
*/
public void loadInventory() {
if (!MBServerStatics.ENABLE_MOB_LOOT)
return;
this.charItemManager.clearInventory();
this.charItemManager.clearEquip();
// Reload equipment set
this.charItemManager.equipped = MobBase.loadEquipmentSet(this.equipmentSetID);
// Only generate loot for mobiles
if (!this.agentType.equals(AIAgentType.MOBILE))
return;
LootManager.GenerateMobLoot(this);
}
@Override
public void updateDatabase() {
}
public void refresh() {
if (this.isAlive())
WorldGrid.updateObject(this);
}
public void recalculateStats() {
8 months ago
if (this.isPlayerGuard()) {
NPCManager.setMaxHealthForGuard(this);
NPCManager.setAttackRatingForGuard(this);
NPCManager.setDefenseForGuard(this);
NPCManager.setDamageAndSpeedForGuard(this);
NPCManager.applyGuardStanceModifiers(this);
8 months ago
} else {
try {
AbstractCharacter.calculateAtrDamageForWeapon(this,this.charItemManager.equipped.get(EquipSlotType.RHELD),true,this.charItemManager.equipped.get(EquipSlotType.LHELD));
AbstractCharacter.calculateAtrDefenseDamage(this);
} catch (Exception e) {
Logger.error(this.getMobBaseID() + " /" + e.getMessage());
}
try {
calculateMaxHealthManaStamina();
} catch (Exception e) {
Logger.error(e.getMessage());
}
}
try {
calculateModifiedStats();
} catch (Exception e) {
Logger.error(e.getMessage());
}
8 months ago
if (this.isSiege())
this.healthMax = 10000;
Resists.calculateResists(this);
}
public void calculateMaxHealthManaStamina() {
8 months ago
float h;
float m;
float s;
8 months ago
h = this.mobBase.getHealthMax();
if (this.isPet()) {
h = this.level * 0.5f * 120;
}
m = this.statSpiCurrent;
s = this.statConCurrent;
8 months ago
// Apply any bonuses from runes and effects
8 months ago
if (this.bonuses != null) {
h += this.bonuses.getFloat(ModType.HealthFull, SourceType.NONE);
m += this.bonuses.getFloat(ModType.ManaFull, SourceType.NONE);
s += this.bonuses.getFloat(ModType.StaminaFull, SourceType.NONE);
8 months ago
//apply effects percent modifiers. DO THIS LAST!
8 months ago
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));
}
8 months ago
// Set max health, mana and stamina
8 months ago
if (h > 0)
this.healthMax = h;
else
this.healthMax = 1;
8 months ago
if (m > -1)
this.manaMax = m;
else
this.manaMax = 0;
8 months ago
if (s > -1)
this.staminaMax = s;
else
this.staminaMax = 0;
8 months ago
// Update health, mana and stamina if needed
8 months ago
if (this.getHealth() > this.healthMax)
this.setHealth(this.healthMax);
8 months ago
if (this.mana.get() > this.manaMax)
this.mana.set(this.manaMax);
8 months ago
if (this.stamina.get() > this.staminaMax)
this.stamina.set(staminaMax);
}
@Override
public void runAfterLoad() {
this.setObjectTypeMask(MBServerStatics.MASK_MOB | this.getTypeMasks());
if (ConfigManager.serverType.equals(ServerType.LOGINSERVER))
return;
this.mobBase = MobBase.getMobBase(loadID);
this.building = BuildingManager.getBuilding(this.buildingUUID);
// Configure AI related values
switch (this.behaviourType) {
case GuardCaptain:
this.agentType = AIAgentType.GUARDCAPTAIN;
this.spawnDelay = 600;
if (this.building == null)
Logger.error("Captain : " + this.getObjectUUID() + " missing building " + this.buildingUUID);
this.guardedCity = ZoneManager.getCityAtLocation(this.building.getLoc());
break;
case GuardWallArcher:
this.agentType = AIAgentType.GUARDWALLARCHER;
this.spawnDelay = 450;
if (this.building == null)
Logger.error("Wall Archer : " + this.getObjectUUID() + " missing building " + this.buildingUUID);
this.guardedCity = ZoneManager.getCityAtLocation(this.building.getLoc());
break;
}
// Default to the mobbase for AI if nothing is in mob field to override.
if (this.behaviourType == null || this.behaviourType.equals(MobBehaviourType.None))
this.behaviourType = this.getMobBase().fsm;
if (this.behaviourType == null)
this.behaviourType = MobBehaviourType.None;
if (this.contractUUID == 0)
this.contract = null;
else
this.contract = DbManager.ContractQueries.GET_CONTRACT(this.contractUUID);
// Setup equipset for contract
if (this.contract != null)
this.equipmentSetID = this.contract.getEquipmentSet();
// Mobiles default to the building guild.
if (this.building != null)
this.guild = this.building.getGuild();
else
this.guild = Guild.getGuild(guildUUID);
if (this.guild == null)
this.guild = Guild.getErrantGuild();
if (this.firstName.isEmpty())
this.firstName = this.mobBase.getFirstName();
if (this.contract != null)
if (this.lastName.isEmpty())
this.lastName = this.getContract().getName();
this.healthMax = this.mobBase.getHealthMax();
this.manaMax = 0;
this.staminaMax = 0;
this.setHealth(this.healthMax);
this.mana.set(this.manaMax);
this.stamina.set(this.staminaMax);
// Don't override level for guard minions or pets
if (this.contract == null)
if (!this.agentType.equals(AIAgentType.GUARDMINION) && !this.agentType.equals(AIAgentType.PET))
this.level = (short) this.mobBase.getLevel();
//set bonuses
this.bonuses = new PlayerBonuses(this);
//TODO set these correctly later
this.rangeHandOne = this.mobBase.getAttackRange();
this.rangeHandTwo = -1;
this.minDamageHandOne = (int) this.mobBase.getDamageMin();
this.maxDamageHandOne = (int) this.mobBase.getDamageMax();
this.minDamageHandTwo = 0;
this.maxDamageHandTwo = 0;
this.atrHandOne = this.mobBase.getAttackRating();
this.defenseRating = (short) this.mobBase.getDefenseRating();
this.isActive = true;
// Configure parent zone adding this NPC to the
// zone collection
this.parentZone = ZoneManager.getZoneByUUID(this.parentZoneUUID);
this.parentZone.zoneMobSet.remove(this);
this.parentZone.zoneMobSet.add(this);
// Handle Mobiles within buildings
if (this.building == null) {
// Do not adjust a pet's bindloc.
if (!this.agentType.equals(AIAgentType.PET))
this.bindLoc = this.parentZone.getLoc().add(this.bindLoc);
} else {
// Mobiles inside buildings are offset from it not the zone
// with the exceptions being mobiles
// with a contract.
if (this.contract != null || this.behaviourType.equals(MobBehaviourType.SiegeEngine))
NPCManager.slotCharacterInBuilding(this);
else
this.bindLoc = building.getLoc().add(bindLoc);
}
// Setup location for this Mobile
this.setLoc(bindLoc);
this.endLoc = new Vector3fImmutable(bindLoc);
// Initialize inventory
this.charItemManager.load();
this.loadInventory();
if (this.equipmentSetID != 0)
this.charItemManager.equipped = MobBase.loadEquipmentSet(this.equipmentSetID);
// Combine mobbase and mob aggro arrays into one bitvector
//skip for pets
if (this.isPet() == false && this.isNecroPet() == false) {
if (this.getMobBase().notEnemy.size() > 0)
this.notEnemy.addAll(this.getMobBase().notEnemy);
if (this.getMobBase().enemy.size() > 0)
this.enemy.addAll(this.getMobBase().enemy);
}
// Load skills, powers and effects
NPCManager.applyMobbaseEffects(this);
NPCManager.applyEquipmentResists(this);
NPCManager.applyMobbaseSkill(this);
NPCManager.applyRuneSkills(this, this.getMobBaseID());
this.recalculateStats();
this.setHealth(this.healthMax);
// Set bounds for this mobile
Bounds mobBounds = Bounds.borrow();
mobBounds.setBounds(this.getLoc());
this.setBounds(mobBounds);
//assign 5 random patrol points for regular mobs
if (this.agentType.equals(AIAgentType.MOBILE))
NPCManager.AssignPatrolPoints(this);
// Load minions for guard captain.
if (this.agentType.equals(AIAgentType.GUARDCAPTAIN))
DbManager.MobQueries.LOAD_GUARD_MINIONS(this);
this.deathTime = 0;
}
@Override
protected ConcurrentHashMap<Integer, CharacterPower> initializePowers() {
return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
}
public boolean canSee(AbstractCharacter target) {
return this.mobBase.getSeeInvis() >= target.hidden;
}
public int getBuildingID() {
return buildingUUID;
}
public void setBuildingID(int buildingID) {
this.buildingUUID = buildingID;
}
public boolean isSiege() {
return this.behaviourType.equals(MobBehaviourType.SiegeEngine);
}
public void setGuardCaptain(AbstractCharacter guardCaptain) {
this.guardCaptain = guardCaptain;
}
public boolean isNecroPet() {
return this.mobBase.isNecroPet();
}
public void handleDirectAggro(AbstractCharacter ac) {
if (!ac.getObjectType().equals(GameObjectType.PlayerCharacter))
return;
if (this.getCombatTarget() == null)
this.setCombatTarget(ac);
}
public void setRank(int newRank) {
DbManager.MobQueries.SET_PROPERTY(this, "mob_level", newRank);
this.level = (short) newRank;
this.recalculateStats();
this.setHealth(this.healthMax);
}
public boolean isRanking() {
return this.upgradeDateTime != null;
}
public long getLastAttackTime() {
return lastAttackTime;
}
public void setDeathTime(long deathTime) {
this.deathTime = deathTime;
}
public boolean isHasLoot() {
return hasLoot;
}
public void setWeaponPower(DeferredPowerJob weaponPower) {
this.weaponPower = weaponPower;
}
public DateTime getUpgradeDateTime() {
lock.readLock().lock();
try {
return upgradeDateTime;
} finally {
lock.readLock().unlock();
}
}
public Contract getContract() {
return contract;
}
public void setContract(Contract contract) {
this.contract = contract;
}
public boolean isPlayerGuard() {
return EnumSet.of(AIAgentType.GUARDCAPTAIN, AIAgentType.GUARDMINION, AIAgentType.GUARDWALLARCHER).contains(this.agentType);
}
public int getLastMobPowerToken() {
return lastMobPowerToken;
}
public void setLastMobPowerToken(int lastMobPowerToken) {
this.lastMobPowerToken = lastMobPowerToken;
}
public boolean isLootSync() {
return lootSync;
}
public void setLootSync(boolean lootSync) {
this.lootSync = lootSync;
}
public String getNameOverride() {
return firstName + " " + lastName;
}
public void processUpgradeMob(PlayerCharacter player) {
lock.writeLock().lock();
try {
// Cannot upgrade an npc not within a building
if (building == null)
return;
// Cannot upgrade an npc at max rank
if (this.getRank() == 7)
return;
// Cannot upgrade an npc who is currently ranking
if (this.isRanking())
return;
int rankCost = Mob.getUpgradeCost(this);
// SEND NOT ENOUGH GOLD ERROR
if (rankCost > building.getStrongboxValue()) {
sendErrorPopup(player, 127);
return;
}
try {
if (!building.transferGold(-rankCost, false))
return;
DateTime dateToUpgrade = DateTime.now().plusHours(Mob.getUpgradeTime(this));
Mob.setUpgradeDateTime(this, dateToUpgrade);
// Schedule upgrade job
Mob.submitUpgradeJob(this);
} catch (Exception e) {
PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity");
}
} catch (Exception e) {
Logger.error(e);
} finally {
lock.writeLock().unlock();
}
}
8 months ago
public Boolean isGuard() {
8 months ago
switch (this.behaviourType) {
case GuardMinion:
case GuardCaptain:
case GuardWallArcher:
case HamletGuard:
case SimpleStandingGuard:
return true;
}
return false;
}
@Override
public long getDelay(@NotNull TimeUnit unit) {
long timeRemaining = this.respawnTime - System.currentTimeMillis();
return unit.convert(timeRemaining, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(@NotNull Delayed o) {
return toIntExact(this.respawnTime - ((Mob) o).respawnTime);
}
}