forked from MagicBane/Server
Wall archers are now sent 252622 Wall Guard in place of 252623 Standing Guard (serialize and getClientRuneIDs). The rune roots its holder (ROOT2, ROOTFSMID=Power), which matches wall archers never moving (canRoam false). No server value change: 252622 and 252623 add the same +100 to all five attributes with the same max, the only fields the server reads from the sent runes. Wall archers don't cast, so the 252623 power rank steps don't apply to them. The rune's effects and its MOB-SNARE grant are not applied by the server, as with the other guard runes. Client side: 252623 is the only rune that sets the client's is-guard flag (0x67AB1B), so the CloseToGuard hint no longer fires near wall archers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1558 lines
48 KiB
Java
1558 lines
48 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://www.magicbane.com
|
|
|
|
|
|
package engine.objects;
|
|
|
|
import engine.InterestManagement.WorldGrid;
|
|
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.mbEnums;
|
|
import engine.mbEnums.*;
|
|
import engine.net.ByteBufferWriter;
|
|
import engine.net.Dispatch;
|
|
import engine.net.client.msg.PetMsg;
|
|
import engine.net.client.msg.PlaceAssetMsg;
|
|
import engine.parsers.objects.RuneTemplate;
|
|
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.ArrayList;
|
|
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<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;
|
|
public long nextAttackTime = System.currentTimeMillis();
|
|
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 = mbEnums.fromLong(rs.getLong("notEnemy"), mbEnums.MonsterType.class);
|
|
this.enemy = mbEnums.fromLong(rs.getLong("enemy"), mbEnums.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) {
|
|
Mob.serializeForClientMsgOtherPlayer(mob, writer);
|
|
}
|
|
|
|
public static void serializeForClientMsgOtherPlayer(Mob mob, ByteBufferWriter writer) {
|
|
writer.putInt(0);
|
|
writer.putInt(0);
|
|
|
|
int tid = (mob.mobBase != null) ? mob.mobBase.getObjectUUID() : 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(), 2518); //warrior class
|
|
serializeRune(mob, writer, 5, GameObjectType.NPCClassRuneThree.ordinal(), mob.getGuardRuneID());
|
|
} else
|
|
writer.putInt(1);
|
|
|
|
//Generate Race Rune
|
|
writer.putInt(1);
|
|
writer.putInt(0);
|
|
|
|
if (mob.mobBase != null)
|
|
writer.putInt(mob.mobBase.getObjectUUID());
|
|
else
|
|
writer.putInt(mob.loadID);
|
|
|
|
writer.putInt(mob.getObjectType().ordinal());
|
|
writer.putInt(mob.currentID);
|
|
|
|
// Stats are the base (spent points, 0 for mobs); the client adds every sent rune's attr adj (0x470610)
|
|
|
|
writer.putInt(5);
|
|
writer.putInt(MBServerStatics.STAT_STR_ID);
|
|
writer.putInt(0);
|
|
writer.putInt(MBServerStatics.STAT_DEX_ID);
|
|
writer.putInt(0);
|
|
writer.putInt(MBServerStatics.STAT_CON_ID);
|
|
writer.putInt(0);
|
|
writer.putInt(MBServerStatics.STAT_INT_ID);
|
|
writer.putInt(0);
|
|
writer.putInt(MBServerStatics.STAT_SPI_ID);
|
|
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().x);
|
|
writer.putFloat(mob.mobBase.getScale().y);
|
|
writer.putFloat(mob.mobBase.getScale().z);
|
|
} 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;
|
|
}
|
|
|
|
// MB_MINION_SPAWN_TIME (seconds) shortens guard/siege minion spawns for testing; 0 or blank keeps the normal timers.
|
|
private static int minionSpawnDelay(int defaultDelay) {
|
|
|
|
String overrideValue;
|
|
int overrideDelay;
|
|
|
|
overrideValue = ConfigManager.MB_MINION_SPAWN_TIME.getValue();
|
|
|
|
if (overrideValue == null || overrideValue.trim().isEmpty())
|
|
return defaultDelay;
|
|
|
|
try {
|
|
overrideDelay = Integer.parseInt(overrideValue.trim());
|
|
} catch (NumberFormatException e) {
|
|
Logger.warn("MB_MINION_SPAWN_TIME is not a number: " + overrideValue);
|
|
return defaultDelay;
|
|
}
|
|
|
|
if (overrideDelay <= 0)
|
|
return defaultDelay;
|
|
|
|
return overrideDelay;
|
|
}
|
|
|
|
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 = minionSpawnDelay((int) (-2.500 * guardCaptain.building.getRank() + 22.5) * 60);
|
|
minionMobile.behaviourType = mbEnums.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.
|
|
|
|
mbEnums.MinionType minionType = mbEnums.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 = minionSpawnDelay(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();
|
|
|
|
// Necro pets count as pets too: DRU-066 targets Pet and requires NECROPETSTATS
|
|
petMinion.setObjectTypeMask(petMinion.getObjectTypeMask() | MBServerStatics.MASK_PET);
|
|
|
|
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;
|
|
}
|
|
|
|
// The runes serializeForClientMsgOtherPlayer sends; the client derives the mob's attributes from all of them
|
|
public ArrayList<Integer> getClientRuneIDs() {
|
|
|
|
ArrayList<Integer> runeIDs = new ArrayList<>();
|
|
|
|
if (this.isPet())
|
|
runeIDs.add(2522);
|
|
else if (this.contract != null || this.isPlayerGuard()) {
|
|
runeIDs.add(2518);
|
|
runeIDs.add(this.getGuardRuneID());
|
|
}
|
|
|
|
runeIDs.add((this.mobBase != null) ? this.mobBase.getObjectUUID() : this.loadID);
|
|
return runeIDs;
|
|
}
|
|
|
|
// Wall archers get 252622 Wall Guard; the rest get 252623 Standing Guard, the only rune that fires the client's CloseToGuard hint (0x67AB1B)
|
|
public int getGuardRuneID() {
|
|
|
|
if (this.agentType == AIAgentType.GUARDWALLARCHER)
|
|
return 252622;
|
|
|
|
return 252623;
|
|
}
|
|
|
|
// Client ArcCharacter_RecalcMaxAttributes 0x470440 + RecalcAttributes 0x470610 with no spent points
|
|
public int getRuneAttribute(AttributeType attributeType) {
|
|
|
|
RuneTemplate rune;
|
|
int value = 0;
|
|
int max = 0;
|
|
|
|
for (int runeID : this.getClientRuneIDs()) {
|
|
rune = RuneTemplate._runes.get(runeID);
|
|
|
|
if (rune == null)
|
|
continue;
|
|
|
|
value += rune.rune_attr_adj.getOrDefault(attributeType, 0);
|
|
max += rune.rune_max_attr_adj.getOrDefault(attributeType, 0);
|
|
}
|
|
|
|
max = Math.max(max, 1);
|
|
return Math.min(Math.max(value, 1), max);
|
|
}
|
|
|
|
public void calculateModifiedStats() {
|
|
|
|
float strVal = this.getRuneAttribute(AttributeType.Strength);
|
|
float dexVal = this.getRuneAttribute(AttributeType.Dexterity);
|
|
float conVal = this.getRuneAttribute(AttributeType.Constitution);
|
|
float intVal = this.getRuneAttribute(AttributeType.Intelligence);
|
|
float spiVal = this.getRuneAttribute(AttributeType.Spirit);
|
|
|
|
// 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.getObjectUUID()) {
|
|
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() == mbEnums.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);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.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);
|
|
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.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 {
|
|
AbstractCharacter.calculateAtrDefenseDamage(this);
|
|
calculateMaxHealthManaStamina();
|
|
}
|
|
|
|
calculateModifiedStats();
|
|
|
|
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.equipmentSet;
|
|
|
|
// 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.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;
|
|
|
|
// siege engines only switch targets on damage, see AbstractCharacter.modifyHealth
|
|
|
|
if (this.isSiege())
|
|
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 getNextAttackTime() {
|
|
return nextAttackTime;
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|