using System; using System.Collections.Generic; using ACE.Entity; using ACE.Entity.Enum; using ACE.Entity.Models; namespace ACE.Server.WorldObjects { /// /// Summonable monsters combat AI /// public partial class CombatPet : Pet { /// /// A new biota be created taking all of its values from weenie. /// public CombatPet(Weenie weenie, ObjectGuid guid) : base(weenie, guid) { SetEphemeralValues(); } /// /// Restore a WorldObject from the database. /// public CombatPet(Biota biota) : base(biota) { SetEphemeralValues(); } private void SetEphemeralValues() { } public override bool? Init(Player player, PetDevice petDevice) { var success = base.Init(player, petDevice); if (success == null || !success.Value) return success; SetCombatMode(CombatMode.Melee); MonsterState = State.Awake; IsAwake = true; // copy ratings from pet device DamageRating = petDevice.GearDamage; DamageResistRating = petDevice.GearDamageResist; CritDamageRating = petDevice.GearCritDamage; CritDamageResistRating = petDevice.GearCritDamageResist; CritRating = petDevice.GearCrit; CritResistRating = petDevice.GearCritResist; // are CombatPets supposed to attack monsters that are in the same faction as the pet owner? // if not, there are a couple of different approaches to this // the easiest way for the code would be to simply set Faction1Bits for the CombatPet to match the pet owner's // however, retail pcaps did not contain Faction1Bits for CombatPets // doing this the easiest way for the code here, and just removing during appraisal Faction1Bits = player.Faction1Bits; return true; } public override void HandleFindTarget() { var creature = AttackTarget as Creature; if (creature == null || creature.IsDead || !IsVisibleTarget(creature)) FindNextTarget(); } public override bool FindNextTarget() { var nearbyMonsters = GetNearbyMonsters(); if (nearbyMonsters.Count == 0) { //Console.WriteLine($"{Name}.FindNextTarget(): empty"); return false; } // get nearest monster var nearest = BuildTargetDistance(nearbyMonsters, true); if (nearest[0].Distance > VisualAwarenessRangeSq) { //Console.WriteLine($"{Name}.FindNextTarget(): next object out-of-range (dist: {Math.Round(Math.Sqrt(nearest[0].Distance))})"); return false; } AttackTarget = nearest[0].Target; //Console.WriteLine($"{Name}.FindNextTarget(): {AttackTarget.Name}"); return true; } /// /// Returns a list of attackable monsters in this pet's visible targets /// public List GetNearbyMonsters() { var monsters = new List(); foreach (var creature in PhysicsObj.ObjMaint.GetVisibleTargetsValuesOfTypeCreature()) { // why does this need to be in here? if (creature.IsDead || !creature.Attackable || creature.Visibility) { //Console.WriteLine($"{Name}.GetNearbyMonsters(): refusing to add dead creature {creature.Name} ({creature.Guid})"); continue; } // combat pets do not aggro monsters belonging to the same faction as the pet owner? if (SameFaction(creature)) { // unless the pet owner or the pet is being retaliated against? if (!creature.HasRetaliateTarget(P_PetOwner) && !creature.HasRetaliateTarget(this)) continue; } monsters.Add(creature); } return monsters; } public override void Sleep() { // pets dont really go to sleep, per say // they keep scanning for new targets, // which is the reverse of the current ACE jurassic park model return; // empty by default } } }