Урны: череп-ретранслятор, прилив прячет духов, меры нагрузки; проверки без мыши
- Череп-ретранслятор: нить урны проходит через черепа-декор (цепочка), каждый пролёт до 15 м; черепа хранятся у нити (запись урны v5, старые - без черепов). ПКМ ножом по черепу - провести нить, ЛКМ - снять нити через него; убрали череп - нить снимается. - Прилив: ночью кровавой луны духи всех урн исчезают, работа стоит, утром - сама. - Нагрузка: общий бюджет дел духов на кадр (3), анимируются только 16 ближайших духов; лимит урн сборщика смягчён - одна на 200 убийств (было 500), тексты на 13 языках. - Описание логиста (кадило, два вида, маленький дух, мигание), черепа-декора (передаёт нить), подсказки ножа - на 13 языках. - Разработка: necrogod, necrofps [uncap], necrotide, necroshot серией, necroclick по клетке, necrobox fill, necrourn с черепами на пути. Проверено в игре (test8): мигание, нить 28 м через череп и её разрыв, прилив, 63 урны сборщика: 13.0 мс на кадр против 11.8 мс без работы урн. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6693d96d5e
commit
85d8036e81
File diff suppressed because one or more lines are too long
+231
-38
@@ -68,8 +68,10 @@ namespace NecromancerTome
|
||||
public const int HalfWidth = 2;
|
||||
public const int Below = 1;
|
||||
public const int Above = 4;
|
||||
public const float KillsPerUrn = 500f;
|
||||
const byte SaveVersion = 4; // 2 - нити, 3 - фильтр логиста (16.10), 4 - нити рабочего «куда» и образец (18.10)
|
||||
/// <summary>Убийств на одну урну сборщика. 17.10: «лимит урн - ослабить/убрать» - нагрузку теперь держит общий бюджет
|
||||
/// шагов (CinerariumLoad), лимит остался мягким игровым порогом: было 500, стало 200.</summary>
|
||||
public const float KillsPerUrn = 200f;
|
||||
const byte SaveVersion = 5; // 2 - нити, 3 - фильтр логиста (16.10), 4 - нити рабочего «куда» и образец (18.10), 5 - черепа-ретрансляторы (18.10)
|
||||
|
||||
public bool HasSkull;
|
||||
public bool ZoneShown;
|
||||
@@ -80,6 +82,13 @@ namespace NecromancerTome
|
||||
public bool HasThreads => HasThreadTo || ThreadFrom.Count > 0 || ThreadOut.Count > 0;
|
||||
/// <summary>Нити рабочего «от урны»: станции (где изготавливать) и ящики (куда класть готовое), главные клетки блоков.</summary>
|
||||
public readonly List<Vector3i> ThreadOut = new List<Vector3i>();
|
||||
/// <summary>Черепа-ретрансляторы на пути каждой нити (18.10: «сам череп повысить из простого предмета, до промежуточного
|
||||
/// ретранслятора... если понадобится протащить коммуникации дальше»; «Цепочка из нескольких»): клетки черепов-декора по
|
||||
/// порядку ОТ УРНЫ к концу нити. Списки идут вровень с ThreadFrom / ThreadOut, ToVia - у нити «куда». Пусто - нить прямая.
|
||||
/// Каждый пролёт - не длиннее CinerariumThreads.MaxLength. Убрали череп - нить через него снимается.</summary>
|
||||
public readonly List<List<Vector3i>> FromVia = new List<List<Vector3i>>();
|
||||
public readonly List<List<Vector3i>> OutVia = new List<List<Vector3i>>();
|
||||
public readonly List<Vector3i> ToVia = new List<Vector3i>();
|
||||
/// <summary>Образец рабочего: что изготавливать; число в стопке - сколько держать в запасе (ящики «куда» и выход станций).
|
||||
/// Лежит в «Содержимом» урны второй ячейкой.</summary>
|
||||
public ItemStack Sample = ItemStack.Empty.Clone();
|
||||
@@ -138,6 +147,10 @@ namespace NecromancerTome
|
||||
ThreadTo = other.ThreadTo;
|
||||
ThreadOut.Clear();
|
||||
ThreadOut.AddRange(other.ThreadOut);
|
||||
CopyVia(other.FromVia, FromVia);
|
||||
CopyVia(other.OutVia, OutVia);
|
||||
ToVia.Clear();
|
||||
ToVia.AddRange(other.ToVia);
|
||||
Sample = other.Sample.Clone();
|
||||
GroupMask = other.GroupMask;
|
||||
_threadsDirty = true;
|
||||
@@ -227,6 +240,61 @@ namespace NecromancerTome
|
||||
}
|
||||
CinerariumWriteHelper.WriteInts(_bw, o.ToArray());
|
||||
(Sample ?? ItemStack.Empty).Clone().Write(_bw);
|
||||
// v5: черепа на пути нитей - по нити: число черепов и их клетки; порядок нитей - как выше (откуда, куда, от урны).
|
||||
List<int> via = new List<int>();
|
||||
AlignVia();
|
||||
foreach (List<Vector3i> l in FromVia)
|
||||
{
|
||||
AppendVia(via, l);
|
||||
}
|
||||
AppendVia(via, ToVia);
|
||||
foreach (List<Vector3i> l in OutVia)
|
||||
{
|
||||
AppendVia(via, l);
|
||||
}
|
||||
CinerariumWriteHelper.WriteInts(_bw, via.ToArray());
|
||||
}
|
||||
|
||||
static void AppendVia(List<int> _dst, List<Vector3i> _l)
|
||||
{
|
||||
_dst.Add(_l.Count);
|
||||
foreach (Vector3i p in _l)
|
||||
{
|
||||
_dst.Add(p.x); _dst.Add(p.y); _dst.Add(p.z);
|
||||
}
|
||||
}
|
||||
|
||||
static List<Vector3i> TakeVia(int[] _v, ref int _k)
|
||||
{
|
||||
List<Vector3i> l = new List<Vector3i>();
|
||||
int cnt = _k < _v.Length ? _v[_k++] : 0;
|
||||
for (int i = 0; i < cnt && _k + 2 < _v.Length; i++, _k += 3)
|
||||
{
|
||||
l.Add(new Vector3i(_v[_k], _v[_k + 1], _v[_k + 2]));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static void CopyVia(List<List<Vector3i>> _src, List<List<Vector3i>> _dst)
|
||||
{
|
||||
_dst.Clear();
|
||||
foreach (List<Vector3i> l in _src)
|
||||
{
|
||||
_dst.Add(new List<Vector3i>(l));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Списки черепов - вровень с нитями (старые записи без черепов, правки списков нитей).</summary>
|
||||
void AlignVia()
|
||||
{
|
||||
while (FromVia.Count < ThreadFrom.Count) FromVia.Add(new List<Vector3i>());
|
||||
while (FromVia.Count > ThreadFrom.Count) FromVia.RemoveAt(FromVia.Count - 1);
|
||||
while (OutVia.Count < ThreadOut.Count) OutVia.Add(new List<Vector3i>());
|
||||
while (OutVia.Count > ThreadOut.Count) OutVia.RemoveAt(OutVia.Count - 1);
|
||||
if (!HasThreadTo)
|
||||
{
|
||||
ToVia.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Read(PooledBinaryReader _br, StreamModeRead _eStreamMode)
|
||||
@@ -298,6 +366,29 @@ namespace NecromancerTome
|
||||
}
|
||||
Sample = ItemStack.Empty.Clone().Read(_br);
|
||||
}
|
||||
FromVia.Clear();
|
||||
OutVia.Clear();
|
||||
ToVia.Clear();
|
||||
if (version >= 5)
|
||||
{
|
||||
int n = _br.ReadInt32();
|
||||
int[] v = new int[n];
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
v[i] = _br.ReadInt32();
|
||||
}
|
||||
int k = 0;
|
||||
for (int i = 0; i < ThreadFrom.Count; i++)
|
||||
{
|
||||
FromVia.Add(TakeVia(v, ref k));
|
||||
}
|
||||
ToVia.AddRange(TakeVia(v, ref k));
|
||||
for (int i = 0; i < ThreadOut.Count; i++)
|
||||
{
|
||||
OutVia.Add(TakeVia(v, ref k));
|
||||
}
|
||||
}
|
||||
AlignVia();
|
||||
_threadsDirty = true;
|
||||
if (version > SaveVersion)
|
||||
{
|
||||
@@ -337,7 +428,10 @@ namespace NecromancerTome
|
||||
}
|
||||
SyncGenie(false);
|
||||
SyncThreads(_world);
|
||||
Work(_world);
|
||||
if (!CinerariumLoad.Tide)
|
||||
{
|
||||
Work(_world);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
@@ -348,39 +442,54 @@ namespace NecromancerTome
|
||||
GameObject _threadView;
|
||||
float _nextThreadCheck;
|
||||
|
||||
public bool AddThreadFrom(Vector3i _box)
|
||||
/// <summary>_via - черепа-ретрансляторы по порядку от урны к ящику (null - прямая нить). Нить к тому же ящику -
|
||||
/// заменяет прежнюю (новый путь).</summary>
|
||||
public bool AddThreadFrom(Vector3i _box, List<Vector3i> _via = null)
|
||||
{
|
||||
if (ThreadFrom.Contains(_box))
|
||||
AlignVia();
|
||||
int i = ThreadFrom.IndexOf(_box);
|
||||
if (i < 0)
|
||||
{
|
||||
return true;
|
||||
if (ThreadFrom.Count >= CinerariumThreads.MaxFrom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ThreadFrom.Add(_box);
|
||||
FromVia.Add(new List<Vector3i>());
|
||||
i = ThreadFrom.Count - 1;
|
||||
}
|
||||
if (ThreadFrom.Count >= CinerariumThreads.MaxFrom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ThreadFrom.Add(_box);
|
||||
FromVia[i] = _via != null ? new List<Vector3i>(_via) : new List<Vector3i>();
|
||||
ThreadsChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetThreadTo(Vector3i _box)
|
||||
public void SetThreadTo(Vector3i _box, List<Vector3i> _via = null)
|
||||
{
|
||||
HasThreadTo = true;
|
||||
ThreadTo = _box;
|
||||
ToVia.Clear();
|
||||
if (_via != null)
|
||||
{
|
||||
ToVia.AddRange(_via);
|
||||
}
|
||||
ThreadsChanged();
|
||||
}
|
||||
|
||||
public bool AddThreadOut(Vector3i _target)
|
||||
public bool AddThreadOut(Vector3i _target, List<Vector3i> _via = null)
|
||||
{
|
||||
if (ThreadOut.Contains(_target))
|
||||
AlignVia();
|
||||
int i = ThreadOut.IndexOf(_target);
|
||||
if (i < 0)
|
||||
{
|
||||
return true;
|
||||
if (ThreadOut.Count >= CinerariumThreads.MaxFrom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ThreadOut.Add(_target);
|
||||
OutVia.Add(new List<Vector3i>());
|
||||
i = ThreadOut.Count - 1;
|
||||
}
|
||||
if (ThreadOut.Count >= CinerariumThreads.MaxFrom)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ThreadOut.Add(_target);
|
||||
OutVia[i] = _via != null ? new List<Vector3i>(_via) : new List<Vector3i>();
|
||||
ThreadsChanged();
|
||||
return true;
|
||||
}
|
||||
@@ -390,6 +499,9 @@ namespace NecromancerTome
|
||||
int n = ThreadFrom.Count + (HasThreadTo ? 1 : 0) + ThreadOut.Count;
|
||||
ThreadFrom.Clear();
|
||||
ThreadOut.Clear();
|
||||
FromVia.Clear();
|
||||
OutVia.Clear();
|
||||
ToVia.Clear();
|
||||
HasThreadTo = false;
|
||||
if (n > 0)
|
||||
{
|
||||
@@ -398,14 +510,41 @@ namespace NecromancerTome
|
||||
return n;
|
||||
}
|
||||
|
||||
public int RemoveThreadsTo(Vector3i _box)
|
||||
/// <summary>Снять нити, у которых конец (_end) или череп на пути (_via) - в клетке _p.</summary>
|
||||
int RemoveWhere(Vector3i _p, bool _end, bool _via)
|
||||
{
|
||||
int n = ThreadFrom.RemoveAll(p => p == _box) + ThreadOut.RemoveAll(p => p == _box);
|
||||
if (HasThreadTo && ThreadTo == _box)
|
||||
AlignVia();
|
||||
int n = 0;
|
||||
for (int i = ThreadFrom.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_end && ThreadFrom[i] == _p || _via && FromVia[i].Contains(_p))
|
||||
{
|
||||
ThreadFrom.RemoveAt(i);
|
||||
FromVia.RemoveAt(i);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
for (int i = ThreadOut.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_end && ThreadOut[i] == _p || _via && OutVia[i].Contains(_p))
|
||||
{
|
||||
ThreadOut.RemoveAt(i);
|
||||
OutVia.RemoveAt(i);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (HasThreadTo && (_end && ThreadTo == _p || _via && ToVia.Contains(_p)))
|
||||
{
|
||||
HasThreadTo = false;
|
||||
ToVia.Clear();
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public int RemoveThreadsTo(Vector3i _box)
|
||||
{
|
||||
int n = RemoveWhere(_box, true, false);
|
||||
if (n > 0)
|
||||
{
|
||||
ThreadsChanged();
|
||||
@@ -413,6 +552,53 @@ namespace NecromancerTome
|
||||
return n;
|
||||
}
|
||||
|
||||
/// <summary>Снять нити, идущие через череп _skull (ЛКМ ножом по черепу; череп убрали).</summary>
|
||||
public int RemoveThreadsVia(Vector3i _skull)
|
||||
{
|
||||
int n = RemoveWhere(_skull, false, true);
|
||||
if (n > 0)
|
||||
{
|
||||
ThreadsChanged();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/// <summary>Все черепы на нитях урны в клетках, где их больше нет (чанк загружен), - нити через них снимаются.</summary>
|
||||
bool DropBrokenRelays(World _world)
|
||||
{
|
||||
AlignVia();
|
||||
bool gone = false;
|
||||
foreach (List<Vector3i> l in AllVia())
|
||||
{
|
||||
foreach (Vector3i p in l)
|
||||
{
|
||||
if (_world.GetChunkFromWorldPos(p) != null && !CinerariumThreads.IsRelay(_world.GetBlock(p).Block))
|
||||
{
|
||||
if (RemoveWhere(p, false, true) > 0)
|
||||
{
|
||||
Debug.Log("[NecromancerTome] Cinerarium: череп-ретранслятор " + p + " убран - нить урны " + ToWorldPos() + " порвалась");
|
||||
gone = true;
|
||||
}
|
||||
return gone; // списки изменились - остальное в следующей проверке
|
||||
}
|
||||
}
|
||||
}
|
||||
return gone;
|
||||
}
|
||||
|
||||
IEnumerable<List<Vector3i>> AllVia()
|
||||
{
|
||||
foreach (List<Vector3i> l in FromVia)
|
||||
{
|
||||
yield return l;
|
||||
}
|
||||
yield return ToVia;
|
||||
foreach (List<Vector3i> l in OutVia)
|
||||
{
|
||||
yield return l;
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadsChanged()
|
||||
{
|
||||
_threadsDirty = true;
|
||||
@@ -427,22 +613,19 @@ namespace NecromancerTome
|
||||
if (_world != null && ConnectionManager.Instance.IsServer && HasThreads && !IsZoneless && _logistKnown)
|
||||
{
|
||||
Debug.Log("[NecromancerTome] Cinerarium: нити у обычной урны " + ToWorldPos() + " сняты - нити только у логиста и рабочего");
|
||||
ThreadFrom.Clear();
|
||||
ThreadOut.Clear();
|
||||
HasThreadTo = false;
|
||||
ThreadsChanged();
|
||||
ClearThreads();
|
||||
}
|
||||
if (_world != null && ConnectionManager.Instance.IsServer && HasThreads && Time.time >= _nextThreadCheck)
|
||||
{
|
||||
_nextThreadCheck = Time.time + 2f;
|
||||
bool gone = false;
|
||||
bool gone = DropBrokenRelays(_world);
|
||||
foreach (Vector3i p in ThreadFrom.ToArray())
|
||||
{
|
||||
if (!CinerariumThreads.Resolve(_world, p, out Vector3i _, out TEFeatureCinerarium u) || u != null)
|
||||
{
|
||||
if (_world.GetChunkFromWorldPos(p) != null) // чанк не загружен - не снимать
|
||||
{
|
||||
ThreadFrom.Remove(p);
|
||||
RemoveWhere(p, true, false);
|
||||
gone = true;
|
||||
}
|
||||
}
|
||||
@@ -451,6 +634,7 @@ namespace NecromancerTome
|
||||
_world.GetChunkFromWorldPos(ThreadTo) != null)
|
||||
{
|
||||
HasThreadTo = false;
|
||||
ToVia.Clear();
|
||||
gone = true;
|
||||
}
|
||||
foreach (Vector3i p in ThreadOut.ToArray())
|
||||
@@ -458,7 +642,7 @@ namespace NecromancerTome
|
||||
bool box = CinerariumThreads.Resolve(_world, p, out Vector3i _, out TEFeatureCinerarium u3) && u3 == null;
|
||||
if (!box && CinerariumThreads.StationAt(_world, p) == null && _world.GetChunkFromWorldPos(p) != null)
|
||||
{
|
||||
ThreadOut.Remove(p);
|
||||
RemoveWhere(p, true, false);
|
||||
gone = true;
|
||||
}
|
||||
}
|
||||
@@ -490,18 +674,19 @@ namespace NecromancerTome
|
||||
_threadView.AddComponent<CinerariumThreadView>();
|
||||
}
|
||||
Vector3i me = ToWorldPos();
|
||||
AlignVia();
|
||||
List<(Vector3 a, Vector3 b)> pairs = new List<(Vector3, Vector3)>();
|
||||
foreach (Vector3i p in ThreadFrom)
|
||||
for (int i = 0; i < ThreadFrom.Count; i++)
|
||||
{
|
||||
pairs.Add((CinerariumThreads.BoxPoint(p, me), CinerariumThreads.UrnPoint(me)));
|
||||
CinerariumThreads.AddPath(pairs, me, FromVia[i], ThreadFrom[i], true);
|
||||
}
|
||||
if (HasThreadTo)
|
||||
{
|
||||
pairs.Add((CinerariumThreads.UrnPoint(me), CinerariumThreads.BoxPoint(ThreadTo, me)));
|
||||
CinerariumThreads.AddPath(pairs, me, ToVia, ThreadTo, false);
|
||||
}
|
||||
foreach (Vector3i p in ThreadOut)
|
||||
for (int i = 0; i < ThreadOut.Count; i++)
|
||||
{
|
||||
pairs.Add((CinerariumThreads.UrnPoint(me), CinerariumThreads.BoxPoint(p, me)));
|
||||
CinerariumThreads.AddPath(pairs, me, OutVia[i], ThreadOut[i], false);
|
||||
}
|
||||
_threadView.GetComponent<CinerariumThreadView>().Build(pairs);
|
||||
}
|
||||
@@ -543,9 +728,9 @@ namespace NecromancerTome
|
||||
_nextWork = Time.time + CinerariumWork.StepSeconds;
|
||||
return;
|
||||
}
|
||||
if (Time.time < _nextWork)
|
||||
if (Time.time < _nextWork || !CinerariumLoad.TryTakeStep())
|
||||
{
|
||||
return;
|
||||
return; // не время или бюджет кадра на дела духов выбран - в следующем кадре
|
||||
}
|
||||
_nextWork = Time.time + CinerariumWork.StepSeconds;
|
||||
Vector3i? at = CinerariumWork.Step(_world, this, out bool stuck, true);
|
||||
@@ -691,7 +876,15 @@ namespace NecromancerTome
|
||||
public void SyncGenie(bool _sound)
|
||||
{
|
||||
SyncGlow();
|
||||
if (HasSkull && _genie == null)
|
||||
// Прилив (17.10: «думаю всех некротехнологов нужно прятать во время прилива... В прилив работа тоже стоит. Урны светятся
|
||||
// как с останками, но духи исчезают»): плотность некроэнергии в воздухе сравнялась с плотностью духа - вне урны он не
|
||||
// держит форму. Свечение урны - по черепу, как обычно; утром дух появляется сам.
|
||||
bool tide = CinerariumLoad.Tide;
|
||||
if (tide && _genie != null)
|
||||
{
|
||||
RemoveGenie(false);
|
||||
}
|
||||
if (HasSkull && _genie == null && !tide)
|
||||
{
|
||||
int logist = 0;
|
||||
if (IsLogist)
|
||||
|
||||
@@ -540,7 +540,8 @@ namespace NecromancerTome
|
||||
if (Time.time >= _nextSleepCheck)
|
||||
{
|
||||
_nextSleepCheck = Time.time + 0.5f;
|
||||
bool asleep = CinerariumGenie.PlayerFar(transform.position);
|
||||
// Уснул - игрок далеко или дух не среди ближайших (CinerariumLoad.AnimateNearest): замирает дома, работа идёт.
|
||||
bool asleep = CinerariumGenie.PlayerFar(transform.position) || !CinerariumLoad.Animated(this);
|
||||
if (asleep && !_asleep)
|
||||
{
|
||||
_t = -1f; // полёт прерван - дело всё равно выполнится
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Меры нагрузки урн (решение 17.10: «общий бюджет шагов работы духов за кадр (очередь по кругу - нагрузка постоянна при
|
||||
/// любом числе урн); полная анимация только ближайших ~15-20 духов; замер 100 урн»).
|
||||
/// Бюджет: дел (пробный шаг + настоящий) за кадр - не больше StepsPerFrame на все урны сейва; урна, которой не досталось,
|
||||
/// пробует в следующем кадре. Урна делает дело раз в CinerariumWork.StepSeconds, поэтому очередь сама идёт по кругу.
|
||||
/// Анимация: двигаются (полёт, жесты, покачивание) только AnimateNearest ближайших к игроку духов, остальные замирают дома,
|
||||
/// как уснувшие (CinerariumGenieMotion); работа у всех идёт.
|
||||
/// Прилив (решение 17.10, «Прилив прячет некротехнологов»): ночью кровавой луны духи всех урн уходят в урны и работа стоит -
|
||||
/// признак берётся раз за кадр.
|
||||
/// </summary>
|
||||
public static class CinerariumLoad
|
||||
{
|
||||
public static int StepsPerFrame = 3;
|
||||
public static int AnimateNearest = 16;
|
||||
|
||||
static int _frame = -1;
|
||||
static int _used;
|
||||
static bool _tide;
|
||||
|
||||
static void NewFrame()
|
||||
{
|
||||
if (Time.frameCount != _frame)
|
||||
{
|
||||
_frame = Time.frameCount;
|
||||
_used = 0;
|
||||
_tide = SnakeMode.IsBloodTide();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Взять одно дело из бюджета кадра.</summary>
|
||||
public static bool TryTakeStep()
|
||||
{
|
||||
NewFrame();
|
||||
if (_used >= StepsPerFrame)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_used++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Идёт ли кровавый прилив (раз за кадр).</summary>
|
||||
public static bool Tide
|
||||
{
|
||||
get
|
||||
{
|
||||
NewFrame();
|
||||
return _tide;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Ближайшие духи.
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
static float _nextRank;
|
||||
static readonly HashSet<CinerariumGenieMotion> _animated = new HashSet<CinerariumGenieMotion>();
|
||||
static readonly List<CinerariumGenieMotion> _sorted = new List<CinerariumGenieMotion>();
|
||||
|
||||
/// <summary>Двигается ли дух: среди AnimateNearest ближайших к игроку (пересчёт раз в 0.5 с, без выделений памяти).</summary>
|
||||
public static bool Animated(CinerariumGenieMotion _m)
|
||||
{
|
||||
if (Time.time >= _nextRank)
|
||||
{
|
||||
_nextRank = Time.time + 0.5f;
|
||||
Rank();
|
||||
}
|
||||
return _animated.Contains(_m);
|
||||
}
|
||||
|
||||
static void Rank()
|
||||
{
|
||||
_animated.Clear();
|
||||
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||
if (pl == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Vector3 me = pl.GetPosition() - Origin.position;
|
||||
_sorted.Clear();
|
||||
_sorted.AddRange(CinerariumGenie.Live);
|
||||
if (_sorted.Count > AnimateNearest)
|
||||
{
|
||||
_sorted.Sort((a, b) => (a.transform.position - me).sqrMagnitude.CompareTo((b.transform.position - me).sqrMagnitude));
|
||||
}
|
||||
for (int i = 0; i < _sorted.Count && i < AnimateNearest; i++)
|
||||
{
|
||||
_animated.Add(_sorted[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>necrofps [секунд] - средний и худший кадр за время замера (по умолчанию 5 с), число духов и урн; ответ - строкой
|
||||
/// почтового ящика в лог. Для замера нагрузки урн (17.10: «замер 100 урн»).</summary>
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroFps : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrofps" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): средний и худший кадр за N секунд";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
float sec = _params.Count > 0 ? float.Parse(_params[0], System.Globalization.CultureInfo.InvariantCulture) : 5f;
|
||||
bool uncap = _params.Count > 1 && _params[1] == "uncap";
|
||||
SdtdConsole.Instance.Output("necrofps: замер " + sec + " с" + (uncap ? " без ограничения кадров" : ""));
|
||||
ThreadManager.StartCoroutine(Measure(sec, uncap));
|
||||
}
|
||||
|
||||
/// <summary>_uncap - на время замера снять вертикальную синхронизацию и предел кадров: при 60 fps по синхронизации
|
||||
/// запас не виден (18.10, замер 100 урн).</summary>
|
||||
static IEnumerator Measure(float _sec, bool _uncap)
|
||||
{
|
||||
int vs = QualitySettings.vSyncCount, fr = Application.targetFrameRate;
|
||||
if (_uncap)
|
||||
{
|
||||
QualitySettings.vSyncCount = 0;
|
||||
Application.targetFrameRate = -1;
|
||||
yield return new WaitForSecondsRealtime(0.5f);
|
||||
}
|
||||
float end = Time.realtimeSinceStartup + _sec;
|
||||
int frames = 0;
|
||||
float worst = 0f, sum = 0f;
|
||||
long gc0 = System.GC.CollectionCount(0);
|
||||
while (Time.realtimeSinceStartup < end)
|
||||
{
|
||||
yield return null;
|
||||
frames++;
|
||||
sum += Time.unscaledDeltaTime;
|
||||
worst = Mathf.Max(worst, Time.unscaledDeltaTime);
|
||||
}
|
||||
if (_uncap)
|
||||
{
|
||||
QualitySettings.vSyncCount = vs;
|
||||
Application.targetFrameRate = fr;
|
||||
}
|
||||
float avg = frames > 0 ? sum / frames * 1000f : 0f;
|
||||
Debug.Log("[NecromancerTome] mailbox: necrofps: кадров " + frames + ", средний " + avg.ToString("F1") + " мс (" + (avg > 0 ? 1000f / avg : 0f).ToString("F0") +
|
||||
" fps), худший " + (worst * 1000f).ToString("F1") + " мс, сборок мусора " + (System.GC.CollectionCount(0) - gc0) +
|
||||
", духов " + CinerariumGenie.Live.Count + ", урн в сейве " + CinerariumRegistry.All().Count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>necrotide on|off|auto - прилив для проверок без ожидания ночи кровавой луны (SnakeMode.ForceTide).</summary>
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroTide : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrotide" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): прилив on|off|auto";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
string v = _params.Count > 0 ? _params[0] : "";
|
||||
SnakeMode.ForceTide = v == "on" ? true : v == "off" ? (bool?)false : null;
|
||||
SdtdConsole.Instance.Output("necrotide: " + (SnakeMode.ForceTide.HasValue ? (SnakeMode.ForceTide.Value ? "прилив" : "нет прилива") : "по часам") +
|
||||
", сейчас " + (SnakeMode.IsBloodTide() ? "прилив" : "не прилив"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
-10
@@ -25,6 +25,10 @@ namespace NecromancerTome
|
||||
/// Урна рабочего (18.10): нити ящик -> урна - откуда брать ингредиенты и топливо, урна -> станция - где изготавливать,
|
||||
/// урна -> ящик - куда класть готовое; станций и ящиков сколько угодно (до MaxFrom в каждую сторону). Нить между станцией
|
||||
/// и урной рабочего - всегда «от урны», в каком бы порядке её ни тянули.
|
||||
///
|
||||
/// Череп-ретранслятор (18.10): череп-декор (блок necroSkullDecorCenter/Back) в середине нити. Начал нить у ящика или урны,
|
||||
/// ПКМ по черепу - нить проходит через него, дальше - следующий череп или конец; каждый пролёт - до MaxLength. Черепа
|
||||
/// хранятся у нити в урне (TEFeatureCinerarium.FromVia/ToVia/OutVia), ЛКМ по черепу - снять все нити через него.
|
||||
/// </summary>
|
||||
public static class CinerariumThreads
|
||||
{
|
||||
@@ -39,6 +43,13 @@ namespace NecromancerTome
|
||||
public static readonly Color SparkColor = new Color(0.8f, 0.95f, 1f, 1f);
|
||||
|
||||
public static bool IsKnife(ItemClass _i) => _i != null && (_i.Name == KnifeName || _i.Name == ThreadKnifeName);
|
||||
|
||||
/// <summary>Череп-ретранслятор - череп-декор в мире (оба вида и старый блок-помощник).</summary>
|
||||
public static bool IsRelay(Block _b)
|
||||
{
|
||||
string n = _b?.GetBlockName();
|
||||
return n == SkullDecorPlace.CenterBlock || n == SkullDecorPlace.BackBlock || n == "necroSkullDecor";
|
||||
}
|
||||
public static bool IsThreadKnife(ItemClass _i) => _i != null && _i.Name == ThreadKnifeName;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
@@ -72,13 +83,19 @@ namespace NecromancerTome
|
||||
static bool _startUrn;
|
||||
static bool _startStation;
|
||||
static GameObject _preview;
|
||||
/// <summary>Черепа, через которые уже прошла тянущаяся нить, по порядку от начала.</summary>
|
||||
static readonly List<Vector3i> _via = new List<Vector3i>();
|
||||
|
||||
public static Vector3i? Start => _start;
|
||||
public static bool StartIsUrn => _startUrn;
|
||||
/// <summary>Откуда сейчас тянется нить: последний череп или начало.</summary>
|
||||
public static Vector3i? LastPoint => _via.Count > 0 ? _via[_via.Count - 1] : _start;
|
||||
public static bool LastIsRelay => _via.Count > 0;
|
||||
|
||||
public static void Cancel()
|
||||
{
|
||||
_start = null;
|
||||
_via.Clear();
|
||||
if (_preview != null)
|
||||
{
|
||||
Object.Destroy(_preview);
|
||||
@@ -127,9 +144,20 @@ namespace NecromancerTome
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool ok = Resolve(w, hi.hit.blockPos, out Vector3i owner, out TEFeatureCinerarium urn);
|
||||
ClickAt(_pl, w, hi.hit.blockPos, _connect);
|
||||
}
|
||||
|
||||
/// <summary>Щелчок ножом для нитей по клетке _pos (луч уже разобран; команда necroclick - напрямую).</summary>
|
||||
public static void ClickAt(EntityPlayerLocal _pl, World w, Vector3i _pos, bool _connect)
|
||||
{
|
||||
if (IsRelay(w.GetBlock(_pos).Block))
|
||||
{
|
||||
ClickRelay(_pl, w, _pos, _connect);
|
||||
return;
|
||||
}
|
||||
bool ok = Resolve(w, _pos, out Vector3i owner, out TEFeatureCinerarium urn);
|
||||
bool station = false;
|
||||
if (!ok && StationAt(w, hi.hit.blockPos, out Vector3i stOwner) != null)
|
||||
if (!ok && StationAt(w, _pos, out Vector3i stOwner) != null)
|
||||
{
|
||||
ok = true;
|
||||
station = true;
|
||||
@@ -174,12 +202,19 @@ namespace NecromancerTome
|
||||
Vector3i s = _start.Value;
|
||||
bool sUrn = _startUrn;
|
||||
bool sStation = _startStation;
|
||||
Vector3i last = LastPoint.Value;
|
||||
// Черепа - по порядку от урны: начали у урны - как шли, начали у ящика или станции - в обратном порядке.
|
||||
List<Vector3i> via = new List<Vector3i>(_via);
|
||||
Cancel();
|
||||
if (s == owner)
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadCancel"));
|
||||
return;
|
||||
}
|
||||
if (!sUrn)
|
||||
{
|
||||
via.Reverse();
|
||||
}
|
||||
// Станция - только с урной рабочего, нить всегда «от урны».
|
||||
if (sStation || station)
|
||||
{
|
||||
@@ -190,12 +225,12 @@ namespace NecromancerTome
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadNeedWorker"));
|
||||
return;
|
||||
}
|
||||
if (Vector3.Distance(s.ToVector3(), owner.ToVector3()) > MaxLength)
|
||||
if (Vector3.Distance(last.ToVector3(), owner.ToVector3()) > MaxLength)
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadTooFar"));
|
||||
return;
|
||||
}
|
||||
if (!wu.AddThreadOut(st))
|
||||
if (!wu.AddThreadOut(st, via))
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadLimit"));
|
||||
return;
|
||||
@@ -215,7 +250,7 @@ namespace NecromancerTome
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadNeedLogist"));
|
||||
return;
|
||||
}
|
||||
if (Vector3.Distance(s.ToVector3(), owner.ToVector3()) > MaxLength)
|
||||
if (Vector3.Distance(last.ToVector3(), owner.ToVector3()) > MaxLength)
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadTooFar"));
|
||||
return;
|
||||
@@ -230,7 +265,7 @@ namespace NecromancerTome
|
||||
if (u.IsWorker)
|
||||
{
|
||||
// У рабочего ящиков «куда» сколько угодно - туда готовое.
|
||||
if (!u.AddThreadOut(owner))
|
||||
if (!u.AddThreadOut(owner, via))
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadLimit"));
|
||||
return;
|
||||
@@ -239,13 +274,13 @@ namespace NecromancerTome
|
||||
}
|
||||
else
|
||||
{
|
||||
u.SetThreadTo(owner);
|
||||
u.SetThreadTo(owner, via);
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadTo"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!urn.AddThreadFrom(s))
|
||||
if (!urn.AddThreadFrom(s, via))
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadLimit"));
|
||||
return;
|
||||
@@ -255,6 +290,76 @@ namespace NecromancerTome
|
||||
_pl.PlayOneShot("wiretool_place");
|
||||
}
|
||||
|
||||
/// <summary>Щелчок ножом по черепу-ретранслятору: ПКМ при тянущейся нити - провести её через череп; ЛКМ - снять все нити
|
||||
/// через него (или бросить тянущуюся).</summary>
|
||||
static void ClickRelay(EntityPlayerLocal _pl, World _w, Vector3i _skull, bool _connect)
|
||||
{
|
||||
if (!_connect)
|
||||
{
|
||||
if (_start.HasValue)
|
||||
{
|
||||
Cancel();
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadCancel"));
|
||||
return;
|
||||
}
|
||||
int n = 0;
|
||||
foreach (Vector3i p in CinerariumRegistry.All())
|
||||
{
|
||||
TEFeatureCinerarium u = UrnAt(_w, p);
|
||||
if (u != null)
|
||||
{
|
||||
n += u.RemoveThreadsVia(_skull);
|
||||
}
|
||||
}
|
||||
if (n > 0)
|
||||
{
|
||||
_pl.PlayOneShot("wiretool_grab");
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadRemoved"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!_start.HasValue)
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadRelayFirst"));
|
||||
return;
|
||||
}
|
||||
if (_via.Contains(_skull))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (Vector3.Distance(LastPoint.Value.ToVector3(), _skull.ToVector3()) > MaxLength)
|
||||
{
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadTooFar"));
|
||||
return;
|
||||
}
|
||||
_via.Add(_skull);
|
||||
_pl.PlayOneShot("wiretool_place");
|
||||
GameManager.ShowTooltip(_pl, Localization.Get("necroThreadRelay"));
|
||||
}
|
||||
|
||||
/// <summary>Нить урны _urn к _end через черепа _via (по порядку от урны) - пролётами в _pairs, огоньки бегут по направлению
|
||||
/// нити: _toUrn - от конца к урне (нить «откуда»), иначе от урны к концу.</summary>
|
||||
public static void AddPath(List<(Vector3 a, Vector3 b)> _pairs, Vector3i _urn, List<Vector3i> _via, Vector3i _end, bool _toUrn)
|
||||
{
|
||||
List<Vector3> pts = new List<Vector3> { UrnPoint(_urn) };
|
||||
if (_via != null)
|
||||
{
|
||||
foreach (Vector3i v in _via)
|
||||
{
|
||||
pts.Add(RelayPoint(v));
|
||||
}
|
||||
}
|
||||
Vector3i before = _via != null && _via.Count > 0 ? _via[_via.Count - 1] : _urn;
|
||||
pts.Add(BoxPoint(_end, before));
|
||||
for (int i = 0; i + 1 < pts.Count; i++)
|
||||
{
|
||||
_pairs.Add(_toUrn ? (pts[i + 1], pts[i]) : (pts[i], pts[i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Точка нити у черепа - над черепом-декором (он лежит на дне клетки). Сцена (без Origin).</summary>
|
||||
public static Vector3 RelayPoint(Vector3i _skull) => World.blockToTransformPos(_skull) - Origin.position + Vector3.up * 0.3f;
|
||||
|
||||
/// <summary>Снять все нити урны или все нити, идущие к ящику (у всех урн сейва). Сколько снято.</summary>
|
||||
static int RemoveAll(World _w, Vector3i _owner, TEFeatureCinerarium _urn)
|
||||
{
|
||||
@@ -444,8 +549,9 @@ namespace NecromancerTome
|
||||
{
|
||||
return;
|
||||
}
|
||||
Vector3i s = CinerariumThreads.Start.Value;
|
||||
Vector3 a = World.blockToTransformPos(s) - Origin.position + Vector3.up * (CinerariumThreads.StartIsUrn ? 0.5f : 0.6f);
|
||||
Vector3i s = CinerariumThreads.LastPoint.Value;
|
||||
Vector3 a = CinerariumThreads.LastIsRelay ? CinerariumThreads.RelayPoint(s)
|
||||
: World.blockToTransformPos(s) - Origin.position + Vector3.up * (CinerariumThreads.StartIsUrn ? 0.5f : 0.6f);
|
||||
Transform cam = pl.playerCamera != null ? pl.playerCamera.transform : null;
|
||||
Vector3 b = cam != null ? cam.position + cam.forward * 0.6f + cam.right * 0.2f - cam.up * 0.25f : pl.GetPosition() - Origin.position + Vector3.up * 1.2f;
|
||||
bool far = Vector3.Distance(s.ToVector3(), pl.GetPosition()) > CinerariumThreads.MaxLength;
|
||||
|
||||
@@ -46,7 +46,19 @@ namespace NecromancerTome
|
||||
case "necrohold": Hold(pl, _params); break;
|
||||
case "necrobox": Box(w, _params); break;
|
||||
case "necrourn": Urn(w, _params); break;
|
||||
case "necroclick": CinerariumThreads.Click(pl, _params.Count > 0 && _params[0] == "1"); Out("necroclick: ok"); break;
|
||||
case "necrogod": God(pl, _params.Count == 0 || _params[0] != "off"); break;
|
||||
case "necroclick":
|
||||
// necroclick 0|1 [x y z] - по блоку под прицелом или по указанной клетке (луч не всегда попадает в низкий череп).
|
||||
if (_params.Count >= 4)
|
||||
{
|
||||
CinerariumThreads.ClickAt(pl, w, P(_params, 1), _params[0] == "1");
|
||||
}
|
||||
else
|
||||
{
|
||||
CinerariumThreads.Click(pl, _params.Count > 0 && _params[0] == "1");
|
||||
}
|
||||
Out("necroclick: ok");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
@@ -78,6 +90,15 @@ namespace NecromancerTome
|
||||
Out("necrolook: yaw " + yaw.ToString("F0") + ", pitch " + pitch.ToString("F0"));
|
||||
}
|
||||
|
||||
/// <summary>Режим бога и невидимость для ИИ: пока ассистент проверяет, персонаж стоит - зомби тестового мира его не трогают
|
||||
/// (18.10: «пока персонаж просто стоит, его скушают»; «Я обычно ставлю режим Бога»).</summary>
|
||||
static void God(EntityPlayerLocal _pl, bool _on)
|
||||
{
|
||||
_pl.IsGodMode.Value = _on;
|
||||
_pl.SetIgnoredByAI(_on);
|
||||
Out("necrogod: " + (_on ? "бог, ИИ не видит" : "обычный"));
|
||||
}
|
||||
|
||||
static void Aim(World _w, EntityPlayerLocal _pl)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder("necroaim: игрок ").Append(_pl.position.ToString("F1")).Append(", взгляд ").Append(_pl.rotation.ToString("F0"));
|
||||
@@ -151,7 +172,20 @@ namespace NecromancerTome
|
||||
return;
|
||||
}
|
||||
string op = _p.Count > 3 ? _p[3] : "list";
|
||||
if (op == "clear")
|
||||
if (op == "fill")
|
||||
{
|
||||
// Все пустые ячейки - полной стопкой предмета (ящик «полон» по-честному).
|
||||
ItemValue iv = ItemClass.GetItem(_p[4], true);
|
||||
for (int i = 0; i < s.ItemGrid.Length; i++)
|
||||
{
|
||||
if (s.ItemGrid[i] == null || s.ItemGrid[i].IsEmpty())
|
||||
{
|
||||
s.UpdateSlot(i, new ItemStack(iv.Clone(), iv.ItemClass.Stacknumber.Value));
|
||||
}
|
||||
}
|
||||
s.SetModified();
|
||||
}
|
||||
else if (op == "clear")
|
||||
{
|
||||
for (int i = 0; i < s.ItemGrid.Length; i++)
|
||||
{
|
||||
@@ -182,6 +216,17 @@ namespace NecromancerTome
|
||||
Out(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>Черепа на пути нити: тройки координат после конца нити, по порядку от урны.</summary>
|
||||
static List<Vector3i> Via(List<string> _p, int _i)
|
||||
{
|
||||
List<Vector3i> l = new List<Vector3i>();
|
||||
for (int k = _i; k + 2 < _p.Count; k += 3)
|
||||
{
|
||||
l.Add(P(_p, k));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static void Urn(World _w, List<string> _p)
|
||||
{
|
||||
Vector3i pos = P(_p, 0);
|
||||
@@ -195,9 +240,9 @@ namespace NecromancerTome
|
||||
switch (op)
|
||||
{
|
||||
case "skull": u.SetSkull(_p[4] == "1"); break;
|
||||
case "from": u.AddThreadFrom(P(_p, 4)); break;
|
||||
case "to": u.SetThreadTo(P(_p, 4)); break;
|
||||
case "out": u.AddThreadOut(P(_p, 4)); break;
|
||||
case "from": u.AddThreadFrom(P(_p, 4), Via(_p, 7)); break;
|
||||
case "to": u.SetThreadTo(P(_p, 4), Via(_p, 7)); break;
|
||||
case "out": u.AddThreadOut(P(_p, 4), Via(_p, 7)); break;
|
||||
case "clear": u.ClearThreads(); break;
|
||||
case "sample":
|
||||
u.SetSample(_p.Count > 5 ? new ItemStack(ItemClass.GetItem(_p[4], true), int.Parse(_p[5])) : ItemStack.Empty.Clone());
|
||||
@@ -207,7 +252,15 @@ namespace NecromancerTome
|
||||
u.SetModified();
|
||||
break;
|
||||
}
|
||||
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck +
|
||||
string blink = "нет";
|
||||
Chunk ch = _w.GetChunkFromWorldPos(pos) as Chunk;
|
||||
BlockEntityData be = ch?.GetBlockEntity(pos);
|
||||
if (be != null && be.bHasTransform)
|
||||
{
|
||||
CinerariumBlink cb = be.transform.GetComponent<CinerariumBlink>();
|
||||
blink = cb == null ? "нет" : (cb.enabled ? "мигает" : "выкл") + " (" + cb.Renderers.Count + " рендер.)";
|
||||
}
|
||||
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck + ", мигание " + blink +
|
||||
", откуда " + u.ThreadFrom.Count + ", куда " + (u.HasThreadTo ? u.ThreadTo.ToString() : "нет") + ", от урны " + u.ThreadOut.Count +
|
||||
", образец " + (u.Sample == null || u.Sample.IsEmpty() ? "нет" : u.Sample.itemValue.ItemClass?.GetItemName() + "x" + u.Sample.count));
|
||||
}
|
||||
@@ -275,4 +328,13 @@ namespace NecromancerTome
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroclick", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroGod : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrogod" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): режим бога и невидимость для ИИ [off]";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrogod", _params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +83,28 @@ namespace NecromancerTome
|
||||
string dir = Path.Combine(NecroMailbox.Dir, "shots");
|
||||
Directory.CreateDirectory(dir);
|
||||
string name = _params.Count > 0 ? _params[0] : System.DateTime.Now.ToString("HHmmss");
|
||||
// necroshot имя N интервал - серия из N кадров (имя1..имяN) через интервал секунд (18.10: мигание, полёт духа).
|
||||
if (_params.Count >= 3 && int.TryParse(_params[1], out int n) &&
|
||||
float.TryParse(_params[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float dt))
|
||||
{
|
||||
ThreadManager.StartCoroutine(Burst(dir, name, n, dt));
|
||||
SdtdConsole.Instance.Output("серия: " + n + " кадров через " + dt + " с");
|
||||
return;
|
||||
}
|
||||
string path = Path.Combine(dir, name + ".png");
|
||||
ScreenCapture.CaptureScreenshot(path);
|
||||
SdtdConsole.Instance.Output("снимок: " + path);
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator Burst(string _dir, string _name, int _n, float _dt)
|
||||
{
|
||||
for (int i = 1; i <= _n; i++)
|
||||
{
|
||||
ScreenCapture.CaptureScreenshot(Path.Combine(_dir, _name + i + ".png"));
|
||||
yield return new WaitForSecondsRealtime(_dt);
|
||||
}
|
||||
Debug.Log("[NecromancerTome] mailbox: серия " + _name + " готова");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>necrospawn - нажать «Появиться» в окне появления (18.10: ассистент сам запускает игру с сейвом
|
||||
|
||||
@@ -75,8 +75,15 @@ namespace NecromancerTome
|
||||
/// Время - ванильное GameUtils.IsBloodMoonTime (от заката дня луны до рассвета), оно считается по часам
|
||||
/// мира и одинаково у хоста и клиента; AIDirector с его BloodMoonActive есть только у сервера.
|
||||
/// </summary>
|
||||
/// <summary>Прилив для проверок (necrotide on|off|auto): null - по часам мира.</summary>
|
||||
public static bool? ForceTide;
|
||||
|
||||
public static bool IsBloodTide()
|
||||
{
|
||||
if (ForceTide.HasValue)
|
||||
{
|
||||
return ForceTide.Value;
|
||||
}
|
||||
World world = GameManager.Instance?.World;
|
||||
return world != null && GameUtils.IsBloodMoonTime(world.worldTime, (world.DuskHour, world.DawnHour),
|
||||
GameStats.GetInt(EnumGameStats.BloodMoonDay));
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user