Dev commands, BuildingManager, ChatManager, ForgeManager, NPCManager, NPC, Warehouse, WorkOrder and ManageNPCMsg now call CObjectTemplate.lookup instead of ItemTemplate.templates.get. Forge production cost reads getItemResourceCosts(). Only the DB loader and linkCacheTemplates still use the legacy class (removed in Task 1.8). Removes the static_item_templates ParityCheck comparator. Final parity (offline p17.py, 7586 rows): every id has a cache template, 0 item_type differences, 0 item_resource_costs differences (84 ITEMs have costs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
568 lines
23 KiB
Java
568 lines
23 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://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.client.ClientConnection;
|
|
import engine.net.client.msg.ErrorPopupMsg;
|
|
import engine.net.client.msg.PetMsg;
|
|
import engine.objects.*;
|
|
import engine.parsers.objects.CObjectTemplate;
|
|
import engine.powers.EffectsBase;
|
|
import engine.powers.RuneSkillAdjustEntry;
|
|
import engine.server.MBServerStatics;
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.EnumMap;
|
|
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<Integer, ArrayList<Integer>> _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);
|
|
DispatchManager.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<String> 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<Building> getProtectedBuildings(NPC npc) {
|
|
|
|
ArrayList<Building> 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 (engine.parsers.objects.ItemTemplate._items.get(weapon.templateID).item_primary_attr == mbEnums.AttributeType.Strength)
|
|
primaryStat = guard.getStatStrCurrent();
|
|
else
|
|
primaryStat = guard.getStatDexCurrent();
|
|
|
|
guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_min) * rankModifier) + primaryStat;
|
|
guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_max) * rankModifier) + primaryStat;
|
|
guard.speedHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_wepspeed;
|
|
guard.rangeHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).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 (engine.parsers.objects.ItemTemplate._items.get(weapon.templateID).item_primary_attr == mbEnums.AttributeType.Strength)
|
|
primaryStat = guard.getStatStrCurrent();
|
|
else
|
|
primaryStat = guard.getStatDexCurrent();
|
|
guard.minDamageHandOne = (int) ((guard.mobBase.getDamageMin() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_min) * rankModifier) + primaryStat;
|
|
guard.maxDamageHandOne = (int) ((guard.mobBase.getDamageMax() + engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_damage.get(0).damage_max) * rankModifier) + primaryStat;
|
|
guard.speedHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).weapon_wepspeed;
|
|
guard.rangeHandOne = engine.parsers.objects.ItemTemplate.getWeapon(weapon.templateID).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.getItemType().equals(mbEnums.ItemType.ARMOR) || ItemManager.isShield(equipped.template))
|
|
armorDefense += engine.parsers.objects.ItemTemplate._items.get(equipped.templateID).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.getItemSkillReq().values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.RHELD).template.getItemSkillReq().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.getItemSkillReq().values().toArray()[0] * 4) + ((int) guard.charItemManager.equipped.get(mbEnums.EquipSlotType.LHELD).template.getItemSkillReq().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) {
|
|
|
|
EnumMap<mbEnums.DamageType, Float> armorResists;
|
|
|
|
if (mob.charItemManager.equipped.isEmpty())
|
|
return;
|
|
|
|
for (Item equipped : mob.charItemManager.equipped.values()) {
|
|
if (equipped.template.getItemType().equals(mbEnums.ItemType.ARMOR)) {
|
|
armorResists = equipped.template.getCombatAttackResist();
|
|
mob.resists.setResist(mbEnums.DamageType.SLASHING, mob.resists.getResist(mbEnums.DamageType.SLASHING, 0) + armorResists.getOrDefault(mbEnums.DamageType.SLASHING, 0f));
|
|
mob.resists.setResist(mbEnums.DamageType.CRUSHING, mob.resists.getResist(mbEnums.DamageType.CRUSHING, 0) + armorResists.getOrDefault(mbEnums.DamageType.CRUSHING, 0f));
|
|
mob.resists.setResist(mbEnums.DamageType.PIERCING, mob.resists.getResist(mbEnums.DamageType.PIERCING, 0) + armorResists.getOrDefault(mbEnums.DamageType.PIERCING, 0f));
|
|
}
|
|
}
|
|
}
|
|
|
|
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)) {
|
|
|
|
// Shopkeeper is already applied above and most rune sets repeat it
|
|
if (runeID == 252620)
|
|
continue;
|
|
|
|
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<Item> getAllCookingForVendor(NPC npc) {
|
|
|
|
ArrayList<Item> itemList = new ArrayList<>();
|
|
|
|
ConcurrentHashMap.KeySetView<WorkOrder, Boolean> vendorWorkOrders = ForgeManager.vendorWorkOrderLookup.get(npc);
|
|
|
|
if (vendorWorkOrders == null)
|
|
return itemList;
|
|
|
|
for (WorkOrder workOrder : vendorWorkOrders)
|
|
itemList.addAll(workOrder.cooking);
|
|
|
|
return itemList;
|
|
}
|
|
|
|
public static long calcRollingDuration(NPC vendor, int templateID) {
|
|
|
|
CObjectTemplate template = CObjectTemplate.lookup(templateID);
|
|
float rollingDuration;
|
|
|
|
if (template == null)
|
|
return 0;
|
|
|
|
if (template.getItemBaneRank() > 0)
|
|
return (long) (template.getItemBaneRank() * 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;
|
|
}
|
|
}
|