// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.ai; import engine.gameManager.ConfigManager; import engine.gameManager.ZoneManager; import engine.objects.Mob; import engine.objects.Zone; import engine.util.ThreadUtils; import org.joda.time.DateTime; import org.pmw.tinylog.Logger; import java.time.Duration; import java.time.Instant; public class MobileFSMManager { private static final MobileFSMManager INSTANCE = new MobileFSMManager(); public static Duration executionTime = Duration.ofNanos(1); public static Duration executionMax = Duration.ofNanos(1); //AI variables moved form mb_server_statics public static int AI_BASE_AGGRO_RANGE = 60; public static int AI_DROP_AGGRO_RANGE = 60; public static int AI_PULSE_MOB_THRESHOLD = 200; public static int AI_PATROL_DIVISOR = 15; public static int AI_POWER_DIVISOR = 20; private volatile boolean alive; private long timeOfKill = -1; public fsmState currentState; public Mob currentMob; public enum fsmState { ATTACKTARGET, ATTACKPLAYER, ATTACKBUILDING, ATTACKMOB, PATROL, CANCAST, MOBCAST, MOBCALLFORHELP, DETERMINEACTION, CHECKFORAGRO, CHECKMOBMOVEMENT, CHECKFORRESPAWN, CHECKFORATTACK, CHECKTOSENDMOBHOME, CHASETARGET, SAFEGUARDAGRO, GUARDCAPTAINLOGIC, GUARDMINIONLOGIC, GUARDWALLARCHERLOGIC, PETLOGIC, HAMLETGUARDLOGIC, DEFAULTLOGIC, CHECKFORPLAYERGUARDAGRO, GUARDCANAGRO, RANDOMGUARDPATROLPOINTS, CHANGETARGETFROMHATEVALUE } public String getFSMState(){ return "Executing: " + this.currentState + " on Mobile UUID: " + this.currentMob.getObjectUUID() + " " + this.currentMob.getName(); } private MobileFSMManager() { Runnable worker = new Runnable() { @Override public void run() { execution(); } }; alive = true; //assign the AI varibales base don difficulty scaling from config file: float difficulty = Float.parseFloat(ConfigManager.MB_AI_AGGRO_RANGE.getValue()); AI_BASE_AGGRO_RANGE = (int) (100 * difficulty); // range at which aggressive mobs will attack you difficulty = Float.parseFloat(ConfigManager.MB_AI_CAST_FREQUENCY.getValue()); AI_POWER_DIVISOR = (int) (20 * (1.5f - difficulty)); //duration between mob casts Thread t = new Thread(worker, "MobileFSMManager"); t.start(); } public static MobileFSMManager getInstance() { return INSTANCE; } /** * Stops the MobileFSMManager */ public void shutdown() { if (alive) { alive = false; timeOfKill = System.currentTimeMillis(); } } public boolean isAlive() { return this.alive; } private void execution() { //Load zone threshold once. long mobPulse = System.currentTimeMillis() + AI_PULSE_MOB_THRESHOLD; Instant startTime; while (alive) { ThreadUtils.sleep(1); if (System.currentTimeMillis() > mobPulse) { startTime = Instant.now(); for (Zone zone : ZoneManager.getAllZones()) { if(zone.respawnQue.size() > 0 && zone.lastRespawn + 100 < System.currentTimeMillis()){ zone.respawnQue.get(0).respawn(); zone.respawnQue.remove(0); zone.lastRespawn = System.currentTimeMillis(); } for (Mob mob : zone.zoneMobSet) { try { if (mob != null) MobileFSM.DetermineAction(mob); } catch (Exception e) { Logger.error("Mob: " + mob.getName() + " UUID: " + mob.getObjectUUID() + " ERROR: " + e); e.printStackTrace(); } } } executionTime = Duration.between(startTime, Instant.now()); if (executionTime.compareTo(executionMax) > 0) executionMax = executionTime; mobPulse = System.currentTimeMillis() + AI_PULSE_MOB_THRESHOLD; Logger.error("MobileFSM cycle completed: " + DateTime.now()); } } } }