Работа по указаниям 2026-09-18. В релиз пока не выходит. НОВЫЙ ПИТОМЕЦ "Дух крысы" (necroRatSpirit), Некромантия 3 (60 убийств): модель зомбоволка в масштабе 0.28, урон 5, кроличьи звуки. Сам не нападает никогда, держится справа-сзади в двух блоках, упёршись в препятствие проходит СКВОЗЬ него и блоков не ломает. Укус замедляет, оставляет метку духа (видна на компасе, +25% получаемого урона) и рвёт жилу. Обгрызая труп, лечится. СВОЯ ЗАДАЧА ИИ. Задачи "иди за сущностью" в игре нет вовсе - проверены все 32 типа EAI*. Написана NecroFollowOwnerTask по образцу EAIApproachSpot: сектор "3-6 часов" от хозяина, FindPath с canBreak:false, проход сквозь препятствие через Entity.IsStuck. Вешается в рантайме, минуя Type.GetType. ПРИКАЗ АТАКОВАТЬ. Повторное применение свитка при живом питомце натравливает его на цель под прицелом: EntityPlayerLocal.HitInfo + ItemActionAttack. GetEntityFromHit. Торговцы и игроки отсеяны. Нет цели - "Нет цели для атаки". ЗОМБОЖИВОТНЫЕ ПРИВЕДЕНЫ К ТОМУ ЖЕ ОБРАЗЦУ. Сняты BreakBlock, Territorial, ApproachSpot, Wander, BlockingTargetTask и поедание трупов; цель они больше не выбирают сами. Лестница урона 20/35/45/60 плюс расчленение у Пса, Медведя и Волка. Кровотечение всем, метка и ослабление - только у крысы. УБИЙСТВА ПИТОМЦЕМ ЗАСЧИТЫВАЮТСЯ ВЛАДЕЛЬЦУ, включая добивание кровотечением. Префикс на AwardKillXPServer подменяет убийцу владельцем; для смерти от баффа заведена память укусов, потому что в DamageSource от баффа нет того, кто его наложил. Зомби под Камнем духов это не задело - решение от 17.09 в силе. ГРИФ откачен на летающую ветку EntityVulture и переименован в Могильного стервятника: модель наконец соответствует имени. Держится у игрока сам, через собственный механизм "дома" (setHomeArea), на время погони дом отвязывается. Попытка натянуть птичий префаб на наземный класс провалилась и записана - так делать нельзя. ПРОЧЕЕ: призрачный вид распространён с торговцев на питомцев (у Пса, Медведя и Волка выключен по указанию), у Пса светятся фиолетовые глаза, белая иконка книги снята со всех свитков призыва, "Жуки Властелина" переименованы в "Рой фараона" на всех 13 языках. ПОРОГИ: крыса 60, стервятник 500, пёс 1300, медведь 2000, волк 4000. ИСПРАВЛЕНО ПО ХОДУ ИГРОВЫХ ПРОВЕРОК: питомцы не призывались обычным кликом (AnimWait требовал удержания), отзыв срабатывал не с первого раза (автомат состояний ItemActionSpawnEntity), крыса проваливалась сквозь мир (IsStuck отключает и пол), питомец подбрасывал хозяина (коллайдеры разводились до появления модели). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
409 lines
22 KiB
C#
409 lines
22 KiB
C#
using GamePath;
|
||
using UnityEngine;
|
||
|
||
namespace NecromancerTome
|
||
{
|
||
/// <summary>
|
||
/// ПЕРВАЯ СВОЯ ЗАДАЧА ИИ В ЭТОМ МОДЕ. "Дух крысы" держится справа-сзади от хозяина, а не
|
||
/// бродит сам по себе - указание 2026-09-18 ("держится сбоку и позади хозяина на 2 блока...
|
||
/// если по циферблату часов, то на 3-6 часов (зависит от препятствий)").
|
||
///
|
||
/// ПОЧЕМУ ЭТО ПРИШЛОСЬ ПИСАТЬ, А НЕ НАСТРОИТЬ. Задачи "иди за сущностью" в игре НЕТ вообще -
|
||
/// проверено по списку типов в Assembly-CSharp: всего 32 класса EAI*, и ни одного
|
||
/// follow-подобного (EAIWander, EAITerritorial, EAIApproachSpot, EAIApproachAndAttackTarget,
|
||
/// EAISetNearestCorpseAsTarget и т.д.). Слежение дрона за хозяином - это захардкоженный C#
|
||
/// внутри EntityDrone, в XML его не выбрать. До сегодняшнего дня мод обходился телепорт-
|
||
/// поводком в PetFollowPatch.cs: раз в секунду, если питомец дальше 32 м, его просто
|
||
/// переставляли к игроку. Это и есть то, что игроки видели как "питомец не идёт следом".
|
||
///
|
||
/// ОБРАЗЕЦ ВЗЯТ С ВАНИЛЬНОГО EAIApproachSpot - он решает ровно ту же задачу "дойти до точки"
|
||
/// и показывает весь нужный обвес: theEntity.FindPath(...) -> theEntity.navigator.getPath()
|
||
/// -> moveHelper. Отличий от него три, и все три - требования пользователя:
|
||
///
|
||
/// 1. ТОЧКА ПЕРЕСЧИТЫВАЕТСЯ КАЖДЫЙ ТИК и привязана к хозяину, а не к месту на земле.
|
||
/// SlotAngles - сектор "3-6 часов" от направления взгляда хозяина, перебираемый от
|
||
/// середины к краям: сначала 135° (ровно между бортом и кормой), потом ближе к борту и к
|
||
/// корме. Первая точка, до которой нашёлся путь, и становится местом крысы - это и есть
|
||
/// "зависит от препятствий".
|
||
///
|
||
/// 2. canBreak: false в FindPath. Питомцы до сих пор носили ванильный AITask BreakBlock и
|
||
/// грызли всё на пути, включая базу игрока. Крыса не может грызть блоки НА УРОВНЕ
|
||
/// ПАФАЙНДЕРА: он просто не строит путей через разрушение. В entityclasses.xml у неё
|
||
/// заодно нет ни BreakBlock, ни BlockingTargetTask.
|
||
///
|
||
/// 3. ПРОХОД СКВОЗЬ БЛОКИ вместо разрушения - указание "технически, дух может проходить
|
||
/// сквозь блоки, если нужно пройти там где есть препятствие. Блоки ломать не нужно".
|
||
/// Для этого не понадобилось ничего изобретать: в Entity есть публичное поле IsStuck, и
|
||
/// единственное место, где игра его читает, - собственный шаг перемещения:
|
||
///
|
||
/// if (IsStuck) { PhysicsTransform.position += hitMove; } // мимо контроллера
|
||
/// else { collisionFlags = m_characterController.Move(hitMove); }
|
||
///
|
||
/// То есть это готовый выключатель столкновений, оставленный игрой себе на случай
|
||
/// застревания. Ни один из просмотренных классов (EntityMoveHelper, EntityZombie,
|
||
/// EntityEnemy, EntityAnimal, EntityAlive, EntityPlayerLocal) в него не ПИШЕТ - поле
|
||
/// фактически бесхозное, и мы можем владеть им единолично.
|
||
///
|
||
/// ОПАСНОСТЬ ЗДЕСЬ РЕАЛЬНАЯ, И ОНА УЖЕ СРАБОТАЛА: IsStuck отключает столкновения со
|
||
/// ВСЕМ, включая пол, и 18.09 крыса провалилась под мир ("fell off the world,
|
||
/// pos=(319.2, -0.4, 1367.2)"). Разбор и все пять страховок - в комментариях к
|
||
/// MaxDropBelowOwner и в UpdatePhase; коротко: гравитация гасится на каждом тике,
|
||
/// ниже цели питомец не опускается, проход не включается на дальней дистанции и
|
||
/// ограничен по времени, а падение ловится отдельной проверкой в начале Update.
|
||
///
|
||
/// ЗАДАЧА ДОБАВЛЯЕТСЯ В РАНТАЙМЕ (SummonPatch.cs), а не через XML. В XML это тоже возможно -
|
||
/// EAIManager.GetType() для незнакомого имени падает в Type.GetType("EAI" + имя), и
|
||
/// индексированные AITask-N берут значение свойства целиком, так что
|
||
/// AITask-N value="NecroFollowOwner,NecromancerHarmony" разрешилось бы в наш тип. Но
|
||
/// Type.GetType с именем сборки дёргает Assembly.Load, а как именно загрузчик модов кладёт
|
||
/// наши DLL - непроверено, и ставить работоспособность питомца в зависимость от этого нет
|
||
/// никакой нужды: aiManager.tasks.AddTask даёт тот же результат без единого допущения, да ещё
|
||
/// и позволяет передать владельца прямо в конструктор.
|
||
/// </summary>
|
||
public class NecroFollowOwnerTask : EAIBase
|
||
{
|
||
/// <summary>Сектор "3-6 часов" в градусах от направления взгляда хозяина: 90° - правый
|
||
/// борт, 180° - корма. Перебор идёт от середины сектора к его краям, чтобы обычное место
|
||
/// крысы было одно и то же, а края работали как запасные при препятствии.</summary>
|
||
public static readonly float[] SlotAngles = { 135f, 112f, 158f, 90f, 180f };
|
||
|
||
/// <summary>Расстояние по умолчанию - два блока от хозяина.</summary>
|
||
public const float DefaultSlotDistance = 2f;
|
||
|
||
/// <summary>Своё у каждого питомца: Медведю и Волку задан блок дальше остальных
|
||
/// (указание 2026-09-18 - "сделай место следования там же где и собаке, но на блок
|
||
/// дальше, они мощнее"). Сектор при этом общий, меняется только радиус, поэтому
|
||
/// крупные звери встают в тот же угол, но не наступают хозяину на пятки.
|
||
/// Значение приходит из SummonPatch.LimitedPets при создании задачи.</summary>
|
||
public float SlotDistance = DefaultSlotDistance;
|
||
|
||
/// <summary>Ближе этого крыса считает, что уже на месте, и останавливается. Квадрат
|
||
/// расстояния, как везде в этом коде - корень тут не нужен.</summary>
|
||
public const float ArrivedDistSq = 1.2f * 1.2f;
|
||
|
||
/// <summary>Столько секунд без сокращения расстояния - и точка считается недостижимой:
|
||
/// пробуем следующую, а когда кончатся все - проходим насквозь.
|
||
///
|
||
/// ПОДНЯТО С 1.5 ДО 3 СЕКУНД 2026-09-18, после того как крыса провалилась сквозь мир.
|
||
/// Полторы секунды - слишком мало: в помещении питомец столько обходит мебель, не
|
||
/// сокращая расстояния ПО ПРЯМОЙ, и проход включался там, где обычный обход дошёл бы
|
||
/// сам. Чем реже поднимается IsStuck, тем меньше поводов у всего, что с ним связано.</summary>
|
||
public const float NoProgressSeconds = 3f;
|
||
|
||
/// <summary>Метры в секунду на проходе сквозь препятствие. Медленнее бега: дух
|
||
/// просачивается, а не выстреливает.</summary>
|
||
public const float PhaseSpeed = 3.5f;
|
||
|
||
/// <summary>Предохранитель. Если за это время проход не закончился, он прекращается
|
||
/// принудительно - лучше крыса, стоящая не там, чем крыса, летящая сквозь мир.</summary>
|
||
public const float PhaseTimeout = 4f;
|
||
|
||
/// <summary>Дальше этого расстояния до хозяина ждать пафайндер бессмысленно - прыжок.
|
||
/// Та же цифра, что у старого поводка в PetFollowPatch.cs (и у ванильного DroneManager,
|
||
/// откуда она изначально и взята).
|
||
///
|
||
/// РАНЬШЕ ЗДЕСЬ БЫЛ ПРОХОД, И ЭТО БЫЛА ОШИБКА (исправлено 18.09, см. MaxDropBelowOwner):
|
||
/// проход задуман для препятствия в шаге, а не для перелёта через тридцать метров
|
||
/// незнакомой местности с отключёнными столкновениями.</summary>
|
||
public const float LeashDistance = 32f;
|
||
|
||
/// <summary>Аварийный предел по высоте: если питомец оказался ниже хозяина больше чем на
|
||
/// столько, разбираться уже некогда - прыжок к хозяину.
|
||
///
|
||
/// ЗАЧЕМ. 18.09 крыса пропала в игре, и лог сказал ровно что случилось:
|
||
/// WRN Entity [type=EntityZombieDog, name=necroRatSpirit, id=1459] fell off the world,
|
||
/// pos=(319.2, -0.4, 1367.2)
|
||
/// Высота -0.4: она провалилась под мир, а игра удаляет всё, что опустилось ниже нуля
|
||
/// (Entity.onUpdate: position.y < 0f -> MarkToUnload). Виноват IsStuck - он снимает
|
||
/// столкновения со ВСЕМ, включая пол, а гравитация никуда не девается: шаг перемещения
|
||
/// складывает hitMove с накопленным motion. Четыре секунды прохода над дыркой в полу -
|
||
/// и питомца нет.
|
||
///
|
||
/// Эта проверка работает независимо от прохода и ловит любое падение, а не только наше
|
||
/// собственное.</summary>
|
||
public const float MaxDropBelowOwner = 6f;
|
||
|
||
/// <summary>Ниже этой высоты вмешиваемся в любом случае: мир кончается на нуле, и пять
|
||
/// метров запаса нужны, чтобы успеть что-то сделать, а не констатировать.</summary>
|
||
public const float WorldFloorGuard = 5f;
|
||
|
||
public int OwnerEntityId = -1;
|
||
|
||
public Vector3 seekPos;
|
||
public int slotIndex;
|
||
public float noProgressTime;
|
||
public float lastDistSq;
|
||
public bool phasing;
|
||
public float phaseTime;
|
||
public int pathRecalculateTicks;
|
||
|
||
public override void Init(EntityAlive _theEntity)
|
||
{
|
||
base.Init(_theEntity);
|
||
// 3 - те же биты, что у EAIApproachSpot: это задача ПЕРЕМЕЩЕНИЯ, и она не должна
|
||
// исполняться одновременно с другой такой же (в первую очередь с погоней за целью).
|
||
MutexBits = 3;
|
||
executeDelay = 0.1f;
|
||
}
|
||
|
||
public EntityAlive Owner()
|
||
{
|
||
if (OwnerEntityId < 0 || theEntity == null || theEntity.world == null)
|
||
{
|
||
return null;
|
||
}
|
||
return theEntity.world.GetEntity(OwnerEntityId) as EntityAlive;
|
||
}
|
||
|
||
/// <summary>Точка для слота slotIndex: хозяин + поворот его собственного направления на
|
||
/// угол из SlotAngles, посаженный на опорный блок.</summary>
|
||
public Vector3 SlotPosition(EntityAlive _owner, int _slot)
|
||
{
|
||
return SlotPosition(_owner, _slot, theEntity.world, SlotDistance);
|
||
}
|
||
|
||
/// <summary>То же самое, но без экземпляра задачи - нужна PetFollowPatch.cs, который
|
||
/// спасает упавших питомцев во время боя, когда сама задача не исполняется. Там точный
|
||
/// радиус не важен (питомца просто возвращают к хозяину), поэтому берётся общий.</summary>
|
||
public static Vector3 SlotPosition(EntityAlive _owner, int _slot, World _world,
|
||
float _slotDistance = DefaultSlotDistance)
|
||
{
|
||
float angle = SlotAngles[_slot % SlotAngles.Length];
|
||
Vector3 dir = _owner.qrotation * (Quaternion.Euler(0f, angle, 0f) * Vector3.forward);
|
||
Vector3 raw = _owner.position + dir * _slotDistance;
|
||
return _world.FindSupportingBlockPos(raw);
|
||
}
|
||
|
||
public override bool CanExecute()
|
||
{
|
||
// Дерётся - не мешаем. Цель крысе даёт игрок (PetCommandPatch.cs), и пока она есть,
|
||
// место в строю подождёт.
|
||
if (theEntity.GetAttackTarget() != null)
|
||
{
|
||
return false;
|
||
}
|
||
if (theEntity.IsDead() || theEntity.IsSleeping)
|
||
{
|
||
return false;
|
||
}
|
||
EntityAlive owner = Owner();
|
||
if (owner == null)
|
||
{
|
||
return false;
|
||
}
|
||
seekPos = SlotPosition(owner, slotIndex);
|
||
return (seekPos - theEntity.position).sqrMagnitude > ArrivedDistSq;
|
||
}
|
||
|
||
public override void Start()
|
||
{
|
||
noProgressTime = 0f;
|
||
lastDistSq = float.MaxValue;
|
||
phasing = false;
|
||
phaseTime = 0f;
|
||
pathRecalculateTicks = 0;
|
||
}
|
||
|
||
public override bool Continue()
|
||
{
|
||
if (theEntity.GetAttackTarget() != null)
|
||
{
|
||
return false;
|
||
}
|
||
EntityAlive owner = Owner();
|
||
if (owner == null)
|
||
{
|
||
return false;
|
||
}
|
||
seekPos = SlotPosition(owner, slotIndex);
|
||
return (seekPos - theEntity.position).sqrMagnitude > ArrivedDistSq;
|
||
}
|
||
|
||
public override void Update()
|
||
{
|
||
EntityAlive owner = Owner();
|
||
if (owner == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// ПЕРВОЙ СТРОКОЙ, ДО ВСЕГО ОСТАЛЬНОГО: не падает ли питомец из мира. Проверка стоит
|
||
// здесь, а не внутри прохода, намеренно - провалиться можно и без нашего участия
|
||
// (дыра в полу POI, взорванный блок), а последствие одно и то же: сущность ниже нуля
|
||
// игра удаляет молча, и питомец просто исчезает.
|
||
if (theEntity.position.y < WorldFloorGuard ||
|
||
theEntity.position.y < owner.position.y - MaxDropBelowOwner)
|
||
{
|
||
SnapToOwner(owner, "падение (высота " + theEntity.position.y.ToCultureInvariantString("0.0") + ")");
|
||
return;
|
||
}
|
||
|
||
Vector3 lookPosition = owner.position;
|
||
lookPosition.y += 0.8f;
|
||
theEntity.SetLookPosition(lookPosition);
|
||
|
||
if (phasing)
|
||
{
|
||
UpdatePhase();
|
||
return;
|
||
}
|
||
|
||
float distSq = (seekPos - theEntity.position).sqrMagnitude;
|
||
bool ownerFarAway = (owner.position - theEntity.position).sqrMagnitude > LeashDistance * LeashDistance;
|
||
|
||
// Сокращаем расстояние - значит путь есть и он работает; счётчик простоя сбрасывается.
|
||
if (distSq < lastDistSq - 0.05f)
|
||
{
|
||
noProgressTime = 0f;
|
||
}
|
||
else
|
||
{
|
||
noProgressTime += executeDelay;
|
||
}
|
||
lastDistSq = distSq;
|
||
|
||
// ХОЗЯИН ДАЛЕКО - ЭТО НЕ ПОВОД ПРОХОДИТЬ СКВОЗЬ СТЕНЫ, и раньше было именно так.
|
||
// Проход задуман для препятствия В ШАГЕ, а не для перелёта через тридцать метров
|
||
// незнакомой местности с отключёнными столкновениями: ровно такой проход 18.09 и
|
||
// уронил крысу под мир. На дальней дистанции работает обычный прыжок поводком - тот
|
||
// самый приём, которым ванильный DroneManager возвращает дрона, и он безопасен,
|
||
// потому что точка назначения известна заранее и стоит на земле.
|
||
if (ownerFarAway)
|
||
{
|
||
SnapToOwner(owner, "хозяин дальше " + LeashDistance + " м");
|
||
return;
|
||
}
|
||
|
||
if (noProgressTime >= NoProgressSeconds)
|
||
{
|
||
noProgressTime = 0f;
|
||
lastDistSq = float.MaxValue;
|
||
if (slotIndex >= SlotAngles.Length - 1)
|
||
{
|
||
// Перепробованы все точки сектора - значит дело в препятствии: сквозь.
|
||
slotIndex = 0;
|
||
BeginPhase();
|
||
return;
|
||
}
|
||
// Следующая точка сектора - "зависит от препятствий".
|
||
slotIndex++;
|
||
seekPos = SlotPosition(owner, slotIndex);
|
||
pathRecalculateTicks = 0;
|
||
}
|
||
|
||
if (theEntity.navigator.getPath() != null)
|
||
{
|
||
theEntity.moveHelper.CalcIfUnreachablePos();
|
||
}
|
||
if (--pathRecalculateTicks <= 0)
|
||
{
|
||
UpdatePath();
|
||
}
|
||
}
|
||
|
||
public void UpdatePath()
|
||
{
|
||
if (PathFinderThread.Instance.IsCalculatingPath(theEntity.entityId))
|
||
{
|
||
return;
|
||
}
|
||
pathRecalculateTicks = 8 + GetRandom(6);
|
||
// canBreak: false - крыса не грызёт блоки, это её главное отличие от всех остальных
|
||
// питомцев мода (см. шапку файла).
|
||
theEntity.FindPath(seekPos, theEntity.GetMoveSpeedAggro(), false, this);
|
||
}
|
||
|
||
public void BeginPhase()
|
||
{
|
||
phasing = true;
|
||
phaseTime = 0f;
|
||
theEntity.moveHelper.Stop();
|
||
theEntity.IsStuck = true;
|
||
}
|
||
|
||
public void EndPhase()
|
||
{
|
||
phasing = false;
|
||
phaseTime = 0f;
|
||
theEntity.IsStuck = false;
|
||
// ОБЯЗАТЕЛЬНО: пока IsStuck был поднят, пола для крысы не существовало. Сажаем её на
|
||
// опорный блок, иначе она останется висеть или продолжит падать.
|
||
theEntity.SetPosition(theEntity.world.FindSupportingBlockPos(theEntity.position), true);
|
||
lastDistSq = float.MaxValue;
|
||
noProgressTime = 0f;
|
||
pathRecalculateTicks = 0;
|
||
}
|
||
|
||
public void UpdatePhase()
|
||
{
|
||
phaseTime += executeDelay;
|
||
|
||
// ГРАВИТАЦИЯ ГАСИТСЯ КАЖДЫЙ ТИК, и это главная правка 18.09. Пока IsStuck поднят,
|
||
// шаг перемещения складывает накопленный motion прямо с позицией, минуя контроллер
|
||
// и пол, - то есть питомец продолжает "падать" между нашими SetPosition, причём
|
||
// ускоряясь. Обнуление motion означает: во время прохода питомца двигает ТОЛЬКО
|
||
// этот метод и ничто больше.
|
||
theEntity.motion = Vector3.zero;
|
||
|
||
Vector3 next = Vector3.MoveTowards(theEntity.position, seekPos, PhaseSpeed * executeDelay);
|
||
|
||
// И отдельно - высота. Даже с обнулённым motion спускаться ниже цели незачем:
|
||
// seekPos уже посажен на опорный блок (FindSupportingBlockPos), так что всё, что
|
||
// ниже него, - это пол, сквозь который мы проходить не собирались.
|
||
if (next.y < seekPos.y)
|
||
{
|
||
next.y = seekPos.y;
|
||
}
|
||
|
||
theEntity.SetPosition(next, true);
|
||
if (phaseTime >= PhaseTimeout || (next - seekPos).sqrMagnitude <= ArrivedDistSq)
|
||
{
|
||
EndPhase();
|
||
}
|
||
}
|
||
|
||
/// <summary>Прыжок к хозяину: аварийный выход и он же дальний поводок. Точка - тот же
|
||
/// слот в секторе, посаженный на опорный блок, а не позиция хозяина в упор (иначе
|
||
/// коллайдеры сложатся и питомец подбросит игрока - эти грабли уже разобраны в
|
||
/// PetFollowPatch.cs).</summary>
|
||
public void SnapToOwner(EntityAlive _owner, string _why)
|
||
{
|
||
if (phasing)
|
||
{
|
||
phasing = false;
|
||
phaseTime = 0f;
|
||
}
|
||
theEntity.IsStuck = false;
|
||
theEntity.motion = Vector3.zero;
|
||
theEntity.moveHelper.Stop();
|
||
slotIndex = 0;
|
||
lastDistSq = float.MaxValue;
|
||
noProgressTime = 0f;
|
||
pathRecalculateTicks = 0;
|
||
|
||
Vector3 dest = SlotPosition(_owner, 0);
|
||
theEntity.SetPosition(dest, true);
|
||
Debug.Log("[NecromancerTome] NecroFollowOwnerTask: pet " + theEntity.entityId +
|
||
" snapped back to owner " + _owner.entityId + " - " + _why);
|
||
}
|
||
|
||
public override void Reset()
|
||
{
|
||
if (phasing)
|
||
{
|
||
EndPhase();
|
||
}
|
||
theEntity.IsStuck = false;
|
||
theEntity.moveHelper.Stop();
|
||
theEntity.SetLookPosition(Vector3.zero);
|
||
}
|
||
|
||
public override string ToString()
|
||
{
|
||
return string.Format("{0}, slot{1}{2} dist{3}", base.ToString(), slotIndex,
|
||
phasing ? " PHASING" : "",
|
||
(theEntity.position - seekPos).magnitude.ToCultureInvariantString());
|
||
}
|
||
}
|
||
}
|