// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.gameManager; import engine.InterestManagement.WorldGrid; import engine.loot.WorkOrder; import engine.math.Quaternion; import engine.math.Vector3f; import engine.math.Vector3fImmutable; import engine.mbEnums; import engine.net.Dispatch; import engine.net.DispatchMessage; import engine.net.client.ClientConnection; import engine.net.client.msg.ErrorPopupMsg; import engine.net.client.msg.PetMsg; import engine.objects.*; import engine.powers.EffectsBase; import engine.powers.RuneSkillAdjustEntry; import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import static engine.math.FastMath.acos; public enum NPCManager { NPC_MANAGER; public static HashMap> _runeSetMap = new HashMap<>(); 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 = (PlayerCharacter) necroPet.guardCaptain; if (petOwner != null) { necroPet.guardCaptain = null; petOwner.setPet(null); if (updateOwner == false) return; PetMsg petMsg = new PetMsg(5, null); Dispatch dispatch = Dispatch.borrow(petOwner, petMsg); DispatchMessage.dispatchMsgDispatch(dispatch, mbEnums.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 = mbEnums.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 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 (NPC._pirateNames.containsKey(mobBaseID)) nameList = NPC._pirateNames.get(mobBaseID); else nameList = NPC._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(mbEnums.ProtectionState.CONTRACT)) protectedBuildings.add(b); if (b.getProtectionState().equals(mbEnums.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(mbEnums.BuildingGroup.TOL) && abstractCharacter.building.getRank() == 8) buildingSlot = BuildingManager.getLastAvailableSlot(abstractCharacter.building); else buildingSlot = BuildingManager.getAvailableSlot(abstractCharacter.building); // Override slot for siege engines if (abstractCharacter.getObjectType().equals(mbEnums.GameObjectType.Mob) && ((Mob) abstractCharacter).behaviourType.equals(mbEnums.MobBehaviourType.SiegeEngine)) { Mob siegeMobile = (Mob) abstractCharacter; buildingSlot = siegeMobile.guardCaptain.minions.size() + 2; } if (buildingSlot == -1) Logger.error("No available slot for NPC: " + abstractCharacter.getObjectUUID()); // Pets are regular mobiles not hirelings (Siege engines) if (abstractCharacter.contract != null) 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 void AssignPatrolPoints(Mob mob) { mob.patrolPoints = new ArrayList<>(); for (int i = 0; i < 5; ++i) { float patrolRadius = mob.getSpawnRadius(); if (patrolRadius > 256) patrolRadius = 256; if (patrolRadius < 60) patrolRadius = 60; Vector3fImmutable newPatrolPoint = Vector3fImmutable.getRandomPointInCircle(mob.getBindLoc(), patrolRadius); mob.patrolPoints.add(newPatrolPoint); if (i == 1) { mob.setLoc(newPatrolPoint); mob.endLoc = newPatrolPoint; } } } public static void applyGuardStanceModifiers(Mob guard) { float damageModifier = 1; float attackRatingModifier = 1; float defenseModifier = 1; float attackSpeedModifier = 1; float powerDamageModifier = 1; //handle stance modifiers for guard mob if (guard.agentType.equals(mbEnums.AIAgentType.GUARDWALLARCHER)) { //apply rogue bonuses attackRatingModifier += 0.5f; defenseModifier += 0.5f; damageModifier += 0.5f; attackSpeedModifier -= 0.36f; } else { Integer contractID; if (guard.agentType.equals(mbEnums.AIAgentType.GUARDMINION)) { contractID = guard.guardCaptain.contract.getContractID(); } else { contractID = guard.contract.getContractID(); } if (mbEnums.MinionType.ContractToMinionMap.get(contractID) != null && mbEnums.MinionType.ContractToMinionMap.get(contractID).isMage()) { //apply mage offensive Stance powerDamageModifier += 0.5f; } else { //apply fighter offensive stance damageModifier += 0.5f; attackSpeedModifier -= 0.36f; } } guard.minDamageHandOne *= damageModifier; guard.minDamageHandTwo *= damageModifier; guard.maxDamageHandOne *= damageModifier; guard.maxDamageHandTwo *= damageModifier; guard.atrHandOne *= attackRatingModifier; guard.atrHandTwo *= attackRatingModifier; guard.defenseRating *= defenseModifier; guard.speedHandOne *= attackSpeedModifier; guard.speedHandTwo *= attackSpeedModifier; //TODO figure out how to apply +50% powerdamage to mage guards } public static void setDamageAndSpeedForGuard(Mob guard) { float rankModifier = 1 + (guard.getRank() * 0.1f); int primaryStat = 0; if (guard.charItemManager.equipped.isEmpty()) { guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin()) * rankModifier); guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax()) * rankModifier); guard.speedHandOne = 30.0f; } else { if (guard.charItemManager.equipped.containsKey(mbEnums.EquipSlotType.RHELD)) { //has main hand weapon Item weapon = guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD); if (weapon.template.item_primary_attr.equals(mbEnums.AttributeType.Strength)) primaryStat = guard.getStatStrCurrent(); else primaryStat = guard.getStatDexCurrent(); guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + weapon.template.item_weapon_damage.values().iterator().next()[0]) * rankModifier) + primaryStat; guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + weapon.template.item_weapon_damage.values().iterator().next()[1]) * rankModifier) + primaryStat; guard.speedHandOne = weapon.template.item_weapon_wepspeed; guard.rangeHandOne = weapon.template.item_weapon_max_range; } else if (guard.charItemManager.equipped.containsKey(mbEnums.EquipSlotType.LHELD) && !ItemManager.isShield(guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template)) { //has off hand weapon Item weapon = guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD); if (weapon.template.item_primary_attr.equals(mbEnums.AttributeType.Strength)) primaryStat = guard.getStatStrCurrent(); else primaryStat = guard.getStatDexCurrent(); guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + weapon.template.item_weapon_damage.values().iterator().next()[0]) * rankModifier) + primaryStat; guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + weapon.template.item_weapon_damage.values().iterator().next()[1]) * rankModifier) + primaryStat; guard.speedHandOne = weapon.template.item_weapon_wepspeed; guard.rangeHandOne = weapon.template.item_weapon_max_range; } else { primaryStat = guard.getStatStrCurrent(); guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin()) * rankModifier) + primaryStat; guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax()) * rankModifier) + primaryStat; guard.speedHandOne = 30.0f; guard.rangeHandOne = 3; } } } public static void setDefenseForGuard(Mob guard) { int dexterity = guard.getStatDexCurrent(); if (dexterity < 1) dexterity = 1; int baseDef = guard.mobBase.getDefenseRating(); int armorDefense = 0; for (Item equipped : guard.charItemManager.equipped.values()) if (equipped.template.item_type.equals(mbEnums.ItemType.ARMOR) || ItemManager.isShield(equipped.template)) armorDefense += equipped.template.item_defense_rating; guard.defenseRating = dexterity + baseDef + armorDefense; } public static void setAttackRatingForGuard(Mob guard) { int strength = guard.getStatStrCurrent(); int baseAtr = guard.mobBase.getAttackRating(); if (guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD) != null) guard.atrHandOne = baseAtr + (int) ((strength * 0.5f) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD).template.item_skill_required.values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD).template.item_skill_required.values().toArray()[0] * 3)); else if (guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD) != null && !ItemManager.isShield(guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template)) guard.atrHandTwo = baseAtr + (int) ((strength * 0.5f) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template.item_skill_required.values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template.item_skill_required.values().toArray()[0] * 3)); else guard.atrHandOne = baseAtr; } public static void setMaxHealthForGuard(Mob guard) { //values derived fom reading memory address for health on client when selecting player guards switch (guard.getRank()) { default: guard.healthMax = 750; //rank 1 break; case 2: guard.healthMax = 2082; break; case 3: guard.healthMax = 2740; break; case 4: guard.healthMax = 3414; break; case 5: guard.healthMax = 4080; break; case 6: guard.healthMax = 4746; break; case 7: guard.healthMax = 5412; break; } guard.setHealth(guard.healthMax); } public static void applyMobbaseEffects(Mob mob) { EffectsBase effectsBase; for (MobBaseEffects mbe : mob.mobBase.effectsList) { effectsBase = PowersManager.getEffectByToken(mbe.getToken()); if (effectsBase == null) { Logger.info("Mob: " + mob.getObjectUUID() + " EffectsBase Null for Token " + mbe.getToken()); continue; } //check to upgrade effects if needed. if (mob.effects.containsKey(Integer.toString(effectsBase.getUUID()))) { if (mbe.getReqLvl() > (int) mob.level) continue; Effect eff = mob.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(); mob.addEffectNoTimer(Integer.toString(effectsBase.getUUID()), effectsBase, mbe.getRank(), true); } else { if (mbe.getReqLvl() > (int) mob.level) continue; mob.addEffectNoTimer(Integer.toString(effectsBase.getUUID()), effectsBase, mbe.getRank(), true); } } } public static void applyEquipmentResists(Mob mob) { if (mob.charItemManager.equipped.isEmpty()) return; for (Item equipped : mob.charItemManager.equipped.values()) { if (equipped.template.item_type.equals(mbEnums.ItemType.ARMOR)) { mob.resists.setResist(mbEnums.DamageType.SLASHING, mob.resists.getResist(mbEnums.DamageType.SLASHING, 0) + equipped.template.combat_attack_resist.get("SLASHING")); mob.resists.setResist(mbEnums.DamageType.CRUSHING, mob.resists.getResist(mbEnums.DamageType.CRUSHING, 0) + equipped.template.combat_attack_resist.get("CRUSHING")); mob.resists.setResist(mbEnums.DamageType.PIERCING, mob.resists.getResist(mbEnums.DamageType.PIERCING, 0) + equipped.template.combat_attack_resist.get("PIERCING")); } } } public static void applyMobbaseSkill(Mob mob) { SkillsBase baseSkill = DbManager.SkillsBaseQueries.GET_BASE_BY_TOKEN(mob.mobBase.getMobBaseStats().getBaseSkill()); if (baseSkill != null) mob.getSkills().put(baseSkill.getName(), new CharacterSkill(baseSkill, mob, mob.mobBase.getMobBaseStats().getBaseSkillAmount())); } public static void applyRuneSkills(Mob mob, int runeID) { //load mob skill adjustments from mobbase rune if (PowersManager._allRuneSkillAdjusts.containsKey(runeID)) for (RuneSkillAdjustEntry entry : PowersManager._allRuneSkillAdjusts.get(runeID)) { if (SkillsBase.getFromCache(entry.skill_type) == null) SkillsBase.putInCache(DbManager.SkillsBaseQueries.GET_BASE_BY_NAME(entry.skill_type)); SkillsBase skillBase = SkillsBase.getFromCache(entry.skill_type); if (skillBase == null) continue; if (entry.level <= mob.level) if (mob.skills.containsKey(entry.name) == false) mob.skills.put(entry.skill_type, new CharacterSkill(skillBase, mob, entry.rank)); else mob.skills.put(entry.skill_type, new CharacterSkill(skillBase, mob, entry.rank + mob.skills.get(entry.skill_type).getNumTrains())); } } public static void applyRunesForNPC(NPC npc) { npc.runes = new ArrayList<>(); RuneBase shopkeeperBase = RuneBase.getRuneBase(252620); CharacterRune shopkeeper = new CharacterRune(shopkeeperBase, npc.getObjectUUID()); npc.runes.add(shopkeeper); if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) { for (int runeID : _runeSetMap.get(npc.runeSetID)) { RuneBase rb = RuneBase.getRuneBase(runeID); if (rb != null) { CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID()); npc.runes.add(toApply); } } } } public static Boolean NPCVaultBankRangeCheck(PlayerCharacter pc, ClientConnection origin, String bankorvault) { if (pc == null) return false; NPC npc = pc.getLastNPCDialog(); if (npc == null) return false; // System.out.println(npc.getContract().getName()); // last npc must be either a banker or vault keeper if (bankorvault.equals("vault")) { if (npc.getContract().getContractID() != 861) return false; } else // assuming banker if (!npc.getContract().getName().equals("Bursar")) return false; if (pc.getLoc().distanceSquared2D(npc.getLoc()) > MBServerStatics.NPC_TALK_RANGE * MBServerStatics.NPC_TALK_RANGE) { ErrorPopupMsg.sendErrorPopup(pc, 14); return false; } else return true; } public static ArrayList getAllCookingForVendor(NPC npc) { ArrayList itemList = new ArrayList<>(); ConcurrentHashMap.KeySetView vendorWorkOrders = ForgeManager.vendorWorkOrderLookup.get(npc); for (WorkOrder workOrder : vendorWorkOrders) itemList.addAll(workOrder.cooking); return itemList; } public static long calcRollingDuration(NPC vendor, int templateID) { ItemTemplate template = ItemTemplate.templates.get(templateID); float rollingDuration; if (template == null) return 0; if (template.item_bane_rank > 0) return (long) (template.item_bane_rank * 60 * 60 * 3 * 1000 * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue())); if (vendor.building == null) return 600; rollingDuration = vendor.getBuilding().getRank() * -5L + 40; rollingDuration = TimeUnit.MINUTES.toMillis((long) rollingDuration); rollingDuration *= Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()); return (long) rollingDuration; } }