// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.objects; import engine.Enum; import engine.Enum.*; import engine.InterestManagement.WorldGrid; import engine.ai.MobileFSM.STATE; import engine.exception.SerializationException; import engine.gameManager.*; import engine.job.JobContainer; import engine.job.JobScheduler; import engine.jobs.UpgradeNPCJob; import engine.math.Bounds; import engine.math.Vector3f; import engine.math.Vector3fImmutable; import engine.net.ByteBufferWriter; import engine.net.Dispatch; import engine.net.DispatchMessage; import engine.net.client.ClientConnection; import engine.net.client.msg.*; import engine.server.MBServerStatics; 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.HashMap; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.ReentrantReadWriteLock; import static engine.net.client.msg.ErrorPopupMsg.sendErrorPopup; import static engine.objects.MobBase.loadEquipmentSet; public class NPC extends AbstractCharacter { //This is called every 10 minutes to remove items from static npc inventory to make room to buy more. private static int NUM_ITEMS_TO_JUNK = 30; // Used for thread safety public final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); protected int loadID; protected boolean isMob; protected MobBase mobBase; protected String name; protected Building building; protected Contract contract; protected int dbID; protected int currentID; private DateTime upgradeDateTime = null; //used by static npcs protected Zone parentZone; protected float statLat; protected float statLon; protected float statAlt; protected float sellPercent; //also train percent protected float buyPercent; protected int vendorID; protected ArrayList modTypeTable; protected ArrayList modSuffixTable; protected ArrayList itemModTable; protected int symbol; public static int SVR_CLOSE_WINDOW = 4; public static ArrayListOprhans = new ArrayList<>(); // Variables NOT to be stored in db protected boolean isStatic = false; private ArrayList rolling = new ArrayList<>(); private ArrayList siegeMinions = new ArrayList<>(); private ConcurrentHashMap siegeMinionMap = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private HashSet canRoll = null; public static HashMap> _pirateNames = new HashMap<>(); public ReentrantReadWriteLock minionLock = new ReentrantReadWriteLock(); private int parentZoneID; public ArrayList forgedItems = new ArrayList<>(); private int buildingLevel; private int buildingFloor; public HashMap equip = null; private String nameOverride = ""; private int equipmentSetID = 0; public int runeSetID = 0; private int slot; private Regions region = null; public Vector3fImmutable inBuildingLoc = Vector3fImmutable.ZERO; private int repairCost = 5; public int classID = 0; public int professionID = 0; public int extraRune = 0; public int extraRune2 = 0; /** * No Id Constructor */ public NPC( String name, short statStrCurrent, short statDexCurrent, short statConCurrent, short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild, byte runningTrains, int npcType, boolean isMob, Building building, int contractID, Zone parent) { super(name, "", statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp, bindLoc, currentLoc, faceDir, guild, runningTrains); this.loadID = npcType; this.isMob = isMob; this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID); if (this.contract != null) this.mobBase = MobBase.getMobBase(this.contract.getMobbaseID()); else this.mobBase = MobBase.getMobBase(loadID); this.name = name; this.buyPercent = 0.33f; this.sellPercent = 1f; this.building = building; this.parentZone = parent; initializeMob(); clearStatic(); this.dbID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET; this.currentID = MBServerStatics.NO_DB_ROW_ASSIGNED_YET; } /** * Normal Constructor */ public NPC( String name, short statStrCurrent, short statDexCurrent, short statConCurrent, short statIntCurrent, short statSpiCurrent, short level, int exp, boolean sit, boolean walk, boolean combat, Vector3fImmutable bindLoc, Vector3fImmutable currentLoc, Vector3fImmutable faceDir, short healthCurrent, short manaCurrent, short stamCurrent, Guild guild, byte runningTrains, int npcType, boolean isMob, Building building, int contractID, Zone parent, int newUUID) { super( name, "", statStrCurrent, statDexCurrent, statConCurrent, statIntCurrent, statSpiCurrent, level, exp, bindLoc, currentLoc, faceDir, guild, runningTrains, newUUID); this.loadID = npcType; this.isMob = isMob; if (this.contract != null) this.mobBase = MobBase.getMobBase(this.contract.getMobbaseID()); else this.mobBase = MobBase.getMobBase(loadID); this.building = building; this.name = name; this.buyPercent = 0.33f; this.sellPercent = 1f; this.parentZone = parent; this.dbID = newUUID; this.currentID = newUUID; initializeMob(); clearStatic(); } /** * ResultSet Constructor */ public NPC(ResultSet rs) throws SQLException { super(rs); java.util.Date sqlDateTime; try{ this.dbID = rs.getInt(1); this.currentID = this.dbID; this.setObjectTypeMask(MBServerStatics.MASK_NPC); int contractID = rs.getInt("npc_contractID"); this.parentZoneID = rs.getInt("parent"); this.gridObjectType = GridObjectType.STATIC; this.contract = DbManager.ContractQueries.GET_CONTRACT(contractID); this.equipmentSetID = rs.getInt("equipmentSet"); this.runeSetID = rs.getInt("runeSet"); if (this.equipmentSetID == 0 && this.contract != null) this.equipmentSetID = this.contract.equipmentSet; if (this.contract != null) this.loadID = this.contract.getMobbaseID(); else this.loadID = 2011; //default to human int mobBaseOverride = rs.getInt("npc_raceID"); this.loadID = mobBaseOverride; this.mobBase = MobBase.getMobBase(this.loadID); this.level = rs.getByte("npc_level"); this.isMob = false; int buildingID = rs.getInt("npc_buildingID"); try{ this.building = BuildingManager.getBuilding(buildingID); }catch(Exception e){ this.building = null; Logger.error( e.getMessage()); } this.name = rs.getString("npc_name"); // Most objects from the cache have a default buy // percentage of 100% which was a dupe source due // to the way MB calculated item values. // this.buyPercent = rs.getFloat("npc_buyPercent"); this.buyPercent = .33f; this.sellPercent = 1; this.setRot(new Vector3f(0, rs.getFloat("npc_rotation"), 0)); this.statLat = rs.getFloat("npc_spawnX"); this.statAlt = rs.getFloat("npc_spawnY"); this.statLon = rs.getFloat("npc_spawnZ"); this.slot = rs.getInt("npc_slot"); if (this.contract != null) { this.symbol = this.contract.getIconID(); this.modTypeTable = this.contract.getNPCModTypeTable(); this.modSuffixTable = this.contract.getNpcModSuffixTable(); this.itemModTable = this.contract.getItemModTable(); int VID = this.contract.getVendorID(); if (VID != 0) this.vendorID = VID; else this.vendorID = 1; //no vendor items } int guildID = rs.getInt("npc_guildID"); if (this.building != null) this.guild = this.building.getGuild(); else this.guild = Guild.getGuild(guildID); if (guildID != 0 && (this.guild == null || this.guild.isEmptyGuild())) NPC.Oprhans.add(currentID); else if(this.building == null && buildingID > 0) NPC.Oprhans.add(currentID); if (this.guild == null) this.guild = Guild.getErrantGuild(); // Set upgrade date JodaTime DateTime object // if one exists in the database. 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) submitUpgradeJob(); this.buildingFloor = (rs.getInt("npc_buildingFloor")); this.buildingLevel = (rs.getInt("npc_buildingLevel")); this.setParentZone(ZoneManager.getZoneByUUID(this.parentZoneID)); this.nameOverride = rs.getString("npc_name"); }catch(Exception e){ Logger.error(e); e.printStackTrace(); } try{ initializeMob(); }catch(Exception e){ Logger.error( e.toString()); } } //This method restarts an upgrade timer when a building is loaded from the database. // Submit upgrade job for this building based upon it's current upgradeDateTime public final void submitUpgradeJob() { JobContainer jc; if (this.getUpgradeDateTime() == null) { Logger.error("Attempt to submit upgrade job for non-ranking NPC"); return; } // Submit upgrade job for future date or current instant if (this.getUpgradeDateTime().isAfter(DateTime.now())) jc = JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), this.getUpgradeDateTime().getMillis()); else JobScheduler.getInstance().scheduleJob(new UpgradeNPCJob(this), 0); } public void setRank(int newRank) { DbManager.NPCQueries.SET_PROPERTY(this, "npc_level", newRank); this.level = (short) newRank; } public int getDBID() { return this.dbID; } @Override public int getObjectUUID() { return currentID; } private void clearStatic() { this.parentZone = null; this.statLat = 0f; this.statLon = 0f; this.statAlt = 0f; } private void initializeMob() { if (this.mobBase != null) { 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); } // Add this npc to building hireling list if not there yet. // For some reason the npc is created and initialized twice when // createMobWithNoID() is called. if (this.building != null && this.loadID != 0) { // Artillery Tower Captains use a hardcoded slot not first available if (this.contract != null && this.contract.getContractID() == 850) this.building.getHirelings().put(this, 2); else slotMobInBuilding(); } //TODO set these correctly later this.rangeHandOne = 8; this.rangeHandTwo = -1; this.minDamageHandOne = 1; this.maxDamageHandOne = 4; this.minDamageHandTwo = 1; this.maxDamageHandTwo = 4; this.atrHandOne = 300; this.atrHandOne = 300; this.defenseRating = 200; this.isActive = true; this.charItemManager.load(); } private void slotMobInBuilding() { int maxSlots = 10; for (int slot = 1; slot < maxSlots + 1; slot++) if (!this.building.getHirelings().containsValue(slot)) { this.building.getHirelings().put(this, slot); break; } } public static NPC getFromCache(int id) { return (NPC) DbManager.getFromCache(GameObjectType.NPC, id); } /* * Getters */ public int getLoadID() { return loadID; } public boolean isMob() { return this.isMob; } public MobBase getMobBase() { return this.mobBase; } public Contract getContract() { return this.contract; } public int getContractID() { if (this.contract != null) return this.contract.getObjectUUID(); return 0; } public boolean isStatic() { return this.isStatic; } public Building getBuilding() { return this.building; } public void setName(String value) { this.name = value; } public static boolean UpdateName(NPC npc, String value) { if (!DbManager.NPCQueries.UPDATE_NAME(npc, value)) return false; npc.name = value; return true; } public void setBuilding(Building value) { this.building = value; } @Override public String getFirstName() { return this.name; } @Override public String getName() { return this.name; } @Override public String getLastName() { return ""; } @Override public Vector3fImmutable getBindLoc() { return this.bindLoc; } @Override public int getGuildUUID() { if (this.guild == null) return 0; return this.guild.getObjectUUID(); } /* * Serialization */ public static void __serializeForClientMsg(NPC npc,ByteBufferWriter writer) throws SerializationException { } public static void serializeNpcForClientMsgOtherPlayer(NPC npc,ByteBufferWriter writer, boolean hideAsciiLastName) throws SerializationException { serializeForClientMsgOtherPlayer(npc,writer); } public static void serializeForClientMsgOtherPlayer(NPC npc,ByteBufferWriter writer) throws SerializationException { writer.putInt(0); writer.putInt(0); //num Runes int cnt = 3; boolean isVamp = false, isHealer = false, isArcher = false, isTrainer = false; int contractID = 0, classID = 0; int extraRune = 0; if (npc.contract != null) { contractID = npc.contract.getContractID(); classID = npc.contract.getClassID(); extraRune = npc.contract.getExtraRune(); if (extraRune == contractID) extraRune = 0; } if ((contractID > 252642 && contractID < 252647) || contractID == 252652) { isVamp = true; cnt++; } if (contractID == 252582 || contractID == 252579 || contractID == 252581 || contractID == 252584 || contractID == 252597 || contractID == 252598 || contractID == 252628 || extraRune == 252582 || extraRune == 252579 || extraRune == 252581 || extraRune == 252584 || extraRune == 252597 || extraRune == 252598 || extraRune == 252628) { isHealer = true; cnt++; } if (contractID == 252570) { isArcher = true; cnt++; } if (classID != 0) cnt++; if (extraRune != 0 && extraRune != contractID) { cnt++; } writer.putInt(cnt); //Race writer.putInt(1); writer.putInt(0); if (npc.mobBase != null) writer.putInt(npc.mobBase.getLoadID()); else writer.putInt(2011); writer.putInt(GameObjectType.NPCRaceRune.ordinal()); writer.putInt(npc.currentID); //Class/Trainer/Whatever writer.putInt(5); writer.putInt(0); if (npc.contract != null) { writer.putInt(contractID); }else writer.putInt(2500); writer.putInt(GameObjectType.NPCClassRune.ordinal()); writer.putInt(npc.currentID); //vampire trainer cnt = 0; if (extraRune != 0) cnt = serializeExtraRune(npc,extraRune, cnt, writer); if (isVamp) cnt = serializeExtraRune(npc,252647, cnt, writer); //Healer trainer if (isHealer) { // int healerRune = 2501; // if (npc.getLevel() >= 60) //healerRune = 252592; cnt = serializeExtraRune(npc,252592, cnt, writer); } if (classID != 0) { writer.putInt(4); writer.putInt(0); writer.putInt(classID); writer.putInt(GameObjectType.NPCExtraRune.ordinal()); writer.putInt(npc.currentID); } //Scout trainer if (isArcher) { cnt = serializeExtraRune(npc,252654, cnt, writer); } // if (extraRune != 0 && extraRune != contractID) { // writer.putInt(3); // writer.putInt(0); // writer.putInt(extraRune); // writer.putInt(GameObjectType.NPCExtraRune.ordinal()); // writer.putInt(npc.getObjectUUID()); // } //Shopkeeper writer.putInt(5); writer.putInt(0); writer.putInt(0x3DACC); writer.putInt(GameObjectType.NPCShopkeeperRune.ordinal()); writer.putInt(npc.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); if (!npc.nameOverride.isEmpty()){ writer.putString(npc.nameOverride); writer.putInt(0); }else if (npc.contract != null) { if (npc.contract.isTrainer()) { writer.putString(npc.name + ", " + npc.contract.getName()); writer.putString(""); } else { writer.putString(npc.name); writer.putString(npc.contract.getName()); } } else { writer.putString(npc.name); writer.putString(""); } writer.putInt(0); writer.putInt(0); writer.putInt(0); writer.putInt(0); //writer.putInt(0); wHy do i see 4 in recording? writer.put((byte) 0); writer.putInt(npc.getObjectType().ordinal()); writer.putInt(npc.currentID); // writer.putLong(npc.getCompositeID()); writer.putFloat(1.0f); writer.putFloat(1.0f); writer.putFloat(1.0f); if (npc.region != null){ writer.putVector3f(npc.inBuildingLoc); }else{ writer.putFloat(npc.getLoc().getX()); writer.putFloat(npc.getLoc().getY()); writer.putFloat(npc.getLoc().getZ()); } //Rotation float radians = (float) Math.asin(npc.getRot().y) * 2; if (npc.building != null) if (npc.building.getBounds() != null && npc.building.getBounds().getQuaternion() != null) radians += (npc.building.getBounds().getQuaternion()).angleY; writer.putFloat(radians); //Running Speed writer.putInt(0); // get a copy of the equipped items. if (npc.equip != null){ writer.putInt(npc.equip.size()); for (MobEquipment me: npc.equip.values()) MobEquipment.serializeForClientMsg(me,writer); }else writer.putInt(0); writer.putInt((npc.level / 10)); writer.putInt(npc.level); writer.putInt(npc.getIsSittingAsInt()); //Standing writer.putInt(npc.getIsWalkingAsInt()); //Walking writer.putInt(npc.getIsCombatAsInt()); //Combat writer.putInt(2); //Unknown writer.putInt(1); //Unknown - Headlights? writer.putInt(0); if (npc.building != null && npc.region != null){ writer.putInt(npc.building.getObjectType().ordinal()); writer.putInt(npc.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); //npc dialog menus from contracts if (npc.contract != null) { ArrayList npcMenuOptions = npc.contract.getNPCMenuOptions(); writer.putInt(npcMenuOptions.size()); for (Integer val : npcMenuOptions) { writer.putInt(val); } } else { writer.putInt(0); } writer.put((byte) 1); if (npc.building != null) { writer.putInt(GameObjectType.StrongBox.ordinal()); writer.putInt(npc.currentID); writer.putInt(GameObjectType.StrongBox.ordinal()); writer.putInt(npc.building.getObjectUUID()); } else { writer.putLong(0); writer.putLong(0); } if (npc.contract != null) { writer.putInt(npc.contract.getIconID()); } else { writer.putInt(0); //npc icon ID } // for (int i=0;i<5;i++) // writer.putInt(0); writer.putInt(0); writer.putShort((short)0); if (npc.contract != null && npc.contract.isTrainer()) { writer.putInt(classID); } else { writer.putInt(0); } if (npc.contract != null && npc.contract.isTrainer()) { writer.putInt(classID); } else { writer.putInt(0); } writer.putInt(0); writer.putInt(0); writer.putFloat(4); writer.putInt(0); writer.putInt(0); writer.putInt(0); writer.put((byte) 0); //Pull guild info from building if linked to one Guild.serializeForClientMsg(npc.guild,writer, null, true); writer.putInt(1); writer.putInt(0x8A2E); // writer.putInt((npc.contract != null) ? npc.contract.getContractID() : 0x8A2E); writer.putInt(0); writer.putInt(0); //TODO Guard writer.put((byte) 0); //Is guard.. writer.putFloat(1500f); //npc.healthMax writer.putFloat(1500f); //npc.health //TODO Peace Zone writer.put((byte) 1); //0=show tags, 1=don't writer.putInt(0); writer.put((byte) 0); } public void removeMinions() { for (Mob toRemove : this.siegeMinionMap.keySet()) { toRemove.state = STATE.Disabled; try { toRemove.clearEffects(); }catch(Exception e){ Logger.error( e.getMessage()); } if (toRemove.getParentZone() != null) toRemove.getParentZone().zoneMobSet.remove(toRemove); WorldGrid.RemoveWorldObject(toRemove); DbManager.removeFromCache(toRemove); PlayerCharacter petOwner = toRemove.getOwner(); if (petOwner != null) { petOwner.setPet(null); toRemove.setOwner(null); PetMsg petMsg = new PetMsg(5, null); Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); } } } private static int serializeExtraRune(NPC npc,int runeID, int cnt, ByteBufferWriter writer) { writer.putInt(5); writer.putInt(0); writer.putInt(runeID); if (cnt == 0) { writer.putInt(GameObjectType.NPCClassRuneTwo.ordinal()); } else { writer.putInt(GameObjectType.NPCClassRuneThree.ordinal()); } writer.putInt(npc.currentID); return cnt + 1; } @Override public float getSpeed() { if (this.isWalk()) { return MBServerStatics.WALKSPEED; } else { return MBServerStatics.RUNSPEED; } } @Override public float getPassiveChance(String type, int AttackerLevel, boolean fromCombat) { //TODO add this later for dodge return 0f; } /** * @ Kill this Character */ @Override public void killCharacter(AbstractCharacter attacker) { //TODO Handle Death killCleanup(); //TODO Send death message if needed } @Override public void killCharacter(String reason) { //TODO Handle Death killCleanup(); //Orphan inventory so it can be looted //if (!this.inSafeZone) if (this.charItemManager != null) { this.charItemManager.orphanInventory(); } //TODO Send death message if needed //Question? How would a mob die to water? } private void killCleanup() { //TODO handle cleanup from death here //set so character won't load this.load = false; //Create Corpse and add to world //Corpse.makeCorpse(this); //TODO damage equipped items //TODO cleanup any timers } public Zone getParentZone() { return this.parentZone; } public int getParentZoneID() { if (this.parentZone != null) { return this.parentZone.getObjectUUID(); } return 0; } public void setParentZone(Zone zone) { if (ConfigManager.serverType.equals(ServerType.LOGINSERVER)) return; if (this.contract == null) return; //update ZoneManager's zone mpc set if (zone != null) { if (this.parentZone != null) { if (zone.getObjectUUID() != this.parentZone.getObjectUUID()) { this.parentZone.zoneNPCSet.remove(this); zone.zoneNPCSet.add(this); } } else { zone.zoneNPCSet.add(this); } } else if (this.parentZone != null) { this.parentZone.zoneNPCSet.remove(this); } if (this.parentZone == null) this.parentZone = zone; if (!this.parentZone.isPlayerCity()) this.sellPercent = 1; if (this.building != null){ BuildingModelBase buildingModel = BuildingModelBase.getModelBase(this.building.getMeshUUID()); Vector3fImmutable slotLocation = Vector3fImmutable.ZERO; if (buildingModel != null){ int putSlot; BuildingLocation buildingLocation = null; //-1 slot means no slot available in building. if (this.slot != -1) putSlot = this.slot; else putSlot = NPC.getBuildingSlot(this); buildingLocation = buildingModel.getSlotLocation(putSlot); if (buildingLocation != null){ slotLocation = buildingLocation.getLoc(); this.setRot(new Vector3f(buildingLocation.getRot())); } else{ //only log if npc has been loaded in building. if (this.building.getHirelings().containsKey(this) && putSlot != -1) { Logger.error("could not slot npc : " + currentID + " in slot " + putSlot + " for building Mesh " + this.building.getMeshUUID()); } } } Vector3fImmutable buildingWorldLoc = ZoneManager.convertLocalToWorld(this.building, slotLocation); //Set floor and level here after building World Location. this.region = BuildingManager.GetRegion(this.building, buildingWorldLoc.x, buildingWorldLoc.y, buildingWorldLoc.z); if (this.region != null){ this.buildingFloor = region.getRoom(); this.buildingLevel = region.getLevel(); this.inBuildingLoc = ZoneManager.convertWorldToLocal(building, this.getLoc()); }else{ this.buildingFloor = -1; this.buildingLevel = -1; } this.setBindLoc(new Vector3fImmutable(buildingWorldLoc.x, buildingWorldLoc.y, buildingWorldLoc.z)); if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)) this.setLoc(new Vector3fImmutable(buildingWorldLoc.x, buildingWorldLoc.y, buildingWorldLoc.z)); }else{ this.setBindLoc(new Vector3fImmutable(this.statLat + zone.absX, this.statAlt + zone.absY, this.statLon + zone.absZ)); if (ConfigManager.serverType.equals(ServerType.WORLDSERVER)) this.setLoc(new Vector3fImmutable(this.statLat + zone.absX, this.statAlt + zone.absY, this.statLon + zone.absZ)); } //create npc profits if (this.parentZone != null){ if (this.parentZone.isPlayerCity()) if (NPC.GetNPCProfits(this) == null) NPCProfits.CreateProfits(this); } } @Override public Vector3fImmutable getLoc() { return super.getLoc(); } public float getSpawnX() { return this.statLat; } public float getSpawnY() { return this.statAlt; } public float getSpawnZ() { return this.statLon; } //Sets the relative position to a parent zone public void setRelPos(Zone zone, float locX, float locY, float locZ) { //update ZoneManager's zone building list if (zone != null) { if (this.parentZone != null) { if (zone.getObjectUUID() != this.parentZone.getObjectUUID()) { this.parentZone.zoneNPCSet.remove(this); zone.zoneNPCSet.add(this); } } else { zone.zoneNPCSet.add(this); } } else if (this.parentZone != null) { this.parentZone.zoneNPCSet.remove(this); } this.statLat = locX; this.statAlt = locY; this.statLon = locZ; this.parentZone = zone; } public float getSellPercent() { return this.sellPercent; } public void setSellPercent(float sellPercent) { this.sellPercent = sellPercent; } public float getBuyPercent() { return this.buyPercent; } public float getBuyPercent(PlayerCharacter player) { if (NPC.GetNPCProfits(this) == null || this.guild == null) return this.buyPercent; NPCProfits profits = NPC.GetNPCProfits(this); if (player.getGuild().equals(this.guild)) return profits.buyGuild; if (player.getGuild().getNation().equals(this.guild.getNation())) return profits.buyNation; return profits.buyNormal; } public float getSellPercent(PlayerCharacter player) { if (NPC.GetNPCProfits(this) == null || this.guild == null) return 1 + this.sellPercent; NPCProfits profits = NPC.GetNPCProfits(this); if (player.getGuild().equals(this.guild)) return 1 + profits.sellGuild; if (player.getGuild().getNation().equals(this.guild.getNation())) return 1 + profits.sellNation; return 1 + profits.sellNormal; } public void setBuyPercent(float buyPercent) { this.buyPercent = buyPercent; } @Override public boolean canBeLooted() { return !this.isAlive(); } // *** Refactor : this has a useInit flag that can be removed public static NPC createNPC(String name, int contractID, Vector3fImmutable spawn, Guild guild, boolean isMob, Zone parent, short level, boolean useInit, Building building) { NPC npcWithoutID = new NPC(name, (short) 0, (short) 0, (short) 0, (short) 0, (short) 0, (short) 1, 0, false, false, false, spawn, spawn, Vector3fImmutable.ZERO, (short) 1, (short) 1, (short) 1, guild, (byte) 0, 0, isMob, building, contractID, parent); npcWithoutID.setLevel(level); if (parent != null) { npcWithoutID.setRelPos(parent, spawn.x - parent.absX, spawn.y - parent.absY, spawn.z - parent.absZ); } if (npcWithoutID.mobBase == null) { return null; } NPC npc; try { npc = DbManager.NPCQueries.ADD_NPC(npcWithoutID, isMob); npc.setObjectTypeMask(MBServerStatics.MASK_NPC); } catch (Exception e) { Logger.error( e); npc = null; } return npc; } public static NPC getNPC(int id) { if (id == 0) return null; NPC npc = (NPC) DbManager.getFromCache(GameObjectType.NPC, id); if (npc != null) return npc; return DbManager.NPCQueries.GET_NPC(id); } public ArrayList getModTypeTable() { return this.modTypeTable; } @Override public void updateDatabase() { DbManager.NPCQueries.updateDatabase(this); } public int getSymbol() { return symbol; } public ArrayList getModSuffixTable() { return modSuffixTable; } public ArrayList getItemModTable() { return itemModTable; } public boolean isRanking() { return this.upgradeDateTime != null; } @Override public void runAfterLoad() { if (ConfigManager.serverType.equals(ServerType.LOGINSERVER)) return; try{ this.equip = loadEquipmentSet(this.equipmentSetID); }catch(Exception e){ Logger.error(e.getMessage()); } if (this.equip == null) this.equip = new HashMap<>(); try{ DbManager.NPCQueries.LOAD_ALL_ITEMS_TO_PRODUCE(this); for (ProducedItem producedItem : this.forgedItems){ MobLoot ml = new MobLoot(this, ItemBase.getItemBase(producedItem.getItemBaseID()), false); DbManager.NPCQueries.UPDATE_ITEM_ID(producedItem.getID(), currentID, ml.getObjectUUID()); if (producedItem.isInForge()){ if (producedItem.getPrefix() != null && !producedItem.getPrefix().isEmpty()){ ml.addPermanentEnchantment(producedItem.getPrefix(), 0, 0, true); ml.setPrefix(producedItem.getPrefix()); } if (producedItem.getSuffix() != null && !producedItem.getSuffix().isEmpty()){ ml.addPermanentEnchantment(producedItem.getSuffix(), 0, 0, false); ml.setSuffix(producedItem.getSuffix()); } if (!producedItem.isRandom()) ml.setIsID(true); ml.loadEnchantments(); ml.setValue(producedItem.getValue()); ml.setDateToUpgrade(producedItem.getDateToUpgrade().getMillis()); ml.containerType = Enum.ItemContainerType.FORGE; this.addItemToForge(ml); }else{ if (producedItem.getPrefix() != null && !producedItem.getPrefix().isEmpty()){ ml.addPermanentEnchantment(producedItem.getPrefix(), 0, 0, true); ml.setPrefix(producedItem.getPrefix()); } if (producedItem.getSuffix() != null && !producedItem.getSuffix().isEmpty()){ ml.addPermanentEnchantment(producedItem.getSuffix(), 0, 0, false); ml.setSuffix(producedItem.getSuffix()); } ml.setDateToUpgrade(producedItem.getDateToUpgrade().getMillis()); ml.containerType = Enum.ItemContainerType.INVENTORY; ml.setIsID(true); this.charItemManager.addItemToInventory(ml); } ml.setValue(producedItem.getValue()); } // Create NPC bounds object Bounds npcBounds = Bounds.borrow(); npcBounds.setBounds(this.getLoc()); }catch (Exception e){ Logger.error( e.getMessage()); } } public void removeFromZone() { this.parentZone.zoneNPCSet.remove(this); } @Override protected ConcurrentHashMap initializePowers() { return new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); } public DateTime getUpgradeDateTime() { lock.readLock().lock(); try { return upgradeDateTime; } finally { lock.readLock().unlock(); } } public void setUpgradeDateTime(DateTime upgradeDateTime) { if (!DbManager.NPCQueries.updateUpgradeTime(this, upgradeDateTime)){ Logger.error( "Failed to set upgradeTime for building " + currentID); return; } this.upgradeDateTime = upgradeDateTime; } public ArrayList getRolling() { synchronized(rolling){ return rolling; } } public int getRollingCount(){ synchronized(this.rolling){ return rolling.size(); } } public void addItemToForge(MobLoot item){ synchronized(this.rolling){ this.rolling.add(item); } } public void removeItemFromForge(Item item){ synchronized(this.rolling){ this.rolling.remove(item); } } public ArrayList getProtectedBuildings() { ArrayList protectedBuildings = new ArrayList<>(); if (this.building == null) return protectedBuildings; if (this.building.getCity() == null) return protectedBuildings; for (Building b : this.building.getCity().getParent().zoneBuildingSet) { if (b.getBlueprint() == null) continue; if (b.getProtectionState().equals(ProtectionState.CONTRACT)) protectedBuildings.add(b); if (b.getProtectionState().equals(ProtectionState.PENDING)) protectedBuildings.add(b); } return protectedBuildings; } @Override public Guild getGuild(){ if (this.building != null) return building.getGuild(); return this.guild; } public ArrayList getSiegeMinions() { return siegeMinions; } public HashSet getCanRoll() { if (this.canRoll == null){ this.canRoll = DbManager.ItemQueries.GET_ITEMS_FOR_VENDOR(this.vendorID); if (this.contract.getVendorID() == 102){ for (int i = 0;i getSiegeMinionMap() { return siegeMinionMap; } public void setSiegeMinionMap(ConcurrentHashMap siegeMinionMap) { this.siegeMinionMap = siegeMinionMap; } // Method removes the npc from the game simulation // and deletes it from the database. public boolean remove() { Building building; // Remove npc from it's building building = this.building; if (building != null) { building.getHirelings().remove(this); this.removeMinions(); } // Delete npc from database if (DbManager.NPCQueries.DELETE_NPC(this) == 0) { return false; } // Remove npc from the simulation this.removeFromCache(); WorldGrid.RemoveWorldObject(this); WorldGrid.removeObject(this); return true; } public static void loadAllPirateNames() { DbManager.NPCQueries.LOAD_PIRATE_NAMES(); } public static String getPirateName(int mobBaseID) { ArrayList nameList = null; // If we cannot find name for this mobbase then // fallback to human male if (_pirateNames.containsKey(mobBaseID)) nameList = _pirateNames.get(mobBaseID); else nameList = _pirateNames.get(2111); if (nameList == null) { Logger.error("Null name list for 2111!"); } return nameList.get(ThreadLocalRandom.current().nextInt(nameList.size())); } public synchronized Mob createSiegeMob(int loadID, Guild guild, Zone parent, Vector3fImmutable loc, short level) { MobBase minionMobBase; Mob mob; if (siegeMinionMap.size() == 3) return null; minionMobBase = MobBase.getMobBase(loadID); if (minionMobBase == null) return null; mob = new Mob(minionMobBase, guild, parent, level,new Vector3fImmutable(1,1,1), 0,false); mob.runAfterLoad(); mob.despawned = true; DbManager.addToCache(mob); if (parent != null) mob.setRelPos(parent, loc.x - parent.absX, loc.y - parent.absY, loc.z - parent.absZ); mob.setObjectTypeMask(MBServerStatics.MASK_MOB | mob.getTypeMasks()); // mob.setMob(); mob.setSiege(true); mob.setParentZone(parent); int slot = 0; if (!siegeMinionMap.containsValue(1)) slot = 1; else if (!siegeMinionMap.containsValue(2)) slot = 2; siegeMinionMap.put(mob, slot); mob.setInBuildingLoc(this.building, this); Vector3fImmutable buildingWorldLoc = ZoneManager.convertLocalToWorld(this.building, mob.inBuildingLoc); mob.setBindLoc(buildingWorldLoc); mob.setLoc(buildingWorldLoc); mob.setSpawnTime(10); mob.setNpcOwner(this); mob.state = STATE.Awake; return mob; } public int getUpgradeCost() { int upgradeCost; upgradeCost = Integer.MAX_VALUE; if (this.getRank() < 7) return (this.getRank() * 100650) + 21450; return upgradeCost; } public int getUpgradeTime() { int upgradeTime; // expressed in hours upgradeTime = Integer.MAX_VALUE; if (this.getRank() < 7) return (this.getRank() * 8); return 0; } public static boolean ISGuardCaptain(int contractID){ return MinionType.ContractToMinionMap.containsKey(contractID); } public synchronized Item produceItem(int playerID,int amount, boolean isRandom, int pToken, int sToken, String customName, int itemID) { Zone serverZone; City city; Item item = null; PlayerCharacter player = null; if (playerID != 0) player = SessionManager.getPlayerCharacterByID(playerID); try{ if (this.getRollingCount() >= this.getRank()) { if (player != null) ChatManager.chatSystemInfo(player, this.getName() + " " + this.getContract().getName() + " slots are full"); return null; } // Cannot roll items without a warehouse. // Due to the fact fillForge references the // warehouse and early exits. *** Refactor??? serverZone = this.building.getParentZone(); if (serverZone == null) return null; city = City.GetCityFromCache(serverZone.getPlayerCityUUID()); if (city == null) { if (player != null) ErrorPopupMsg.sendErrorMsg(player, "Could not find city."); // Production denied: This building must be protected to gain access to warehouse resources. return null; } if (this.building == null){ if (player != null) ErrorPopupMsg.sendErrorMsg(player, "Could not find building."); // Production denied: This building must be protected to gain ac return null; } //TODO create Normal Items. if (amount == 0) amount = 1; if (isRandom) item = ItemFactory.randomRoll(this, player,amount, itemID); else item = ItemFactory.fillForge(this, player,amount,itemID, pToken,sToken, customName); if (item == null) return null; ItemProductionMsg outMsg = new ItemProductionMsg(this.building, this, item, 8, true); DispatchMessage.dispatchMsgToInterestArea(this, outMsg, DispatchChannel.SECONDARY, 700, false, false); } catch(Exception e){ Logger.error(e); } return item; } public synchronized boolean completeItem(int itemUUID) { MobLoot targetItem; try { targetItem = MobLoot.getFromCache(itemUUID); if (targetItem == null) return false; if (!this.getCharItemManager().forgeContains(targetItem, this)) return false; if (!DbManager.NPCQueries.UPDATE_ITEM_TO_INVENTORY(targetItem.getObjectUUID(), currentID)) return false; targetItem.setIsID(true); this.rolling.remove(targetItem); this.getCharItemManager().addItemToInventory(targetItem); //remove from client forge window ItemProductionMsg outMsg1 = new ItemProductionMsg(this.building, this, targetItem, 9, true); DispatchMessage.dispatchMsgToInterestArea(this, outMsg1, DispatchChannel.SECONDARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false); ItemProductionMsg outMsg = new ItemProductionMsg(this.building, this, targetItem, 10, true); DispatchMessage.dispatchMsgToInterestArea(this, outMsg, DispatchChannel.SECONDARY, MBServerStatics.STRUCTURE_LOAD_RANGE, false, false); } catch(Exception e) { Logger.error( e.getMessage()); } return true; } public int getBuildingLevel() { return buildingLevel; } public int getBuildingFloor() { return buildingFloor; } public HashMap getEquip() { return equip; } public static int getBuildingSlot(NPC npc){ int slot = -1; if (npc.building == null) return -1; BuildingModelBase buildingModel = BuildingModelBase.getModelBase(npc.building.getMeshUUID()); if (buildingModel == null) return -1; if (npc.building.getHirelings().containsKey(npc)) slot = (npc.building.getHirelings().get(npc)); if (buildingModel.getNPCLocation(slot) == null) return -1; return slot; } public int getEquipmentSetID() { return equipmentSetID; } public static boolean UpdateSlot(NPC npc,int slot){ if (!DbManager.NPCQueries.UPDATE_SLOT(npc, slot)) return false; npc.slot = slot; return true; } public static boolean UpdateEquipSetID(NPC npc, int equipSetID){ if (!NPCManager._equipmentSetMap.containsKey(equipSetID)) return false; if (!DbManager.NPCQueries.UPDATE_EQUIPSET(npc, equipSetID)) return false; npc.equipmentSetID = equipSetID; return true; } public static boolean UpdateRaceID(NPC npc, int raceID){ if (!DbManager.NPCQueries.UPDATE_MOBBASE(npc, raceID)) return false; npc.loadID = raceID; npc.mobBase = MobBase.getMobBase(npc.loadID); return true; } public String getNameOverride() { return nameOverride; } public static NPCProfits GetNPCProfits(NPC npc){ return NPCProfits.ProfitCache.get(npc.currentID); } public int getRepairCost() { return repairCost; } public void setRepairCost(int repairCost) { this.repairCost = repairCost; } public void processUpgradeNPC(PlayerCharacter player) { int rankCost; Building building; DateTime dateToUpgrade; this.lock.writeLock().lock(); try{ building = this.getBuilding(); // 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; rankCost = this.getUpgradeCost(); // SEND NOT ENOUGH GOLD ERROR if (!building.hasFunds(rankCost)){ sendErrorPopup(player, 127); return; } if (rankCost > building.getStrongboxValue()) { sendErrorPopup(player, 127); return; } try { if (!building.transferGold(-rankCost,false)) return; dateToUpgrade = DateTime.now().plusHours(this.getUpgradeTime()); this.setUpgradeDateTime(dateToUpgrade); // Schedule upgrade job this.submitUpgradeJob(); } 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{ this.lock.writeLock().unlock(); } } public void processRedeedNPC( ClientConnection origin) { // Member variable declaration PlayerCharacter player; Contract contract; CharacterItemManager itemMan; ItemBase itemBase; Item item; this.lock.writeLock().lock(); try{ if (building == null) return; player = SessionManager.getPlayerCharacter(origin); itemMan = player.getCharItemManager(); contract = this.getContract(); if (!player.getCharItemManager().hasRoomInventory((short)1)){ ErrorPopupMsg.sendErrorPopup(player, 21); return; } if (!building.getHirelings().containsKey(this)) return; if (!this.remove()) { PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); return; } building.getHirelings().remove(this); itemBase = ItemBase.getItemBase(contract.getContractID()); if (itemBase == null) { Logger.error("Could not find Contract for npc: " + this.getObjectUUID()); return; } boolean itemWorked = false; item = new Item( itemBase, player.getObjectUUID(), OwnerType.PlayerCharacter, (byte) ((byte) this.getRank() - 1), (byte) ((byte) this.getRank() - 1), (short) 1, (short) 1, true, false, Enum.ItemContainerType.INVENTORY, (byte) 0, new ArrayList<>(),""); item.setNumOfItems(1); item.containerType = Enum.ItemContainerType.INVENTORY; try { item = DbManager.ItemQueries.ADD_ITEM(item); itemWorked = true; } catch (Exception e) { Logger.error(e); } if (itemWorked) { itemMan.addItemToInventory(item); itemMan.updateInventory(); } ManageCityAssetsMsg mca = new ManageCityAssetsMsg(); mca.actionType = SVR_CLOSE_WINDOW; mca.setTargetType(building.getObjectType().ordinal()); mca.setTargetID(building.getObjectUUID()); origin.sendMsg(mca); }catch(Exception e){ Logger.error(e); }finally{ this.lock.writeLock().unlock(); } } }