package engine.gameManager; import engine.Enum; import engine.InterestManagement.WorldGrid; import engine.exception.SerializationException; import engine.math.Quaternion; import engine.math.Vector3f; import engine.math.Vector3fImmutable; import engine.net.ByteBufferWriter; import engine.net.Dispatch; import engine.net.DispatchMessage; import engine.net.client.msg.PetMsg; import engine.objects.*; import engine.powers.EffectsBase; import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.ThreadLocalRandom; import static engine.math.FastMath.acos; public enum NPCManager { NPC_MANAGER; public static HashMap> _runeSetMap = new HashMap<>(); public static int SVR_CLOSE_WINDOW = 4; public static HashMap> _pirateNames = new HashMap<>(); public static void LoadAllRuneSets() { _runeSetMap = DbManager.ItemBaseQueries.LOAD_RUNES_FOR_NPC_AND_MOBS(); } public static void LoadAllBootySets() { LootManager._bootySetMap = DbManager.LootQueries.LOAD_BOOTY_TABLES(); } public static void applyRuneSetEffects(Mob mob) { // Early exit if (mob.runeSet == 0) return; //Apply all rune effects. if (NPCManager._runeSetMap.get(mob.runeSet).contains(252623)) { mob.isPlayerGuard = true; } // Only captains have contracts if (mob.contract != null || mob.isPlayerGuard) applyEffectsForRune(mob, 252621); // Apply effects from RuneSet if (mob.runeSet != 0) for (int runeID : _runeSetMap.get(mob.runeSet)) applyEffectsForRune(mob, runeID); // Not sure why but apply Warrior effects for some reason? applyEffectsForRune(mob, 2518); } public static void applyEffectsForRune(AbstractCharacter character, int runeID) { EffectsBase effectsBase; RuneBase sourceRune = RuneBase.getRuneBase(runeID); // Race runes are in the runeset but not in runebase for some reason if (sourceRune == null) return; for (MobBaseEffects mbe : sourceRune.getEffectsList()) { effectsBase = PowersManager.getEffectByToken(mbe.getToken()); if (effectsBase == null) { Logger.info("Mob: " + character.getObjectUUID() + " EffectsBase Null for Token " + mbe.getToken()); continue; } //check to upgrade effects if needed. if (character.effects.containsKey(Integer.toString(effectsBase.getUUID()))) { if (mbe.getReqLvl() > (int) character.level) continue; Effect eff = character.effects.get(Integer.toString(effectsBase.getUUID())); if (eff == null) continue; //Current effect is a higher rank, dont apply. if (eff.getTrains() > mbe.getRank()) continue; //new effect is of a higher rank. remove old effect and apply new one. eff.cancelJob(); character.addEffectNoTimer(Integer.toString(effectsBase.getUUID()), effectsBase, mbe.getRank(), true); } else { if (mbe.getReqLvl() > (int) character.level) continue; character.addEffectNoTimer(Integer.toString(effectsBase.getUUID()), effectsBase, mbe.getRank(), true); } } } public static void dismissNecroPet(Mob necroPet, boolean updateOwner) { necroPet.setCombatTarget(null); necroPet.hasLoot = false; if (necroPet.parentZone != null) necroPet.parentZone.zoneMobSet.remove(necroPet); try { necroPet.clearEffects(); } catch (Exception e) { Logger.error(e.getMessage()); } necroPet.playerAgroMap.clear(); WorldGrid.RemoveWorldObject(necroPet); DbManager.removeFromCache(necroPet); PlayerCharacter petOwner = necroPet.getOwner(); if (petOwner != null) { necroPet.setOwner(null); petOwner.setPet(null); if (updateOwner == false) return; PetMsg petMsg = new PetMsg(5, null); Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.PRIMARY); } } public static void auditNecroPets(PlayerCharacter player) { int removeIndex = 0; while (player.necroPets.size() >= 10) { if (removeIndex == player.necroPets.size()) break; Mob necroPet = player.necroPets.get(removeIndex); if (necroPet == null) { removeIndex++; continue; } dismissNecroPet(necroPet, true); player.necroPets.remove(necroPet); removeIndex++; } } public static void resetNecroPets(PlayerCharacter player) { for (Mob necroPet : player.necroPets) if (necroPet.isPet()) necroPet.agentType = Enum.AIAgentType.MOBILE; } public static void spawnNecroPet(PlayerCharacter playerCharacter, Mob mob) { if (mob == null) return; if (mob.getMobBaseID() != 12021 && mob.getMobBaseID() != 12022) return; auditNecroPets(playerCharacter); resetNecroPets(playerCharacter); playerCharacter.necroPets.add(mob); } public static void dismissNecroPets(PlayerCharacter playerCharacter) { if (playerCharacter.necroPets.isEmpty()) return; for (Mob necroPet : playerCharacter.necroPets) { try { dismissNecroPet(necroPet, true); } catch (Exception e) { Logger.error(e); } } playerCharacter.necroPets.clear(); } public static void removeSiegeMinions(Mob mobile) { for (Mob toRemove : mobile.siegeMinionMap.keySet()) { if (mobile.isMoving()) { mobile.stopMovement(mobile.getLoc()); if (toRemove.parentZone != null) toRemove.parentZone.zoneMobSet.remove(toRemove); } try { toRemove.clearEffects(); } catch (Exception e) { Logger.error(e.getMessage()); } if (toRemove.parentZone != null) toRemove.parentZone.zoneMobSet.remove(toRemove); WorldGrid.RemoveWorldObject(toRemove); WorldGrid.removeObject(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); } } } public static boolean removeMobileFromBuilding(Mob mobile, Building building) { // Remove npc from it's building try { mobile.clearEffects(); } catch (Exception e) { Logger.error(e.getMessage()); } if (mobile.parentZone != null) mobile.parentZone.zoneMobSet.remove(mobile); if (building != null) { building.getHirelings().remove(mobile); removeSiegeMinions(mobile); } // Delete npc from database if (DbManager.MobQueries.DELETE_MOB(mobile) == 0) return false; // Remove npc from the simulation mobile.removeFromCache(); DbManager.removeFromCache(mobile); WorldGrid.RemoveWorldObject(mobile); WorldGrid.removeObject(mobile); 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 static ArrayList getProtectedBuildings(NPC npc) { ArrayList protectedBuildings = new ArrayList<>(); if (npc.building == null) return protectedBuildings; if (npc.building.getCity() == null) return protectedBuildings; for (Building b : npc.building.getCity().getParent().zoneBuildingSet) { if (b.getBlueprint() == null) continue; if (b.getProtectionState().equals(Enum.ProtectionState.CONTRACT)) protectedBuildings.add(b); if (b.getProtectionState().equals(Enum.ProtectionState.PENDING)) protectedBuildings.add(b); } return protectedBuildings; } public static int slotCharacterInBuilding(AbstractCharacter abstractCharacter) { int buildingSlot; if (abstractCharacter.building == null) return -1; // Get next available slot for this NPC and use it // to add the NPC to the building's hireling list // Account for R8's having slots reversed. if (abstractCharacter.building.getBlueprint() != null && abstractCharacter.building.getBlueprint().getBuildingGroup().equals(Enum.BuildingGroup.TOL) && abstractCharacter.building.getRank() == 8) buildingSlot = BuildingManager.getLastAvailableSlot(abstractCharacter.building); else buildingSlot = BuildingManager.getAvailableSlot(abstractCharacter.building); //if (buildingSlot == -1) //Logger.error("No available slot for NPC: " + abstractCharacter.getObjectUUID()); abstractCharacter.building.getHirelings().put(abstractCharacter, buildingSlot); // Override bind and location for this npc derived // from BuildingManager slot location data. Vector3fImmutable slotLocation = BuildingManager.getSlotLocation(abstractCharacter.building, buildingSlot).getLocation(); abstractCharacter.bindLoc = abstractCharacter.building.getLoc().add(slotLocation); // Rotate slot position by the building rotation abstractCharacter.bindLoc = Vector3fImmutable.rotateAroundPoint(abstractCharacter.building.getLoc(), abstractCharacter.bindLoc, abstractCharacter.building.getBounds().getQuaternion().angleY); abstractCharacter.loc = new Vector3fImmutable(abstractCharacter.bindLoc); // Rotate NPC rotation by the building's rotation Quaternion slotRotation = new Quaternion().fromAngles(0, acos(abstractCharacter.getRot().y) * 2, 0); slotRotation = slotRotation.mult(abstractCharacter.building.getBounds().getQuaternion()); abstractCharacter.setRot(new Vector3f(0, slotRotation.y, 0)); // Configure region and floor/level for this NPC abstractCharacter.region = BuildingManager.GetRegion(abstractCharacter.building, abstractCharacter.bindLoc.x, abstractCharacter.bindLoc.y, abstractCharacter.bindLoc.z); return buildingSlot; } public static boolean ISWallArcher(Contract contract) { if (contract == null) return false; //838, 950, 1051, 1181, 1251, 1351, 1451, 1501, 1526, 1551, 980101, return contract.getAllowedBuildings().contains(Enum.BuildingGroup.WALLCORNER) || contract.getAllowedBuildings().contains(Enum.BuildingGroup.WALLSTRAIGHTTOWER); } public static NPC getFromCache(int id) { return (NPC) DbManager.getFromCache(Enum.GameObjectType.NPC, id); } public static boolean UpdateName(NPC npc, String value) { if (!DbManager.NPCQueries.UPDATE_NAME(npc, value)) return false; npc.setName(value); return true; } 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.getMobBase() != null) writer.putInt(npc.getMobBase().getLoadID()); else writer.putInt(2011); writer.putInt(Enum.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(Enum.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) cnt = serializeExtraRune(npc, 252592, cnt, writer); if (classID != 0) { writer.putInt(4); writer.putInt(0); writer.putInt(classID); writer.putInt(Enum.GameObjectType.NPCExtraRune.ordinal()); writer.putInt(npc.currentID); } //Scout trainer if (isArcher) cnt = serializeExtraRune(npc, 252654, cnt, writer); //Shopkeeper writer.putInt(5); writer.putInt(0); writer.putInt(0x3DACC); writer.putInt(Enum.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); writer.putString(npc.getName()); writer.putString(""); writer.putInt(0); writer.putInt(0); writer.putInt(0); writer.putInt(0); writer.put((byte) 0); writer.putInt(npc.getObjectType().ordinal()); writer.putInt(npc.currentID); writer.putFloat(1.0f); writer.putFloat(1.0f); writer.putFloat(1.0f); if (npc.region != null) writer.putVector3f(ZoneManager.convertWorldToLocal(npc.building, npc.getLoc())); else writer.putVector3f(npc.getLoc()); //Rotation float radians = (float) Math.acos(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(Enum.GameObjectType.StrongBox.ordinal()); writer.putInt(npc.currentID); writer.putInt(Enum.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 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(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); } 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(Enum.GameObjectType.NPCClassRuneTwo.ordinal()); else writer.putInt(Enum.GameObjectType.NPCClassRuneThree.ordinal()); writer.putInt(npc.currentID); return cnt + 1; } public static NPC createNPC(String name, int contractID, Vector3fImmutable spawn, Guild guild, Zone parent, short level, Building building) { NPC newNPC = new NPC(); newNPC.setName(name); newNPC.contractUUID = contractID; if (building == null) newNPC.bindLoc = spawn; else newNPC.bindLoc = Vector3fImmutable.ZERO; newNPC.parentZoneUUID = parent.getObjectUUID(); newNPC.guildUUID = guild.getObjectUUID(); if (building == null) newNPC.buildingUUID = 0; else newNPC.buildingUUID = building.getObjectUUID(); newNPC.level = level; newNPC.buyPercent = .33f; newNPC.sellPercent = 1; NPC npc; try { npc = DbManager.NPCQueries.PERSIST(newNPC); 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(Enum.GameObjectType.NPC, id); if (npc != null) return npc; return DbManager.NPCQueries.GET_NPC(id); } public static boolean ISGuardCaptain(int contractID) { return Enum.MinionType.ContractToMinionMap.containsKey(contractID); } public static boolean UpdateEquipSetID(NPC npc, int equipSetID) { if (!LootManager._bootySetMap.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 static NPCProfits GetNPCProfits(NPC npc) { return NPCProfits.ProfitCache.get(npc.currentID); } }