Логист - латунное кадило: два вида, дух 1/3 из окон, мигание; команды разработки
- Блоки логиста на модели кадила: по центру на подставке (necroCinerariumLogist) и новый вид у стены на кронштейне (necroCinerariumLogistWall/+Skull); выбор вида - R с урной в руке. - Дух логиста втрое меньше, в покое сидит в кадиле, на перевозку вылетает через окно крышки (у стены - не в стену) и возвращается тем же окном. - Застрял (ящик «куда» полон) - урна мигает (CinerariumBlink) вместо почерневшего духа; прежняя зелёная подмена свечения старой модели убрана. - Команды разработки без мыши: necrospawn, necroexit [quit], necrolook, necroaim, necroplace, necrohold, necrobox, necrourn, necroclick. Проверено в игре (test8): перевозка обоими видами, вылет духа, вид у стены от стены. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b5bd4e4b2c
commit
6693d96d5e
+42
-50
@@ -35,11 +35,20 @@ namespace NecromancerTome
|
||||
/// без зоны - ставится вплотную к любым урнам, только нити и перевозка, глазницы всегда зелёные. Лимит - общий.</summary>
|
||||
public const string LogistBlockName = "necroCinerariumLogist";
|
||||
public const string LogistSkullBlockName = "necroCinerariumLogistSkull";
|
||||
/// <summary>Логист у стены (18.10, кадило на кронштейне): тот же логист, другой вид - выбор в меню R (CinerariumLogistForm).</summary>
|
||||
public const string LogistWallBlockName = "necroCinerariumLogistWall";
|
||||
public const string LogistWallSkullBlockName = "necroCinerariumLogistWallSkull";
|
||||
|
||||
public static bool IsLogistBlock(Block _b)
|
||||
{
|
||||
string n = _b?.GetBlockName();
|
||||
return n == LogistBlockName || n == LogistSkullBlockName;
|
||||
return n == LogistBlockName || n == LogistSkullBlockName || n == LogistWallBlockName || n == LogistWallSkullBlockName;
|
||||
}
|
||||
|
||||
public static bool IsLogistWallBlock(Block _b)
|
||||
{
|
||||
string n = _b?.GetBlockName();
|
||||
return n == LogistWallBlockName || n == LogistWallSkullBlockName;
|
||||
}
|
||||
/// <summary>Урна рабочего (17.10, BACKLOG «Урна рабочего»): изготавливает предмет из ячейки образца на станциях, к которым
|
||||
/// от неё тянутся нити; ингредиенты - из ящиков с нитями к урне, готовое - в ящики с нитями от урны. Без зоны и вне лимита,
|
||||
@@ -143,7 +152,7 @@ namespace NecromancerTome
|
||||
_worker = IsWorkerBlock(_blockValue.Block);
|
||||
_logistKnown = true;
|
||||
string placed = _blockValue.Block?.GetBlockName();
|
||||
if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == WorkerSkullBlockName)
|
||||
if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == LogistWallSkullBlockName || placed == WorkerSkullBlockName)
|
||||
{
|
||||
HasSkull = true; // поставили урну, поднятую с черепом
|
||||
}
|
||||
@@ -543,9 +552,10 @@ namespace NecromancerTome
|
||||
if (stuck != Stuck)
|
||||
{
|
||||
Stuck = stuck;
|
||||
Debug.Log("[NecromancerTome] Cinerarium: дух урны " + ToWorldPos() + (stuck ? " почернел - готовое класть некуда" : " ожил"));
|
||||
_glowState = -1; // логист - мигание
|
||||
Debug.Log("[NecromancerTome] Cinerarium: дух урны " + ToWorldPos() + (stuck ? (IsLogist ? " застрял - ящик «куда» полон, урна мигает" : " почернел - готовое класть некуда") : " ожил"));
|
||||
}
|
||||
if (motion != null)
|
||||
if (motion != null && !IsLogist)
|
||||
{
|
||||
motion.SetStuck(Stuck && at == null);
|
||||
}
|
||||
@@ -683,7 +693,13 @@ namespace NecromancerTome
|
||||
SyncGlow();
|
||||
if (HasSkull && _genie == null)
|
||||
{
|
||||
_genie = CinerariumGenie.Spawn(ModelTransform(), IsWorker ? CinerariumGenie.WorkerDrop : 0f);
|
||||
int logist = 0;
|
||||
if (IsLogist)
|
||||
{
|
||||
World w = GameManager.Instance?.World;
|
||||
logist = w != null && IsLogistWallBlock(w.GetBlock(ToWorldPos()).Block) ? 2 : 1;
|
||||
}
|
||||
_genie = CinerariumGenie.Spawn(ModelTransform(), IsWorker ? CinerariumGenie.WorkerDrop : 0f, logist);
|
||||
if (_genie != null && _sound)
|
||||
{
|
||||
Audio.Manager.BroadcastPlay(World.blockToTransformPos(ToWorldPos()), GenieSound);
|
||||
@@ -705,14 +721,15 @@ namespace NecromancerTome
|
||||
void SyncGlow()
|
||||
{
|
||||
Transform t = ModelTransform();
|
||||
// Логист (16.10: «Так сразу будет видно что логистика, а что технолог») - всегда своя текстура свечения (руны и
|
||||
// глазницы зелёные) и зеленоватая глазурь; светится, как и обычная урна, только с черепом.
|
||||
int want = (HasSkull ? 1 : 0) + (IsLogist ? 2 : 0);
|
||||
Texture logist = IsLogist ? LogistEmission() : null;
|
||||
// Логист (18.10, кадило): своё свечение в материале - внутренность кадила, светятся окна крышки. Застрял (класть
|
||||
// некуда) - вместо почерневшего духа мигает (18.10: «При застревании должно мигать»): CinerariumBlink на модели.
|
||||
bool blink = HasSkull && IsLogist && Stuck;
|
||||
int want = (HasSkull ? 1 : 0) + (blink ? 2 : 0);
|
||||
if (t == null || (_glowState == want && _glowModel == t))
|
||||
{
|
||||
return;
|
||||
}
|
||||
List<Renderer> own = new List<Renderer>();
|
||||
foreach (Renderer r in t.GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
if (_genie != null && r.transform.IsChildOf(_genie.transform) || _bounds != null && r.transform.IsChildOf(_bounds.transform) ||
|
||||
@@ -720,53 +737,25 @@ namespace NecromancerTome
|
||||
{
|
||||
continue;
|
||||
}
|
||||
own.Add(r);
|
||||
r.GetPropertyBlock(_glowBlock);
|
||||
_glowBlock.SetColor(EmissionColorId, HasSkull ? GlowColor : Color.black);
|
||||
if (logist != null)
|
||||
{
|
||||
_glowBlock.SetTexture(EmissionMapId, logist);
|
||||
_glowBlock.SetColor(AlbedoColorId, LogistTint);
|
||||
}
|
||||
r.SetPropertyBlock(_glowBlock);
|
||||
}
|
||||
CinerariumBlink b = t.GetComponent<CinerariumBlink>();
|
||||
if (blink && b == null)
|
||||
{
|
||||
b = t.gameObject.AddComponent<CinerariumBlink>();
|
||||
}
|
||||
if (b != null)
|
||||
{
|
||||
b.Renderers = own;
|
||||
b.enabled = blink;
|
||||
}
|
||||
_glowState = want;
|
||||
_glowModel = t;
|
||||
}
|
||||
|
||||
/// <summary>Глазурь урны-логиста - множитель цвета материала (чёрная глазурь с зелёным отливом).</summary>
|
||||
public static Color LogistTint = new Color(0.55f, 1f, 0.75f, 1f);
|
||||
static readonly int AlbedoColorId = Shader.PropertyToID("_Color");
|
||||
static readonly int EmissionMapId = Shader.PropertyToID("_EmissionMap");
|
||||
static Texture2D _logistTex;
|
||||
static bool _logistTried;
|
||||
public const string LogistTextureFile = "necroCinerarium_e_logist.png";
|
||||
|
||||
/// <summary>Свечение урны-логиста (16.10: «пусть глаза черепов светятся другим цветом. Зелёным»): копия запечённой
|
||||
/// necroCinerarium_e с зелёными глазницами (руны - те же), из папки Resources мода - бандл не пересобирается.</summary>
|
||||
static Texture2D LogistEmission()
|
||||
{
|
||||
if (!_logistTried)
|
||||
{
|
||||
_logistTried = true;
|
||||
try
|
||||
{
|
||||
string path = Path.Combine(ModEntry.Instance.Path, "Resources", LogistTextureFile);
|
||||
Texture2D tex = new Texture2D(2, 2, TextureFormat.RGBA32, true);
|
||||
if (tex.LoadImage(File.ReadAllBytes(path)))
|
||||
{
|
||||
tex.name = "necroCinerarium_e_logist";
|
||||
tex.anisoLevel = 4;
|
||||
_logistTex = tex;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] Cinerarium: свечение логиста не загружено (" + e.Message + ")");
|
||||
}
|
||||
}
|
||||
return _logistTex;
|
||||
}
|
||||
|
||||
Transform _glowModel;
|
||||
|
||||
void RemoveGenie(bool _sound)
|
||||
@@ -864,7 +853,7 @@ namespace NecromancerTome
|
||||
{
|
||||
string n = _b?.GetBlockName();
|
||||
return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName ||
|
||||
n == WorkerBlockName || n == WorkerSkullBlockName;
|
||||
n == LogistWallBlockName || n == LogistWallSkullBlockName || n == WorkerBlockName || n == WorkerSkullBlockName;
|
||||
}
|
||||
|
||||
public static int UrnLimit(EntityPlayer _player)
|
||||
@@ -1224,8 +1213,11 @@ namespace NecromancerTome
|
||||
return false;
|
||||
}
|
||||
bool logist = TEFeatureCinerarium.IsLogistBlock(bv.Block);
|
||||
bool wall = TEFeatureCinerarium.IsLogistWallBlock(bv.Block);
|
||||
bool worker = TEFeatureCinerarium.IsWorkerBlock(bv.Block);
|
||||
ItemValue iv = ItemClass.GetItem(logist
|
||||
ItemValue iv = ItemClass.GetItem(wall
|
||||
? (urn.HasSkull ? TEFeatureCinerarium.LogistWallSkullBlockName : TEFeatureCinerarium.LogistWallBlockName)
|
||||
: logist
|
||||
? (urn.HasSkull ? TEFeatureCinerarium.LogistSkullBlockName : TEFeatureCinerarium.LogistBlockName)
|
||||
: worker
|
||||
? (urn.HasSkull ? TEFeatureCinerarium.WorkerSkullBlockName : TEFeatureCinerarium.WorkerBlockName)
|
||||
|
||||
@@ -74,7 +74,17 @@ namespace NecromancerTome
|
||||
return _prefab != null;
|
||||
}
|
||||
|
||||
public static GameObject Spawn(Transform _urnModel, float _drop)
|
||||
/// <summary>Дух логиста (18.10: «Пусть дух вылетает когда нужно что-то нести. Но уменьшим его в три раза»): размер.</summary>
|
||||
public static float LogistScale = 1f / 3f;
|
||||
/// <summary>Дом духа логиста в осях модели урны - корень модели духа внутри кадила, середина тела на высоте окон крышки
|
||||
/// (по центру - окна на 0.63 м, у стены - 0.73 м, кадило в 0.22 м от стены, стена - -z).</summary>
|
||||
public static Vector3 LogistHome = new Vector3(0f, 0.25f, 0f);
|
||||
public static Vector3 LogistWallHome = new Vector3(0f, 0.35f, -0.22f);
|
||||
/// <summary>Окна крышки - на расстоянии LogistExit от оси кадила (радиус крышки 0.21 м), по осям модели.</summary>
|
||||
public static float LogistExit = 0.3f;
|
||||
|
||||
/// <summary>_logist: 0 - обычный дух над горлышком, 1 - дух логиста в кадиле по центру, 2 - в кадиле у стены.</summary>
|
||||
public static GameObject Spawn(Transform _urnModel, float _drop, int _logist = 0)
|
||||
{
|
||||
if (_urnModel == null || !Load())
|
||||
{
|
||||
@@ -107,6 +117,12 @@ namespace NecromancerTome
|
||||
}
|
||||
CinerariumGenieMotion m = g.AddComponent<CinerariumGenieMotion>();
|
||||
m.Drop = _drop;
|
||||
if (_logist > 0)
|
||||
{
|
||||
m.Small = true;
|
||||
m.Wall = _logist == 2;
|
||||
g.transform.localScale = Vector3.one * LogistScale;
|
||||
}
|
||||
m.Pose();
|
||||
return g;
|
||||
}
|
||||
@@ -273,8 +289,14 @@ namespace NecromancerTome
|
||||
{
|
||||
public Vector3 Hover, Look; // в осях модели урны
|
||||
public float Fly;
|
||||
public bool Pass; // точка пути (окно кадила) - без жеста
|
||||
}
|
||||
|
||||
/// <summary>Дух логиста (18.10): втрое меньше, дома (в кадиле) не виден, вылетает и влетает через окно крышки.</summary>
|
||||
public bool Small;
|
||||
/// <summary>Кадило у стены: дом сдвинут к кронштейну, окно в стену (-z) не используется.</summary>
|
||||
public bool Wall;
|
||||
|
||||
readonly List<Stop> _stops = new List<Stop>();
|
||||
float _backFly;
|
||||
float _t = -1f;
|
||||
@@ -374,11 +396,23 @@ namespace NecromancerTome
|
||||
Vector3 home = Home();
|
||||
Vector3 prev = home;
|
||||
float delay = 0f;
|
||||
Vector3 exit = home;
|
||||
if (Small)
|
||||
{
|
||||
// Сначала - к окну крышки в сторону первого места (у стены окно в стену не годится), оттуда - к месту.
|
||||
exit = Window(home, transform.parent.InverseTransformPoint(_list[0].stand) - home);
|
||||
_stops.Add(new Stop { Hover = exit, Look = exit + (exit - home), Fly = 0.35f, Pass = true });
|
||||
delay += 0.35f;
|
||||
prev = exit;
|
||||
}
|
||||
foreach ((Vector3 stand, Vector3 look, bool beside) in _list)
|
||||
{
|
||||
// Высота корня духа над низом клетки места: обычный дух - над станцией 0.8 (сбоку 0.15) с поправкой урны;
|
||||
// маленький - над верхом клетки (1.05) или у её середины сбоку (0.4).
|
||||
float up = Small ? (beside ? 0.4f : 1.05f) : CinerariumGenie.OffsetY + Drop + (beside ? 0.15f : 0.8f);
|
||||
Stop st = new Stop
|
||||
{
|
||||
Hover = transform.parent.InverseTransformPoint(stand) + new Vector3(0f, CinerariumGenie.OffsetY + Drop + (beside ? 0.15f : 0.8f), 0f),
|
||||
Hover = transform.parent.InverseTransformPoint(stand) + new Vector3(0f, up, 0f),
|
||||
Look = transform.parent.InverseTransformPoint(look)
|
||||
};
|
||||
st.Fly = Mathf.Max(OutSeconds, Vector3.Distance(prev, st.Hover) / FlySpeed);
|
||||
@@ -386,13 +420,44 @@ namespace NecromancerTome
|
||||
prev = st.Hover;
|
||||
_stops.Add(st);
|
||||
}
|
||||
_backFly = Mathf.Max(BackSeconds, Vector3.Distance(prev, home) / FlySpeed);
|
||||
if (Small)
|
||||
{
|
||||
// Назад - тем же окном.
|
||||
_stops.Add(new Stop { Hover = exit, Look = home, Fly = Mathf.Max(BackSeconds, Vector3.Distance(prev, exit) / FlySpeed), Pass = true });
|
||||
prev = exit;
|
||||
}
|
||||
_backFly = Small ? 0.35f : Mathf.Max(BackSeconds, Vector3.Distance(prev, home) / FlySpeed);
|
||||
_t = 0f;
|
||||
return delay - WorkSeconds * 0.5f;
|
||||
}
|
||||
|
||||
/// <summary>Окно крышки кадила по направлению _dir (оси модели): одно из четырёх по осям x/z, у стены - не в стену.</summary>
|
||||
Vector3 Window(Vector3 _home, Vector3 _dir)
|
||||
{
|
||||
Vector3 best = Vector3.forward;
|
||||
float bestDot = float.MinValue;
|
||||
foreach (Vector3 w in new[] { Vector3.forward, Vector3.back, Vector3.left, Vector3.right })
|
||||
{
|
||||
if (Wall && w == Vector3.back)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float d = Vector3.Dot(w, new Vector3(_dir.x, 0f, _dir.z));
|
||||
if (d > bestDot)
|
||||
{
|
||||
bestDot = d;
|
||||
best = w;
|
||||
}
|
||||
}
|
||||
return _home + best * CinerariumGenie.LogistExit;
|
||||
}
|
||||
|
||||
Vector3 Home()
|
||||
{
|
||||
if (Small)
|
||||
{
|
||||
return Wall ? CinerariumGenie.LogistWallHome : CinerariumGenie.LogistHome;
|
||||
}
|
||||
return new Vector3(0f, CinerariumGenie.OffsetY + Drop, 0f) + Quaternion.Euler(0f, _yaw, 0f) * new Vector3(0f, 0f, CinerariumGenie.Forward);
|
||||
}
|
||||
|
||||
@@ -483,14 +548,16 @@ namespace NecromancerTome
|
||||
transform.localPosition = Home();
|
||||
}
|
||||
_asleep = asleep;
|
||||
if (_hiddenApplied != CinerariumGenie.Hidden)
|
||||
}
|
||||
// Виден ли: не скрыт командой; дух логиста - только в полёте (дома сидит в кадиле). Рендереры - только при смене
|
||||
// (16.10, проверка на утечки: раньше список рендереров собирался каждые 0.5 с - мусор для GC).
|
||||
bool show = !CinerariumGenie.Hidden && (!Small || _t >= 0f);
|
||||
if (_hiddenApplied != !show)
|
||||
{
|
||||
_hiddenApplied = !show;
|
||||
foreach (Renderer r in GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
// Только при смене (16.10, проверка на утечки: раньше список рендереров собирался каждые 0.5 с - мусор для GC).
|
||||
_hiddenApplied = CinerariumGenie.Hidden;
|
||||
foreach (Renderer r in GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
r.enabled = !_hiddenApplied;
|
||||
}
|
||||
r.enabled = show;
|
||||
}
|
||||
}
|
||||
if (_stuck || _asleep)
|
||||
@@ -515,7 +582,7 @@ namespace NecromancerTome
|
||||
return;
|
||||
}
|
||||
acc += st.Fly;
|
||||
if (_t < acc + WorkSeconds)
|
||||
if (!st.Pass && _t < acc + WorkSeconds)
|
||||
{
|
||||
transform.localPosition = st.Hover;
|
||||
Face(st.Look - st.Hover); // за работой - лицом к месту
|
||||
@@ -523,7 +590,10 @@ namespace NecromancerTome
|
||||
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
|
||||
return;
|
||||
}
|
||||
acc += WorkSeconds;
|
||||
if (!st.Pass)
|
||||
{
|
||||
acc += WorkSeconds;
|
||||
}
|
||||
from = st.Hover;
|
||||
}
|
||||
if (_t < acc + _backFly)
|
||||
@@ -537,6 +607,11 @@ namespace NecromancerTome
|
||||
_t = -1f;
|
||||
Gesture(0f);
|
||||
}
|
||||
if (Small)
|
||||
{
|
||||
transform.localPosition = home; // в кадиле, не виден
|
||||
return;
|
||||
}
|
||||
_yaw = yawTarget % 360f;
|
||||
float bob = CinerariumGenie.BobPeriod > 0.01f
|
||||
? Mathf.Sin((Time.time + _phase) * Mathf.PI * 2f / CinerariumGenie.BobPeriod) * CinerariumGenie.BobHeight
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
using System.Collections.Generic;
|
||||
using GUI_2;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Урна логиста - латунное кадило (18.10.2026, BACKLOG «Урна логиста - своя модель: кадило»): два вида - на подставке по
|
||||
/// центру клетки (necroCinerariumLogist) и на кронштейне у стены (necroCinerariumLogistWall). Пользователь: «Должна быть
|
||||
/// возможность крепить её и к стене, и по центру какого-нибудь столба... как мы делали с черепом. Только череп у нас изначально
|
||||
/// не блок, а тут должно получиться попроще». Вид выбирается, как у черепа-декора: урна в руке, R - меню блока игры, к нему
|
||||
/// добавлен второй вид; выбор превращает стопку в ячейке пояса в другой блок (тот же счёт). Пустая урна и урна с черепом -
|
||||
/// каждая в свой вид того же рода.
|
||||
/// </summary>
|
||||
public static class CinerariumLogistForm
|
||||
{
|
||||
/// <summary>Номера наших пунктов в меню блока - вне занятых игрой (-2..10) и черепом (40..42).</summary>
|
||||
public const int MenuCenter = 43, MenuWall = 44;
|
||||
|
||||
static readonly Dictionary<string, (string center, string wall)> Pairs = new Dictionary<string, (string, string)>
|
||||
{
|
||||
{ TEFeatureCinerarium.LogistBlockName, (TEFeatureCinerarium.LogistBlockName, TEFeatureCinerarium.LogistWallBlockName) },
|
||||
{ TEFeatureCinerarium.LogistWallBlockName, (TEFeatureCinerarium.LogistBlockName, TEFeatureCinerarium.LogistWallBlockName) },
|
||||
{ TEFeatureCinerarium.LogistSkullBlockName, (TEFeatureCinerarium.LogistSkullBlockName, TEFeatureCinerarium.LogistWallSkullBlockName) },
|
||||
{ TEFeatureCinerarium.LogistWallSkullBlockName, (TEFeatureCinerarium.LogistSkullBlockName, TEFeatureCinerarium.LogistWallSkullBlockName) },
|
||||
};
|
||||
|
||||
public static bool IsLogistItem(ItemClass _item) => _item != null && Pairs.ContainsKey(_item.Name);
|
||||
|
||||
static void Convert(EntityPlayerLocal _pl, bool _wall)
|
||||
{
|
||||
ItemStack cur = _pl?.inventory?.holdingItemStack;
|
||||
if (cur == null || cur.IsEmpty() || !Pairs.TryGetValue(cur.itemValue.ItemClass.Name, out var pair))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string name = _wall ? pair.wall : pair.center;
|
||||
if (cur.itemValue.ItemClass.Name == name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ItemValue iv = ItemClass.GetItem(name);
|
||||
if (iv == null || iv.IsEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
_pl.inventory.SetItem(_pl.inventory.holdingItemIdx, new ItemStack(iv, cur.count));
|
||||
}
|
||||
|
||||
public static void OnBlockMenu(XUiC_Radial _sender, int _commandIndex, XUiC_Radial.RadialContextAbs _context)
|
||||
{
|
||||
EntityPlayerLocal pl = _sender?.xui?.playerUI?.entityPlayer;
|
||||
switch (_commandIndex)
|
||||
{
|
||||
case MenuCenter: Convert(pl, false); return;
|
||||
case MenuWall: Convert(pl, true); return;
|
||||
}
|
||||
_sender.handleBlockShapeCommand(_sender, _commandIndex, _context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Меню блока с урной логиста в руке: к пунктам игры - второй вид (подставка / у стены).</summary>
|
||||
[HarmonyPatch(typeof(XUiC_Radial), nameof(XUiC_Radial.SetupBlockShapeData))]
|
||||
public static class Patch_XUiC_Radial_SetupBlockShapeData_LogistForm
|
||||
{
|
||||
public static void Postfix(XUiC_Radial __instance)
|
||||
{
|
||||
EntityPlayerLocal pl = __instance.xui?.playerUI?.entityPlayer;
|
||||
ItemClass held = pl?.inventory?.holdingItem;
|
||||
if (!CinerariumLogistForm.IsLogistItem(held))
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool wall = held.Name == TEFeatureCinerarium.LogistWallBlockName || held.Name == TEFeatureCinerarium.LogistWallSkullBlockName;
|
||||
if (wall)
|
||||
{
|
||||
__instance.CreateRadialEntry(CinerariumLogistForm.MenuCenter, TEFeatureCinerarium.LogistBlockName, "ItemIconAtlas", "", Localization.Get("necroCinerariumLogistFormCenter"));
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.CreateRadialEntry(CinerariumLogistForm.MenuWall, TEFeatureCinerarium.LogistWallBlockName, "ItemIconAtlas", "", Localization.Get("necroCinerariumLogistFormWall"));
|
||||
}
|
||||
__instance.SetCommonData(UIUtils.GetButtonIconForAction(__instance.xui.playerUI.playerInput.Activate), CinerariumLogistForm.OnBlockMenu,
|
||||
new XUiC_Radial.RadialContextHoldingSlotIndex(pl.inventory.holdingItemIdx), -1, true, XUiC_Radial.RadialValidSameHoldingSlotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Мигание урны логиста, когда класть некуда (18.10: «При застревании должно мигать»): свечение модели
|
||||
/// (MaterialPropertyBlock, как у TEFeatureCinerarium.SyncGlow) пульсирует от тёмного до обычного. Выключено - обычное свечение.</summary>
|
||||
[Preserve]
|
||||
public class CinerariumBlink : MonoBehaviour
|
||||
{
|
||||
public List<Renderer> Renderers = new List<Renderer>();
|
||||
public static float Period = 0.8f;
|
||||
static readonly MaterialPropertyBlock _block = new MaterialPropertyBlock();
|
||||
static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Резкий переход - мигание, а не дыхание: половину периода горит, половину почти погасла.
|
||||
float k = Mathf.Repeat(Time.time, Period) < Period * 0.5f ? 1f : 0.08f;
|
||||
Set(TEFeatureCinerarium.GlowColor * k);
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Set(TEFeatureCinerarium.GlowColor);
|
||||
}
|
||||
|
||||
void Set(Color _c)
|
||||
{
|
||||
foreach (Renderer r in Renderers)
|
||||
{
|
||||
if (r == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
r.GetPropertyBlock(_block);
|
||||
_block.SetColor(EmissionColorId, _c);
|
||||
r.SetPropertyBlock(_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ namespace NecromancerTome
|
||||
// Урна с нитями - логист (16.10: «занимается только перетаскиванием из ящиков в ящики»).
|
||||
if (_urn.IsLogist)
|
||||
{
|
||||
return Ferry(_world, _urn);
|
||||
return Ferry(_world, _urn, out _stuck);
|
||||
}
|
||||
// Урна рабочего - изготовление по образцу на станциях по нитям (CinerariumCraft.cs).
|
||||
if (_urn.IsWorker)
|
||||
@@ -139,10 +139,11 @@ namespace NecromancerTome
|
||||
}
|
||||
|
||||
/// <summary>Перевозка по нитям: одна стопка из ящика «откуда» в ящик «куда». Только предметы включённых в урне групп
|
||||
/// (фильтр логиста, CinerariumGroups; 16.10 заменил прежнее правило «по образцу»); «куда» полон - ждём (дух не чернеет). Открытые у игрока ящики и закреплённые
|
||||
/// ячейки не трогаются. Возвращает ящик «куда», LastFrom - откуда взято.</summary>
|
||||
static Vector3i? Ferry(World _world, TEFeatureCinerarium _urn)
|
||||
/// (фильтр логиста, CinerariumGroups; 16.10 заменил прежнее правило «по образцу»); «куда» полон - ждём, _stuck - урна
|
||||
/// мигает (18.10). Открытые у игрока ящики и закреплённые ячейки не трогаются. Возвращает ящик «куда», LastFrom - откуда взято.</summary>
|
||||
static Vector3i? Ferry(World _world, TEFeatureCinerarium _urn, out bool _stuck)
|
||||
{
|
||||
_stuck = false;
|
||||
if (!_urn.HasThreadTo)
|
||||
{
|
||||
return null;
|
||||
@@ -166,11 +167,17 @@ namespace NecromancerTome
|
||||
for (int k = 0; k < src.ItemGrid.Length; k++)
|
||||
{
|
||||
ItemStack s = src.ItemGrid[k];
|
||||
if (s == null || s.IsEmpty() || Locked(src, k) || !_urn.GroupOn(CinerariumGroups.Of(s.itemValue)) || !Fits(dst, s))
|
||||
if (s == null || s.IsEmpty() || Locked(src, k) || !_urn.GroupOn(CinerariumGroups.Of(s.itemValue)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!Fits(dst, s))
|
||||
{
|
||||
_stuck = true; // есть что везти, но некуда
|
||||
continue;
|
||||
}
|
||||
LastFrom = f;
|
||||
_stuck = false; // везти есть куда - не застрял
|
||||
if (_dry)
|
||||
{
|
||||
return _urn.ThreadTo;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NecromancerTome
|
||||
|
||||
static bool Ours(string _name)
|
||||
{
|
||||
return _name == "necroBookMindPrefab" || _name == "necroKnifeDecorPrefab" || _name == "necroMortarPrefab" || _name.StartsWith("necroBloodRose") || _name.StartsWith("necroPoster") || _name == "necroBookDoorPrefab" || _name.StartsWith("necroSkullDecor") || _name == "necroCinerariumPrefab" || _name == "necroCinerariumWorkerPrefab";
|
||||
return _name == "necroBookMindPrefab" || _name == "necroKnifeDecorPrefab" || _name == "necroMortarPrefab" || _name.StartsWith("necroBloodRose") || _name.StartsWith("necroPoster") || _name == "necroBookDoorPrefab" || _name.StartsWith("necroSkullDecor") || _name == "necroCinerariumPrefab" || _name == "necroCinerariumWorkerPrefab" || _name.StartsWith("necroCinerariumLogist");
|
||||
}
|
||||
|
||||
public static void Postfix(Transform __result)
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Команды разработки для проверок без мыши (18.10.2026: «И управление персонажем тоже, чтобы ты мог его повернуть как надо,
|
||||
/// довести куда надо»). Набираются через почтовый ящик (NecroMailbox), ответы - в лог. Перемещение - teleport игры
|
||||
/// (tp x y z), время - settime, предметы - giveself; здесь - то, чего у игры нет.
|
||||
/// necrolook yaw [pitch] | necrolook at x y z - повернуть взгляд (градусы; север 0, восток 90; pitch вверх +, вниз -)
|
||||
/// necroaim - позиция игрока и блок под прицелом
|
||||
/// necroplace блок x y z [поворот 0-3] - поставить блок (как игрок: OnBlockAdded, TE)
|
||||
/// necrohold предмет [число] - положить в текущую ячейку пояса и взять в руку
|
||||
/// necrobox x y z list|clear|add предмет число - содержимое хранилища (ящик, сумка урны)
|
||||
/// necrourn x y z info|skull 0|1|from|to|out bx by bz|clear|sample предмет число|group все|нет - урна
|
||||
/// necroclick 0|1 - отпущенная ЛКМ/ПКМ ножом для нитей по блоку под прицелом
|
||||
/// </summary>
|
||||
public static class NecroDev
|
||||
{
|
||||
static void Out(string _s) => SdtdConsole.Instance.Output(_s);
|
||||
|
||||
static float F(string _s) => float.Parse(_s, CultureInfo.InvariantCulture);
|
||||
|
||||
static Vector3i P(List<string> _p, int _i) => new Vector3i(int.Parse(_p[_i]), int.Parse(_p[_i + 1]), int.Parse(_p[_i + 2]));
|
||||
|
||||
/// <summary>Общий вход всех команд: игрок есть - выполнить, ошибка - строкой в ответ.</summary>
|
||||
public static void Run(string _cmd, List<string> _params)
|
||||
{
|
||||
World w = GameManager.Instance.World;
|
||||
EntityPlayerLocal pl = w?.GetPrimaryPlayer();
|
||||
if (pl == null)
|
||||
{
|
||||
Out(_cmd + ": нет игрока");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
switch (_cmd)
|
||||
{
|
||||
case "necrolook": Look(pl, _params); break;
|
||||
case "necroaim": Aim(w, pl); break;
|
||||
case "necroplace": PlaceBlock(w, _params); break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Out(_cmd + ": ошибка - " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
static void Look(EntityPlayerLocal _pl, List<string> _p)
|
||||
{
|
||||
float yaw, pitch = 0f;
|
||||
if (_p.Count >= 4 && _p[0] == "at")
|
||||
{
|
||||
Vector3 eye = _pl.getHeadPosition();
|
||||
Vector3 t = new Vector3(F(_p[1]) + 0.5f, F(_p[2]) + 0.5f, F(_p[3]) + 0.5f);
|
||||
Vector3 d = t - eye;
|
||||
yaw = Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg;
|
||||
pitch = Mathf.Atan2(d.y, new Vector2(d.x, d.z).magnitude) * Mathf.Rad2Deg; // в игре + вверх
|
||||
}
|
||||
else
|
||||
{
|
||||
yaw = F(_p[0]);
|
||||
if (_p.Count > 1)
|
||||
{
|
||||
pitch = F(_p[1]);
|
||||
}
|
||||
}
|
||||
_pl.TeleportToPosition(_pl.position, false, new Vector3(pitch, yaw, 0f));
|
||||
Out("necrolook: yaw " + yaw.ToString("F0") + ", pitch " + pitch.ToString("F0"));
|
||||
}
|
||||
|
||||
static void Aim(World _w, EntityPlayerLocal _pl)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder("necroaim: игрок ").Append(_pl.position.ToString("F1")).Append(", взгляд ").Append(_pl.rotation.ToString("F0"));
|
||||
WorldRayHitInfo hi = _pl.HitInfo;
|
||||
if (hi != null && hi.bHitValid)
|
||||
{
|
||||
BlockValue bv = _w.GetBlock(hi.hit.blockPos);
|
||||
sb.Append(", под прицелом ").Append(hi.hit.blockPos).Append(' ').Append(bv.Block?.GetBlockName()).Append(" rot ").Append(bv.rotation)
|
||||
.Append(", дальность ").Append(Mathf.Sqrt(hi.hit.distanceSq).ToString("F1"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(", под прицелом ничего");
|
||||
}
|
||||
Out(sb.ToString());
|
||||
}
|
||||
|
||||
static void PlaceBlock(World _w, List<string> _p)
|
||||
{
|
||||
Block b = Block.GetBlockByName(_p[0], true);
|
||||
if (b == null)
|
||||
{
|
||||
Out("necroplace: нет блока " + _p[0]);
|
||||
return;
|
||||
}
|
||||
BlockValue bv = b.ToBlockValue();
|
||||
if (_p.Count > 4)
|
||||
{
|
||||
bv.rotation = (byte)int.Parse(_p[4]);
|
||||
}
|
||||
Vector3i pos = P(_p, 1);
|
||||
_w.SetBlockRPC(pos, bv);
|
||||
// Как поставленный игроком (хозяин - мы): ящики без хозяина нити и духи не считают ящиками игрока.
|
||||
if (_w.GetTileEntity(pos) is TileEntityComposite comp && comp.Owner == null)
|
||||
{
|
||||
comp.SetOwner(Platform.PlatformManager.InternalLocalUserIdentifier);
|
||||
}
|
||||
Out("necroplace: " + b.GetBlockName() + " в " + pos + " rot " + bv.rotation);
|
||||
}
|
||||
|
||||
static void Hold(EntityPlayerLocal _pl, List<string> _p)
|
||||
{
|
||||
ItemValue iv = ItemClass.GetItem(_p[0], true);
|
||||
if (iv == null || iv.IsEmpty())
|
||||
{
|
||||
Out("necrohold: нет предмета " + _p[0]);
|
||||
return;
|
||||
}
|
||||
int n = _p.Count > 1 ? int.Parse(_p[1]) : 1;
|
||||
if (iv.ItemClass.HasQuality)
|
||||
{
|
||||
iv = new ItemValue(iv.type, 1, 1, true);
|
||||
}
|
||||
_pl.inventory.SetItem(_pl.inventory.holdingItemIdx, new ItemStack(iv, n));
|
||||
Out("necrohold: " + iv.ItemClass.GetItemName() + " x" + n + " в ячейке " + _pl.inventory.holdingItemIdx);
|
||||
}
|
||||
|
||||
static TEFeatureStorage StorageAt(World _w, Vector3i _p)
|
||||
{
|
||||
BlockValue bv = _w.GetBlock(_p);
|
||||
Vector3i owner = bv.ischild ? _p + bv.parent : _p;
|
||||
return _w.GetTileEntity(owner) is TileEntityComposite c && c.TryGetSelfOrFeature<TEFeatureStorage>(out TEFeatureStorage s) ? s : null;
|
||||
}
|
||||
|
||||
static void Box(World _w, List<string> _p)
|
||||
{
|
||||
TEFeatureStorage s = StorageAt(_w, P(_p, 0));
|
||||
if (s == null)
|
||||
{
|
||||
Out("necrobox: нет хранилища в " + P(_p, 0));
|
||||
return;
|
||||
}
|
||||
string op = _p.Count > 3 ? _p[3] : "list";
|
||||
if (op == "clear")
|
||||
{
|
||||
for (int i = 0; i < s.ItemGrid.Length; i++)
|
||||
{
|
||||
s.UpdateSlot(i, ItemStack.Empty.Clone());
|
||||
}
|
||||
s.SetModified();
|
||||
}
|
||||
else if (op == "add")
|
||||
{
|
||||
ItemValue iv = ItemClass.GetItem(_p[4], true);
|
||||
ItemStack st = new ItemStack(iv, int.Parse(_p[5]));
|
||||
CinerariumWork.Place(st, new[] { s });
|
||||
s.SetModified();
|
||||
if (st.count > 0)
|
||||
{
|
||||
Out("necrobox: не влезло " + st.count);
|
||||
}
|
||||
}
|
||||
StringBuilder sb = new StringBuilder("necrobox: ").Append(P(_p, 0)).Append(':');
|
||||
for (int i = 0; i < s.ItemGrid.Length; i++)
|
||||
{
|
||||
ItemStack st = s.ItemGrid[i];
|
||||
if (st != null && !st.IsEmpty())
|
||||
{
|
||||
sb.Append(' ').Append(st.itemValue.ItemClass?.GetItemName()).Append('x').Append(st.count);
|
||||
}
|
||||
}
|
||||
Out(sb.ToString());
|
||||
}
|
||||
|
||||
static void Urn(World _w, List<string> _p)
|
||||
{
|
||||
Vector3i pos = P(_p, 0);
|
||||
TEFeatureCinerarium u = CinerariumThreads.UrnAt(_w, pos);
|
||||
if (u == null)
|
||||
{
|
||||
Out("necrourn: нет урны в " + pos);
|
||||
return;
|
||||
}
|
||||
string op = _p.Count > 3 ? _p[3] : "info";
|
||||
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 "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());
|
||||
break;
|
||||
case "group":
|
||||
u.GroupMask = _p[4] == "нет" || _p[4] == "none" ? 0u : CinerariumGroups.All;
|
||||
u.SetModified();
|
||||
break;
|
||||
}
|
||||
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck +
|
||||
", откуда " + 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));
|
||||
}
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroLook : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrolook" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): повернуть взгляд: yaw [pitch] | at x y z";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrolook", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroAim : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necroaim" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): позиция игрока и блок под прицелом";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroaim", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroPlace : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necroplace" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): поставить блок: имя x y z [поворот]";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroplace", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroHold : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrohold" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): предмет в руку: имя [число]";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrohold", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroBox : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrobox" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): хранилище: x y z list|clear|add предмет число";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrobox", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroUrn : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrourn" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): урна: x y z info|skull|from|to|out|clear|sample|group";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrourn", _params);
|
||||
}
|
||||
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroClick : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necroclick" };
|
||||
public override string getDescription() => "NecromancerTome (разработка): нож для нитей: 0 - ЛКМ, 1 - ПКМ по блоку под прицелом";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroclick", _params);
|
||||
}
|
||||
}
|
||||
@@ -88,4 +88,65 @@ namespace NecromancerTome
|
||||
SdtdConsole.Instance.Output("снимок: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>necrospawn - нажать «Появиться» в окне появления (18.10: ассистент сам запускает игру с сейвом
|
||||
/// -LoadSaveGame=true и входит в мир без мыши). То же, что кнопка: XUiC_SpawnSelectionWindow.SpawnButtonPressed.</summary>
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroSpawn : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necrospawn" };
|
||||
public override string getDescription() => "NecromancerTome: нажать «Появиться» (разработка, почтовый ящик)";
|
||||
public override bool AllowedInMainMenu => false;
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
foreach (LocalPlayerUI ui in LocalPlayerUI.PlayerUIs)
|
||||
{
|
||||
XUiC_SpawnSelectionWindow w = ui?.xui?.GetWindowByType<XUiC_SpawnSelectionWindow>();
|
||||
if (w != null && w.IsOpen)
|
||||
{
|
||||
w.SpawnButtonPressed(SpawnMethod.Invalid);
|
||||
SdtdConsole.Instance.Output("necrospawn: нажато «Появиться»");
|
||||
return;
|
||||
}
|
||||
}
|
||||
SdtdConsole.Instance.Output("necrospawn: окно появления не открыто");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>necroexit [quit] - выйти из мира с сохранением, как «Выход» в меню паузы (GameManager.Disconnect); с quit -
|
||||
/// затем закрыть игру. Нужно перед сборкой бандлов и заменой DLL (игра держит файлы).</summary>
|
||||
[Preserve]
|
||||
public class ConsoleCmdNecroExit : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necroexit" };
|
||||
public override string getDescription() => "NecromancerTome: выйти из мира с сохранением [quit - и закрыть игру] (разработка)";
|
||||
public override bool AllowedInMainMenu => true;
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
bool quit = _params.Count > 0 && _params[0] == "quit";
|
||||
SdtdConsole.Instance.Output("necroexit: выход" + (quit ? " и закрытие игры" : " в меню"));
|
||||
ThreadManager.StartCoroutine(ExitCo(quit));
|
||||
}
|
||||
|
||||
static System.Collections.IEnumerator ExitCo(bool _quit)
|
||||
{
|
||||
if (GameManager.Instance.World != null)
|
||||
{
|
||||
GameManager.Instance.Disconnect();
|
||||
while (GameManager.Instance.World != null)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
Debug.Log("[NecromancerTome] necroexit: мир сохранён и закрыт");
|
||||
}
|
||||
if (_quit)
|
||||
{
|
||||
yield return new WaitForSecondsRealtime(2f);
|
||||
Debug.Log("[NecromancerTome] necroexit: закрываю игру");
|
||||
Application.Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user