From 2fa90306822ff807527a63b69d6bd38ba86e6119 Mon Sep 17 00:00:00 2001 From: FatBoy-DOTC Date: Wed, 12 Feb 2025 20:04:35 -0600 Subject: [PATCH] AI branches initial commit --- src/engine/mobileAI/Threads/MobAIThread.java | 9 + src/engine/mobileAI/behaviours/GuardAI.java | 259 ++++++++++++++++++ src/engine/mobileAI/behaviours/PetAI.java | 17 ++ .../mobileAI/behaviours/SiegeEngineAI.java | 89 ++++++ .../mobileAI/behaviours/StandardAI.java | 10 + 5 files changed, 384 insertions(+) create mode 100644 src/engine/mobileAI/behaviours/GuardAI.java create mode 100644 src/engine/mobileAI/behaviours/PetAI.java create mode 100644 src/engine/mobileAI/behaviours/SiegeEngineAI.java create mode 100644 src/engine/mobileAI/behaviours/StandardAI.java diff --git a/src/engine/mobileAI/Threads/MobAIThread.java b/src/engine/mobileAI/Threads/MobAIThread.java index 8a7d8a8a..7da61ace 100644 --- a/src/engine/mobileAI/Threads/MobAIThread.java +++ b/src/engine/mobileAI/Threads/MobAIThread.java @@ -3,6 +3,8 @@ package engine.mobileAI.Threads; import engine.gameManager.ConfigManager; import engine.mobileAI.MobAI; import engine.gameManager.ZoneManager; +import engine.mobileAI.behaviours.GuardAI; +import engine.mobileAI.behaviours.SiegeEngineAI; import engine.objects.Mob; import engine.objects.Zone; import engine.server.MBServerStatics; @@ -33,6 +35,13 @@ public class MobAIThread implements Runnable{ for (Mob mob : zone.zoneMobSet) { try { if (mob != null) { + if(mob.isSiege()){ + SiegeEngineAI.run(mob); + continue; + }else if(mob.isPlayerGuard){ + GuardAI.run(mob); + continue; + } MobAI.DetermineAction(mob); } } catch (Exception e) { diff --git a/src/engine/mobileAI/behaviours/GuardAI.java b/src/engine/mobileAI/behaviours/GuardAI.java new file mode 100644 index 00000000..af0054f6 --- /dev/null +++ b/src/engine/mobileAI/behaviours/GuardAI.java @@ -0,0 +1,259 @@ +package engine.mobileAI.behaviours; + +import engine.Enum; +import engine.gameManager.MovementManager; +import engine.gameManager.PowersManager; +import engine.gameManager.ZoneManager; +import engine.math.Vector3f; +import engine.math.Vector3fImmutable; +import engine.mobileAI.utilities.CombatUtilities; +import engine.mobileAI.utilities.MovementUtilities; +import engine.objects.*; +import engine.powers.MobPowerEntry; +import engine.powers.PowersBase; +import org.pmw.tinylog.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public class GuardAI { + + public static HashMap quedPowerCasts = new HashMap<>(); + + public static void run(Mob guard){ + + //1. check for players nearby to aggro to + if(guard.combatTarget == null) + CheckForPlayerGuardAggro(guard); + + //2. patrol if no target acquired + if(guard.combatTarget == null) { + patrol(guard); + return; + } + + //3. attack based on whether spellcaster, archer or mele + if(guard.mobPowers.isEmpty()){ + if(guard.getEquip().get(2) != null && guard.getEquip().get(2).getItemBase().getRange() > 20) { + runArcher(guard); + }else { + runMele(guard); + } + }else{ + runCaster(guard); + } + } + + public static void runCaster(Mob guard){ + + } + + public static void runMele(Mob guard){ + + } + + public static void runArcher(Mob guard){ + + } + + public static void CheckForPlayerGuardAggro(Mob mob) { + + try { + + //looks for and sets mobs combatTarget + + if (!mob.isAlive()) + return; + + ConcurrentHashMap loadedPlayers = mob.playerAgroMap; + + for (Map.Entry playerEntry : loadedPlayers.entrySet()) { + + int playerID = (int) playerEntry.getKey(); + PlayerCharacter loadedPlayer = PlayerCharacter.getFromCache(playerID); + + //Player is null, let's remove them from the list. + + if (loadedPlayer == null) { + loadedPlayers.remove(playerID); + continue; + } + + //Player is Dead, Mob no longer needs to attempt to aggro. Remove them from aggro map. + + if (!loadedPlayer.isAlive()) { + loadedPlayers.remove(playerID); + continue; + } + + //Can't see target, skip aggro. + + if (!mob.canSee(loadedPlayer)) + continue; + + // No aggro for this player + + if (GuardCanAggro(mob, loadedPlayer) == false) + continue; + + if (MovementUtilities.inRangeToAggro(mob, loadedPlayer) && mob.getCombatTarget() == null) { + mob.setCombatTarget(loadedPlayer); + return; + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: CheckForPlayerGuardAggro" + e.getMessage()); + } + } + + public static Boolean GuardCanAggro(Mob mob, PlayerCharacter target) { + + try { + + if (mob.getGuild().getNation().equals(target.getGuild().getNation())) + return false; + + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardMinion.ordinal()) { + if (((Mob) mob.npcOwner).building.getCity().cityOutlaws.contains(target.getObjectUUID()) == true) { + return true; + } + } else if (mob.building.getCity().cityOutlaws.contains(target.getObjectUUID()) == true) { + return true; + } + + //first check condemn list for aggro allowed (allies button is checked) + + if (ZoneManager.getCityAtLocation(mob.getLoc()).getTOL().reverseKOS) { + for (Map.Entry entry : ZoneManager.getCityAtLocation(mob.getLoc()).getTOL().getCondemned().entrySet()) { + + //target is listed individually + + if (entry.getValue().getPlayerUID() == target.getObjectUUID() && entry.getValue().isActive()) + return false; + + //target's guild is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild()) + return false; + + //target's nation is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild().getNation()) + return false; + } + return true; + } else { + + //allies button is not checked + + for (Map.Entry entry : ZoneManager.getCityAtLocation(mob.getLoc()).getTOL().getCondemned().entrySet()) { + + //target is listed individually + + if (entry.getValue().getPlayerUID() == target.getObjectUUID() && entry.getValue().isActive()) + return true; + + //target's guild is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild()) + return true; + + //target's nation is listed + + if (Guild.getGuild(entry.getValue().getGuildUID()) == target.getGuild().getNation()) + return true; + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: GuardCanAggro" + " " + e.getMessage()); + } + return false; + } + + public static void randomGuardPatrolPoint(Mob mob) { + + try { + + //early exit for a mob who is already moving to a patrol point + //while mob moving, update lastPatrolTime so that when they stop moving the 10 second timer can begin + + if (mob.isMoving() == true) { + mob.stopPatrolTime = System.currentTimeMillis(); + return; + } + + //wait between 10 and 15 seconds after reaching patrol point before moving + + int patrolDelay = ThreadLocalRandom.current().nextInt(10000) + 5000; + + //early exit while waiting to patrol again + + if (mob.stopPatrolTime + patrolDelay > System.currentTimeMillis()) + return; + + float xPoint = ThreadLocalRandom.current().nextInt(400) - 200; + float zPoint = ThreadLocalRandom.current().nextInt(400) - 200; + Vector3fImmutable TreePos = mob.getGuild().getOwnedCity().getLoc(); + mob.destination = new Vector3fImmutable(TreePos.x + xPoint, TreePos.y, TreePos.z + zPoint); + + MovementUtilities.aiMove(mob, mob.destination, true); + + if (mob.BehaviourType.ordinal() == Enum.MobBehaviourType.GuardCaptain.ordinal()) { + for (Map.Entry minion : mob.siegeMinionMap.entrySet()) { + + //make sure mob is out of combat stance + + if (minion.getKey().despawned == false) { + if (MovementUtilities.canMove(minion.getKey())) { + Vector3f minionOffset = Formation.getOffset(2, minion.getValue() + 3); + minion.getKey().updateLocation(); + Vector3fImmutable formationPatrolPoint = new Vector3fImmutable(mob.destination.x + minionOffset.x, mob.destination.y, mob.destination.z + minionOffset.z); + MovementUtilities.aiMove(minion.getKey(), formationPatrolPoint, true); + } + } + } + } + } catch (Exception e) { + Logger.info(mob.getObjectUUID() + " " + mob.getName() + " Failed At: randomGuardPatrolPoints" + " " + e.getMessage()); + } + } + + public static void patrol(Mob guard){ + if (!guard.BehaviourType.equals(Enum.MobBehaviourType.GuardCaptain) && guard.npcOwner.isAlive()) + return; + + //patrol + Building barracks = guard.building; + + if (barracks != null && barracks.patrolPoints != null && !barracks.getPatrolPoints().isEmpty()) { + guard.patrolPoints = barracks.patrolPoints; + } else { + randomGuardPatrolPoint(guard); + return; + } + if (guard.lastPatrolPointIndex > guard.patrolPoints.size() - 1) + guard.lastPatrolPointIndex = 0; + + guard.destination = guard.patrolPoints.get(guard.lastPatrolPointIndex); + guard.lastPatrolPointIndex += 1; + + MovementUtilities.aiMove(guard, guard.destination, true); + + if (guard.BehaviourType.equals(Enum.MobBehaviourType.GuardCaptain)) { + for (Map.Entry minion : guard.siegeMinionMap.entrySet()) + + //make sure mob is out of combat stance + + if (!minion.getKey().despawned) { + if (MovementUtilities.canMove(minion.getKey())) { + Vector3f minionOffset = Formation.getOffset(2, minion.getValue() + 3); + minion.getKey().updateLocation(); + Vector3fImmutable formationPatrolPoint = new Vector3fImmutable(guard.destination.x + minionOffset.x, guard.destination.y, guard.destination.z + minionOffset.z); + MovementUtilities.aiMove(minion.getKey(), formationPatrolPoint, true); + } + } + } + } +} diff --git a/src/engine/mobileAI/behaviours/PetAI.java b/src/engine/mobileAI/behaviours/PetAI.java new file mode 100644 index 00000000..24e52df4 --- /dev/null +++ b/src/engine/mobileAI/behaviours/PetAI.java @@ -0,0 +1,17 @@ +package engine.mobileAI.behaviours; + +import engine.objects.Mob; + +public class PetAI { + + public static void run(Mob pet){ + + //1. check for combat + + //2. check for distance from player + + //3. follow player + + //4. chase combat target + } +} diff --git a/src/engine/mobileAI/behaviours/SiegeEngineAI.java b/src/engine/mobileAI/behaviours/SiegeEngineAI.java new file mode 100644 index 00000000..a097ec14 --- /dev/null +++ b/src/engine/mobileAI/behaviours/SiegeEngineAI.java @@ -0,0 +1,89 @@ +package engine.mobileAI.behaviours; + +import engine.Enum; +import engine.gameManager.BuildingManager; +import engine.gameManager.CombatManager; +import engine.gameManager.MovementManager; +import engine.gameManager.ZoneManager; +import engine.mobileAI.utilities.CombatUtilities; +import engine.objects.Building; +import engine.objects.City; +import engine.objects.Mob; +import engine.server.MBServerStatics; +import org.pmw.tinylog.Logger; + +public class SiegeEngineAI { + + public static void run(Mob engine) { + + //1. check to respawn if engine is dead or initially spawning + if (!engine.isAlive() || engine.despawned) { + if (System.currentTimeMillis() - engine.deathTime > MBServerStatics.FIFTEEN_MINUTES) { + engine.respawn(); + return; + } + } + + //2. early exit if owner is null, siege engines cannot act with player intervention + if (engine.getOwner() == null) + return; + + //3. early exit if target is null, siege engines have no purpose without a target + if (engine.combatTarget == null) + return; + + //4. early exit if target is not a building, siege engines can only attack buildings + if (!engine.combatTarget.getObjectType().equals(Enum.GameObjectType.Building)) { + engine.setCombatTarget(null); + return; + } + + //5. early exit if target is out of range, engines don't move and neither do buildings, avoid infinite loop + if(CombatManager.NotInRange(engine,engine.combatTarget,engine.getRange())) { + engine.setCombatTarget(null); + return; + } + + //6. attack target, sanity checks passed, attack target + AttackBuilding(engine,(Building) engine.combatTarget); + } + + public static void AttackBuilding(Mob engine, Building target) { + + try { + + if (engine == null || target == null) + return; + + if (target.getRank() == -1 || !target.isVulnerable() || BuildingManager.getBuildingFromCache(target.getObjectUUID()) == null) { + engine.setCombatTarget(null); + return; + } + + City playercity = ZoneManager.getCityAtLocation(engine.getLoc()); + + if (playercity != null) + for (Mob guard : playercity.getParent().zoneMobSet) + if (guard.BehaviourType != null && guard.BehaviourType.equals(Enum.MobBehaviourType.GuardCaptain)) + if (guard.getCombatTarget() == null && guard.getGuild() != null && engine.getGuild() != null && !guard.getGuild().equals(engine.getGuild())) + guard.setCombatTarget(engine); + + + MovementManager.sendRWSSMsg(engine); + + CombatUtilities.combatCycle(engine, target, true, null); + int delay = 15000; + engine.setLastAttackTime(System.currentTimeMillis() + delay); + + + //if (mob.isSiege()) { + // PowerProjectileMsg ppm = new PowerProjectileMsg(mob, target); + // ppm.setRange(50); + // DispatchMessage.dispatchMsgToInterestArea(mob, ppm, DispatchChannel.SECONDARY, MBServerStatics.CHARACTER_LOAD_RANGE, false, false); + //} + + } catch (Exception e) { + Logger.info(engine.getObjectUUID() + " " + engine.getName() + " Failed At: AttackBuilding" + " " + e.getMessage()); + } + } +} diff --git a/src/engine/mobileAI/behaviours/StandardAI.java b/src/engine/mobileAI/behaviours/StandardAI.java new file mode 100644 index 00000000..a68c3943 --- /dev/null +++ b/src/engine/mobileAI/behaviours/StandardAI.java @@ -0,0 +1,10 @@ +package engine.mobileAI.behaviours; + +import engine.objects.Mob; + +public class StandardAI { + public static void run(Mob mob){ + + } + +}