// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // 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 public final ConcurrentHashMap 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"); 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 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 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! 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 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; 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; 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() { if (this.isPlayerGuard()) { NPCManager.setMaxHealthForGuard(this); NPCManager.setAttackRatingForGuard(this); NPCManager.setDefenseForGuard(this); NPCManager.setDamageAndSpeedForGuard(this); NPCManager.applyGuardStanceModifiers(this); } 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); } try { calculateMaxHealthManaStamina(); } catch (Exception e) { Logger.error(e.getMessage()); } } try { calculateModifiedStats(); } catch (Exception e) { Logger.error(e.getMessage()); } if (this.isSiege()) this.healthMax = 10000; Resists.calculateResists(this); } public void calculateMaxHealthManaStamina() { float h; float m; float s; h = this.mobBase.getHealthMax(); if (this.isPet()) { h = this.level * 0.5f * 120; } m = this.statSpiCurrent; s = this.statConCurrent; // Apply any bonuses from runes and effects 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); //apply effects percent modifiers. DO THIS LAST! 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.getHealth() > 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 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 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(); } } public Boolean isGuard() { 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); } }