diff --git a/HarmonySrc/Cinerarium.cs b/HarmonySrc/Cinerarium.cs
index ab2cf98..1a2f7af 100644
--- a/HarmonySrc/Cinerarium.cs
+++ b/HarmonySrc/Cinerarium.cs
@@ -153,29 +153,52 @@ namespace NecromancerTome
float _nextWork;
public bool Stuck;
+ /// Дело найдено пробным шагом, дух полетел; выполнить в _actAt (16.10: «делал действие не сразу»).
+ bool _pending;
+ float _actAt;
- /// Работа духа (CinerariumWork): раз в StepSeconds одно дело, только с черепом и только на сервере.
+ /// Работа духа (CinerariumWork): раз в StepSeconds одно дело, только с черепом и только на сервере.
+ /// Сначала пробный шаг (ничего не меняет) - куда лететь; само дело - когда дух долетел и машет руками. Если за это
+ /// время что-то поменялось, настоящий шаг сделает то, что нужно на тот момент.
void Work(World _world)
{
- if (!HasSkull || _world == null || !ConnectionManager.Instance.IsServer || Time.time < _nextWork)
+ if (!HasSkull || _world == null || !ConnectionManager.Instance.IsServer)
+ {
+ _pending = false;
+ return;
+ }
+ CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent() : null;
+ if (_pending)
+ {
+ if (Time.time < _actAt)
+ {
+ return;
+ }
+ _pending = false;
+ CinerariumWork.Step(_world, this, out bool _, false);
+ _nextWork = Time.time + CinerariumWork.StepSeconds;
+ return;
+ }
+ if (Time.time < _nextWork)
{
return;
}
_nextWork = Time.time + CinerariumWork.StepSeconds;
- Vector3i? at = CinerariumWork.Step(_world, this, out bool stuck);
+ Vector3i? at = CinerariumWork.Step(_world, this, out bool stuck, true);
if (stuck != Stuck)
{
Stuck = stuck;
Debug.Log("[NecromancerTome] Cinerarium: дух урны " + ToWorldPos() + (stuck ? " почернел - готовое класть некуда" : " ожил"));
}
- CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent() : null;
if (motion != null)
{
motion.SetStuck(Stuck && at == null);
- if (at.HasValue)
- {
- motion.Visit(World.blockToTransformPos(at.Value) - Origin.position);
- }
+ }
+ if (at.HasValue)
+ {
+ _pending = true;
+ _actAt = Time.time + (motion != null ? CinerariumGenieMotion.ActDelay : 0f);
+ motion?.Visit(World.blockToTransformPos(at.Value) - Origin.position);
}
}
diff --git a/HarmonySrc/CinerariumGenie.cs b/HarmonySrc/CinerariumGenie.cs
index 9f3c447..e7cb347 100644
--- a/HarmonySrc/CinerariumGenie.cs
+++ b/HarmonySrc/CinerariumGenie.cs
@@ -226,10 +226,20 @@ namespace NecromancerTome
{
float _yaw;
float _phase;
- /// Полёт к месту работы и обратно: точка в координатах модели урны и сколько осталось.
+ /// Полёт к месту работы и обратно (16.10: «возвращается задом», «делал действие не сразу, а хоть руками
+ /// пошевелив»): туда - лицом к месту, над местом - жест руками (дело делается в его середине, см. ActDelay),
+ /// обратно - развернувшись лицом к урне. Точка - в координатах модели урны; _t - время с начала полёта, -1 - дома.
Vector3 _visit;
- float _visitLeft;
- public const float VisitSeconds = 2.2f;
+ float _t = -1f;
+ public static float OutSeconds = 1.3f;
+ public static float WorkSeconds = 1.6f;
+ public static float BackSeconds = 1.3f;
+ /// Через сколько после начала полёта дело выполняется - в середине жеста.
+ public static float ActDelay => OutSeconds + WorkSeconds * 0.5f;
+ /// Жест: плечи вперёд до GestureFwd градусов, локти ходят на ±GestureWave вразнобой.
+ public static float GestureFwd = 55f;
+ public static float GestureWave = 30f;
+ bool _gesturing;
/// Почернел и замер (готовое класть некуда, решение 06.10).
bool _stuck;
/// Кости рук (плечо, предплечье, кисть по сторонам) и их поворот в позе покоя.
@@ -240,6 +250,11 @@ namespace NecromancerTome
/// Опустить руки из позы покоя: направление плеча и предплечья задаётся в осях модели (вперёд - +Z),
/// поворот - от текущего направления кости к нужному, так что оси самих костей значения не имеют.
public void Pose()
+ {
+ Pose(CinerariumGenie.ArmFwd, CinerariumGenie.Elbow, CinerariumGenie.Elbow);
+ }
+
+ void Pose(float _fwd, float _elbowL, float _elbowR)
{
if (_arm == null)
{
@@ -283,9 +298,9 @@ namespace NecromancerTome
continue;
}
float sign = Mathf.Sign(transform.InverseTransformPoint(up.position).x);
- float o = CinerariumGenie.ArmOut * Mathf.Deg2Rad, f = CinerariumGenie.ArmFwd * Mathf.Deg2Rad;
+ float o = CinerariumGenie.ArmOut * Mathf.Deg2Rad, f = _fwd * Mathf.Deg2Rad;
Vector3 upDir = new Vector3(sign * Mathf.Sin(o), -Mathf.Cos(o) * Mathf.Cos(f), Mathf.Sin(f)).normalized;
- Vector3 foreDir = Vector3.RotateTowards(upDir, Vector3.forward, CinerariumGenie.Elbow * Mathf.Deg2Rad, 0f);
+ Vector3 foreDir = Vector3.RotateTowards(upDir, Vector3.forward, (side == 0 ? _elbowL : _elbowR) * Mathf.Deg2Rad, 0f);
up.rotation = Quaternion.FromToRotation(fore.position - up.position, transform.TransformDirection(upDir)) * up.rotation;
fore.rotation = Quaternion.FromToRotation(hand.position - fore.position, transform.TransformDirection(foreDir)) * fore.rotation;
}
@@ -302,7 +317,7 @@ namespace NecromancerTome
return;
}
_visit = transform.parent.InverseTransformPoint(_worldUnity);
- _visitLeft = VisitSeconds;
+ _t = 0f;
}
public void SetStuck(bool _on)
@@ -359,21 +374,54 @@ namespace NecromancerTome
}
float yawTarget = _yaw + CinerariumGenie.TurnSpeed * Time.deltaTime;
Vector3 home = new Vector3(0f, CinerariumGenie.OffsetY, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
- if (_visitLeft > 0f)
+ if (_t >= 0f)
{
- // Туда и обратно по синусу: на полпути - над местом работы, лицом к нему.
- _visitLeft -= Time.deltaTime;
- float k = Mathf.Sin(Mathf.Clamp01(1f - _visitLeft / VisitSeconds) * Mathf.PI);
- Vector3 over = _visit + new Vector3(0f, CinerariumGenie.OffsetY + 0.6f, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
- transform.localPosition = Vector3.Lerp(home, over, k);
- Vector3 d = _visit - home;
- if (d.x * d.x + d.z * d.z > 0.01f)
+ _t += Time.deltaTime;
+ if (_t < OutSeconds + WorkSeconds + BackSeconds)
{
- yawTarget = Mathf.LerpAngle(_yaw, Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg, Time.deltaTime * 4f);
+ Vector3 over = _visit + new Vector3(0f, CinerariumGenie.OffsetY + 0.6f, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
+ Vector3 d = _visit - new Vector3(0f, CinerariumGenie.OffsetY, 0f);
+ bool far = d.x * d.x + d.z * d.z > 0.01f;
+ float toPlace = far ? Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg : _yaw;
+ float k, face, w = 0f;
+ if (_t < OutSeconds)
+ {
+ k = Mathf.SmoothStep(0f, 1f, _t / OutSeconds);
+ face = toPlace;
+ }
+ else if (_t < OutSeconds + WorkSeconds)
+ {
+ k = 1f;
+ face = toPlace;
+ w = Mathf.Sin((_t - OutSeconds) / WorkSeconds * Mathf.PI);
+ }
+ else
+ {
+ k = 1f - Mathf.SmoothStep(0f, 1f, (_t - OutSeconds - WorkSeconds) / BackSeconds);
+ face = far ? toPlace + 180f : _yaw; // назад - лицом к урне
+ }
+ _yaw = Mathf.MoveTowardsAngle(_yaw, face, 300f * Time.deltaTime) % 360f;
+ transform.localPosition = Vector3.Lerp(home, over, k);
+ transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
+ if (w > 0f)
+ {
+ float wave = Mathf.Sin(_t * Mathf.PI * 2f * 1.5f) * GestureWave * w;
+ Pose(Mathf.Lerp(CinerariumGenie.ArmFwd, GestureFwd, w), CinerariumGenie.Elbow + 15f * w + wave, CinerariumGenie.Elbow + 15f * w - wave);
+ _gesturing = true;
+ }
+ else if (_gesturing)
+ {
+ Pose();
+ _gesturing = false;
+ }
+ return;
+ }
+ _t = -1f;
+ if (_gesturing)
+ {
+ Pose();
+ _gesturing = false;
}
- _yaw = yawTarget % 360f;
- transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
- return;
}
_yaw = yawTarget % 360f;
float bob = CinerariumGenie.BobPeriod > 0.01f
diff --git a/HarmonySrc/CinerariumWork.cs b/HarmonySrc/CinerariumWork.cs
index 2fab8db..d659e4c 100644
--- a/HarmonySrc/CinerariumWork.cs
+++ b/HarmonySrc/CinerariumWork.cs
@@ -41,8 +41,25 @@ namespace NecromancerTome
static readonly HashSet _seen = new HashSet();
- /// Одно дело. Возвращает, где дух работал (для полёта модели), или null; _stuck - есть что забрать, но некуда.
- public static Vector3i? Step(World _world, TEFeatureCinerarium _urn, out bool _stuck)
+ /// Пробный шаг: найти дело и место, ничего не меняя.
+ static bool _dry;
+
+ /// Одно дело. Возвращает, где дух работал (для полёта модели), или null; _stuck - есть что забрать, но некуда.
+ /// _dryRun - только узнать, где будет дело (дух летит туда заранее), ничего не трогая.
+ public static Vector3i? Step(World _world, TEFeatureCinerarium _urn, out bool _stuck, bool _dryRun)
+ {
+ _dry = _dryRun;
+ try
+ {
+ return DoStep(_world, _urn, out _stuck);
+ }
+ finally
+ {
+ _dry = false;
+ }
+ }
+
+ static Vector3i? DoStep(World _world, TEFeatureCinerarium _urn, out bool _stuck)
{
_stuck = false;
Zone z = Scan(_world, _urn);
@@ -51,7 +68,10 @@ namespace NecromancerTome
{
if (Refill(c, c.FuelSlots, c.GetFuelTypes(), false, z) || Refill(c, c.CatalystSlots, c.GetCatalystTypes(), true, z))
{
- c.SetModified();
+ if (!_dry)
+ {
+ c.SetModified();
+ }
return z.Where[c];
}
}
@@ -62,8 +82,11 @@ namespace NecromancerTome
ItemStack[] items = c.Items;
if (Unload(items, z, ref blocked))
{
- c.Items = items;
- c.SetModified();
+ if (!_dry)
+ {
+ c.Items = items;
+ c.SetModified();
+ }
return z.Where[c];
}
}
@@ -72,7 +95,10 @@ namespace NecromancerTome
ItemStack[] output = w.Output;
if (Unload(output, z, ref blocked))
{
- w.Output = output;
+ if (!_dry)
+ {
+ w.Output = output;
+ }
return z.Where[w];
}
}
@@ -151,6 +177,10 @@ namespace NecromancerTome
_blocked = true;
return false;
}
+ if (_dry)
+ {
+ return true;
+ }
_world.SetBlockRPC(_p, BlockValue.Air);
if (!replant.isair)
{
@@ -232,6 +262,10 @@ namespace NecromancerTome
{
continue;
}
+ if (_dry)
+ {
+ return p;
+ }
Take(pick.src, pick.slot, 1);
_world.SetBlockRPC(p, bv);
Debug.Log("[NecromancerTome] Cinerarium: дух посадил " + bv.Block.GetBlockName() + " в " + p);
@@ -351,6 +385,10 @@ namespace NecromancerTome
{
continue;
}
+ if (_dry)
+ {
+ return true;
+ }
if (want == null)
{
_slots[i] = new ItemStack(s.itemValue.Clone(), take);
@@ -382,6 +420,15 @@ namespace NecromancerTome
{
continue;
}
+ if (_dry)
+ {
+ if (HasRoom(s, _z))
+ {
+ return true;
+ }
+ _blocked = true;
+ continue;
+ }
int before = s.count;
// Сначала ящики зоны (к таким же, потом в пустое), они полны - тогда сумка урны (решение 06.10).
Place(s, _z.Boxes);