Оссуарий-котёл: одна ячейка образца, хранилище 6, перенос старых ячеек, свечение, огонёк, рецепт в кузне (#66, #69)
- blocks.xml: 6 ячеек хранилища (цикл вдвое дольше - добыча прежняя); видно одну ячейку образца и одну топлива, остальные спрятаны (в данных их по-прежнему 3 и 6 - сейвы не режутся). - Перенос: содержимое спрятанных ячеек у уже поставленных оссуариев уходит в хранилище (проверено на test8: уголь, камень, сферы). - Свечение рун и окошек и сфера на крышке - пока работает (OssuaryGlow). - Огонёк дикого духа: вылет из окошка с уходом в землю и затуханием, возврат с другой стороны в противоположное окошко; только пока работает. В игре пока не увиден - разобрать (necroossuary wisp on|off). - Рецепт - кузня, железо вместо дерева и юкки (плавильня / лом). - Описание на 13 языках под котёл и вольных духов. - Консоль: necroossuary info / put (отладка ячеек). - DLL включает и #72, #74. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e3cdff0323
commit
c2610da59a
@@ -53,15 +53,23 @@ namespace NecromancerTome
|
||||
|
||||
public static bool Is(TileEntityCollector te) => te != null && Is(te.blockValue.Block);
|
||||
|
||||
/// <summary>os1 -> 0, os2 -> 1, os3 -> 2; иначе -1.</summary>
|
||||
/// <summary>Ячейка образца для выхода: у котла (#66, 22.10) образец один - все выходы os1..os6 берут ресурс из
|
||||
/// ячейки 0 («Ячейка под образец - одна»). Не наш выход - -1.</summary>
|
||||
public static int ModSlotOf(BlockCollector.OutputType outputType)
|
||||
{
|
||||
string name = outputType?.Name;
|
||||
if (name == null || name.Length != 3 || !name.StartsWith("os"))
|
||||
return -1;
|
||||
return name[2] - '1';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Видимых в окне ячеек образца и топлива (#66, 22.10: «Ячейка под образец - одна; ячейка под кровавую
|
||||
/// сферу (или камень) - одна»). В данных их по-прежнему 3 и 6: длины пишутся в сейв, а при загрузке игра обрезает
|
||||
/// лишнее по конфигу - уменьшение молча выбросило бы содержимое. Лишние прячутся в окне, их старое содержимое
|
||||
/// переносит в хранилище Patch_TileEntityCollector_UpdateTick_GeoOssuaryMigrate.</summary>
|
||||
public const int VisibleModSlots = 1;
|
||||
public const int VisibleFuelSlots = 1;
|
||||
|
||||
/// <summary>Ресурс в ячейке модификации, если он из списка; иначе null.</summary>
|
||||
public static ItemClass ResourceIn(TileEntityCollector te, int modSlot)
|
||||
{
|
||||
@@ -82,8 +90,11 @@ namespace NecromancerTome
|
||||
public static void Postfix(XUiC_DewCollectorModGrid __instance, TileEntityCollector te)
|
||||
{
|
||||
bool ossuary = GeoOssuary.Is(te);
|
||||
foreach (XUiC_ItemStack slot in __instance.itemControllers)
|
||||
for (int i = 0; i < __instance.itemControllers.Length; i++)
|
||||
{
|
||||
XUiC_ItemStack slot = __instance.itemControllers[i];
|
||||
// окно у всех коллекторов общее - чужим возвращаем все ячейки
|
||||
slot.ViewComponent.IsVisible = !ossuary || i < GeoOssuary.VisibleModSlots;
|
||||
if (!(slot is XUiC_RequiredItemStack required))
|
||||
continue;
|
||||
required.OverrideStackCount = ossuary ? 1 : -1;
|
||||
@@ -251,4 +262,104 @@ namespace NecromancerTome
|
||||
__result = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Одна ячейка топлива в окне (GeoOssuary.VisibleFuelSlots): остальные ячейки сетки и их подсказки-картинки
|
||||
/// спрятаны. Сетка общая для всех коллекторов (и для катализатора) - чужим всё показываем обратно.</summary>
|
||||
public static class GeoOssuaryFuelGrid
|
||||
{
|
||||
public static void Apply(XUiC_CollectorFuelGrid _grid)
|
||||
{
|
||||
if (_grid?.tileEntity == null || _grid.fuelBehavior != XUiC_CollectorFuelGrid.FuelBehavior.Fuel)
|
||||
return;
|
||||
bool ossuary = GeoOssuary.Is(_grid.tileEntity);
|
||||
int n = _grid.tileEntity.FuelGridLength;
|
||||
for (int i = 0; i < _grid.itemControllers.Length; i++)
|
||||
{
|
||||
bool show = i < n && (!ossuary || i < GeoOssuary.VisibleFuelSlots);
|
||||
if (_grid.itemControllers[i].ViewComponent.IsVisible != show && (ossuary || i < n))
|
||||
_grid.itemControllers[i].ViewComponent.IsVisible = show;
|
||||
if (_grid.previewSprites != null && i < _grid.previewSprites.Length && _grid.previewSprites[i] != null && ossuary && i >= GeoOssuary.VisibleFuelSlots)
|
||||
_grid.previewSprites[i].IsVisible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(XUiC_CollectorFuelGrid), nameof(XUiC_CollectorFuelGrid.SetTileEntity))]
|
||||
public static class Patch_XUiC_CollectorFuelGrid_SetTileEntity_GeoOssuary
|
||||
{
|
||||
public static void Postfix(XUiC_CollectorFuelGrid __instance) => GeoOssuaryFuelGrid.Apply(__instance);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(XUiC_CollectorFuelGrid), nameof(XUiC_CollectorFuelGrid.OnTileEntityChanged))]
|
||||
public static class Patch_XUiC_CollectorFuelGrid_OnTileEntityChanged_GeoOssuary
|
||||
{
|
||||
public static void Postfix(XUiC_CollectorFuelGrid __instance) => GeoOssuaryFuelGrid.Apply(__instance);
|
||||
}
|
||||
|
||||
/// <summary>Shift-клик топливом: у оссуария - только в видимую ячейку (ваниль раскладывает по всей сетке).</summary>
|
||||
[HarmonyPatch(typeof(XUiC_CollectorFuelGrid), nameof(XUiC_CollectorFuelGrid.TryAddFuel))]
|
||||
public static class Patch_XUiC_CollectorFuelGrid_TryAddFuel_GeoOssuary
|
||||
{
|
||||
public static bool Prefix(XUiC_CollectorFuelGrid __instance, ItemClass newItemClass, ItemStack newItemStack, ref bool __result)
|
||||
{
|
||||
if (__instance.fuelBehavior != XUiC_CollectorFuelGrid.FuelBehavior.Fuel || !GeoOssuary.Is(__instance.tileEntity))
|
||||
return true;
|
||||
if (System.Array.IndexOf(__instance.tileEntity.GetFuelTypes(), newItemClass.Name) >= 0)
|
||||
{
|
||||
XUiC_ItemStack slot = __instance.itemControllers[0];
|
||||
if (slot.ItemStack.IsEmpty())
|
||||
{
|
||||
slot.ItemStack = newItemStack.Clone();
|
||||
newItemStack.count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int n = slot.ItemStack.StackTransferCount(newItemStack);
|
||||
slot.ItemStack.count += n;
|
||||
newItemStack.count -= n;
|
||||
}
|
||||
__instance.UpdateBackend(__instance.getUISlots());
|
||||
}
|
||||
__result = newItemStack.count == 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Переход на котёл (#66, 22.10: «у уже поставленных оссуариев при первой загрузке новой версии всё лишнее из
|
||||
/// старых ячеек образцов и топлива переложить в новое хранилище - ничего не теряется»). Сервер на каждом тике смотрит
|
||||
/// спрятанные ячейки (образцы 2-3, топливо 2-6) и перекладывает их содержимое в свободные ячейки хранилища; нет места -
|
||||
/// ждёт, пока игрок заберёт добычу.</summary>
|
||||
[HarmonyPatch(typeof(TileEntityCollector), nameof(TileEntityCollector.UpdateTick))]
|
||||
public static class Patch_TileEntityCollector_UpdateTick_GeoOssuaryMigrate
|
||||
{
|
||||
public static void Postfix(TileEntityCollector __instance)
|
||||
{
|
||||
if (!GeoOssuary.Is(__instance) || !SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
|
||||
return;
|
||||
bool moved = Move(__instance, __instance.ModSlots, GeoOssuary.VisibleModSlots, "образца");
|
||||
moved |= Move(__instance, __instance.FuelSlots, GeoOssuary.VisibleFuelSlots, "топлива");
|
||||
if (moved)
|
||||
__instance.SetModified();
|
||||
}
|
||||
|
||||
static bool Move(TileEntityCollector _te, ItemStack[] _from, int _keep, string _what)
|
||||
{
|
||||
bool moved = false;
|
||||
ItemStack[] store = _te.Items;
|
||||
for (int i = _keep; i < _from.Length; i++)
|
||||
{
|
||||
if (_from[i] == null || _from[i].IsEmpty())
|
||||
continue;
|
||||
int free = System.Array.FindIndex(store, s => s == null || s.IsEmpty());
|
||||
if (free < 0)
|
||||
return moved;
|
||||
store[free] = _from[i].Clone();
|
||||
_from[i] = ItemStack.Empty.Clone();
|
||||
moved = true;
|
||||
Debug.Log("[NecromancerTome] GeoOssuary: " + store[free].itemValue.ItemClass?.Name + " x" + store[free].count +
|
||||
" из ячейки " + _what + " " + (i + 1) + " -> хранилище " + (free + 1) + " в " + _te.ToWorldPos());
|
||||
}
|
||||
return moved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Новый оссуарий-котёл (#67, 22.10.2026): свечение модели и отладочная команда necroossuary.
|
||||
/// Свечение - руны на котле, знаки на крышке, прах и кости внутри (карта _e, цвет - EmissionColor через MaterialPropertyBlock,
|
||||
/// общий материал не трогается) и свет glow внутри котла (дочерний объект префаба, выключен) - пока оссуарий исправно работает
|
||||
/// (22.10: «Когда оссуарий исправно работает, он должен подсвечиваться изнутри. Чтобы сразу было видно если что-то не так»;
|
||||
/// «Пусть руны тоже загораются»). Сфера в держателе (sphere) видна, пока в топливе есть сфера или камень. Следит компонент
|
||||
/// OssuaryVisual на модели - поэтому работает и у клиентов сервера. Выключатель - necroossuary off (модель как в префабе).
|
||||
/// </summary>
|
||||
public static class OssuaryGlow
|
||||
{
|
||||
public static bool Enabled = true;
|
||||
/// <summary>Дикий дух-огонёк (#69): летает, пока оссуарий работает. Выключатель - necroossuary wisp off.</summary>
|
||||
public static bool WispEnabled = true;
|
||||
public const string WispPath = "#@modfolder(NecromancerTome):Resources/necrowisp?necroWispPrefab.prefab";
|
||||
static GameObject _wispPrefab;
|
||||
static bool _wispTried;
|
||||
|
||||
public static GameObject WispPrefab()
|
||||
{
|
||||
if (!_wispTried)
|
||||
{
|
||||
_wispTried = true;
|
||||
_wispPrefab = DataLoader.LoadAsset<GameObject>(WispPath);
|
||||
Debug.Log("[NecromancerTome] OssuaryGlow: огонёк " + (_wispPrefab != null ? "загружен" : "НЕ загружен: " + WispPath));
|
||||
}
|
||||
return _wispPrefab;
|
||||
}
|
||||
|
||||
public static Color GlowColor = new Color(2.0f, 0.24f, 0.12f, 1f);
|
||||
static readonly MaterialPropertyBlock _block = new MaterialPropertyBlock();
|
||||
static readonly int EmissionColorId = Shader.PropertyToID("_EmissionColor");
|
||||
|
||||
public static Transform Model(World _w, Vector3i _pos)
|
||||
{
|
||||
Chunk chunk = _w.GetChunkFromWorldPos(_pos) as Chunk;
|
||||
BlockEntityData be = chunk?.GetBlockEntity(_pos);
|
||||
return be != null && be.bHasTransform ? be.transform : null;
|
||||
}
|
||||
|
||||
/// <summary>true - модель найдена и переключена.</summary>
|
||||
public static bool Set(Transform _model, bool _on, bool _sphere = true)
|
||||
{
|
||||
if (_model == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
foreach (string part in new[] { "body", "lid" })
|
||||
{
|
||||
Transform t = _model.Find(part);
|
||||
Renderer r = t != null ? t.GetComponent<Renderer>() : null;
|
||||
if (r == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
r.GetPropertyBlock(_block);
|
||||
_block.SetColor(EmissionColorId, _on ? GlowColor : Color.black);
|
||||
r.SetPropertyBlock(_block);
|
||||
}
|
||||
Transform glow = _model.Find("glow");
|
||||
if (glow != null)
|
||||
{
|
||||
glow.gameObject.SetActive(_on);
|
||||
}
|
||||
Transform sphere = _model.Find("sphere");
|
||||
if (sphere != null)
|
||||
{
|
||||
sphere.gameObject.SetActive(_sphere);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Оссуарии (главные блоки) в кубе вокруг точки.</summary>
|
||||
public static List<Vector3i> Find(World _w, Vector3i _c, int _r, int _ry)
|
||||
{
|
||||
var res = new List<Vector3i>();
|
||||
for (int x = _c.x - _r; x <= _c.x + _r; x++)
|
||||
{
|
||||
for (int z = _c.z - _r; z <= _c.z + _r; z++)
|
||||
{
|
||||
for (int y = Mathf.Max(1, _c.y - _ry); y <= Mathf.Min(254, _c.y + _ry); y++)
|
||||
{
|
||||
var p = new Vector3i(x, y, z);
|
||||
BlockValue bv = _w.GetBlock(p);
|
||||
if (!bv.ischild && GeoOssuary.Is(bv.Block))
|
||||
{
|
||||
res.Add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>На модели оссуария: два раза в секунду смотрит на коллектор и переключает сферу и свечение, только при смене.
|
||||
/// anyEnabled - ванильная проверка «есть рабочий выход»: учитывает топливо, заполненный выход, воду и пустые ячейки
|
||||
/// модификаций (GeoOssuaryPatch.isDisabled).</summary>
|
||||
public class OssuaryVisual : MonoBehaviour
|
||||
{
|
||||
public Vector3i Pos;
|
||||
float _next;
|
||||
int _state = -1;
|
||||
|
||||
void Update()
|
||||
{
|
||||
_wisp.Tick(transform, _state > 0 && (_state & 1) != 0 && OssuaryGlow.WispEnabled); // -1 - ещё не смотрели
|
||||
if (Time.time < _next)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_next = Time.time + 0.5f;
|
||||
World w = GameManager.Instance?.World;
|
||||
if (w == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int st; // бит 1 - светится, бит 2 - сфера видна; выключатель - как в префабе (2)
|
||||
if (OssuaryGlow.Enabled)
|
||||
{
|
||||
st = 0;
|
||||
if (w.GetTileEntity(Pos) is TileEntityCollector te)
|
||||
{
|
||||
bool fuel = false;
|
||||
foreach (ItemStack s in te.FuelSlots)
|
||||
{
|
||||
if (s != null && !s.IsEmpty())
|
||||
{
|
||||
fuel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
st = (fuel ? 2 : 0) + (fuel && te.anyEnabled() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
st = 2;
|
||||
}
|
||||
if (st == _state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_state = st;
|
||||
OssuaryGlow.Set(transform, (st & 1) != 0, (st & 2) != 0);
|
||||
}
|
||||
|
||||
readonly OssuaryWisp _wisp = new OssuaryWisp();
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
_wisp.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Дикий дух оссуария (#69; 20.10: «Хватит одного огонька. Пусть светится сам. Производительность нам важна»):
|
||||
/// чистая анимация, без сущностей (22.10: «периодически из окошка в котле вылетает дух и уходит вниз, увеличивая прозрачность
|
||||
/// до полной, а через какое-то время с другой стороны снизу вылетает полностью прозрачный дух, плавно уменьшая прозрачность,
|
||||
/// и влетает в противоположное окошко»). Один огонёк на котёл (бандл necrowisp), только пока оссуарий исправно работает
|
||||
/// и игрок ближе WispViewRange. На добычу не влияет.</summary>
|
||||
public class OssuaryWisp
|
||||
{
|
||||
public const float WispViewRange = 60f;
|
||||
/// <summary>Окошки крышки (модель #67): шесть, между швами, через 60°; по высоте - на WindowUp от низа крышки,
|
||||
/// от оси - на WindowR (доли полуширины крышки; в модели 0.14 м и 0.37 м при полуширине 0.418).</summary>
|
||||
const float WindowUp = 0.14f / 0.836f, WindowR = 0.37f / 0.418f;
|
||||
const float Out = 0.9f; // насколько дальше от оси котла дух уходит в землю, м
|
||||
const float Under = 0.35f; // насколько под землю, м
|
||||
const float FlyTime = 2.4f;
|
||||
static readonly int TintId = Shader.PropertyToID("_TintColor");
|
||||
static readonly int ColorId = Shader.PropertyToID("_Color");
|
||||
static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock();
|
||||
GameObject _go;
|
||||
Renderer[] _rs;
|
||||
Color[] _base;
|
||||
TrailRenderer _trail;
|
||||
int _phase = -1; // -1 дома, 0 вылет и уход в землю, 1 под землёй, 2 подъём и влёт
|
||||
float _t0, _until;
|
||||
Vector3 _winOut, _winIn, _inOut, _inIn, _down, _up;
|
||||
|
||||
static float Ease(float _k) => _k * _k * (3f - 2f * _k);
|
||||
|
||||
public void Tick(Transform _model, bool _work)
|
||||
{
|
||||
EntityPlayerLocal pl = GameManager.Instance?.World?.GetPrimaryPlayer();
|
||||
bool near = pl != null && (pl.position - Origin.position - _model.position).sqrMagnitude < WispViewRange * WispViewRange;
|
||||
float now = Time.time;
|
||||
if (!_work || !near)
|
||||
{
|
||||
if (_phase >= 0)
|
||||
{
|
||||
Hide();
|
||||
_phase = -1;
|
||||
}
|
||||
_until = Mathf.Max(_until, now + Random.Range(1f, 4f));
|
||||
return;
|
||||
}
|
||||
if (_go == null && !Spawn(_model))
|
||||
{
|
||||
return;
|
||||
}
|
||||
float k = Mathf.Clamp01((now - _t0) / Mathf.Max(0.01f, _until - _t0));
|
||||
switch (_phase)
|
||||
{
|
||||
case -1:
|
||||
if (now < _until || !Places(_model))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Show(_inOut);
|
||||
Next(0, FlyTime);
|
||||
return;
|
||||
case 0: // из окошка наружу и вниз, в землю; тает
|
||||
Fly(_inOut, _winOut, _down, k);
|
||||
Alpha(1f - Ease(Mathf.InverseLerp(0.25f, 1f, k)));
|
||||
if (k >= 1f)
|
||||
{
|
||||
Hide();
|
||||
Next(1, Random.Range(12f, 20f));
|
||||
}
|
||||
return;
|
||||
case 1: // под землёй
|
||||
if (now >= _until)
|
||||
{
|
||||
Alpha(0f);
|
||||
Show(_up);
|
||||
Next(2, FlyTime);
|
||||
}
|
||||
return;
|
||||
case 2: // с другой стороны снизу вверх, проявляется, влетает в противоположное окошко
|
||||
Fly(_up, _winIn, _inIn, k);
|
||||
Alpha(Ease(Mathf.InverseLerp(0f, 0.75f, k)));
|
||||
if (k >= 1f)
|
||||
{
|
||||
Hide();
|
||||
_phase = -1;
|
||||
_until = now + Random.Range(3f, 6f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Next(int _p, float _dur)
|
||||
{
|
||||
_phase = _p;
|
||||
_t0 = Time.time;
|
||||
_until = _t0 + _dur;
|
||||
}
|
||||
|
||||
/// <summary>Квадратичная кривая _a -> _b -> _c: проходит окошко _b по касательной.</summary>
|
||||
void Fly(Vector3 _a, Vector3 _b, Vector3 _c, float _k)
|
||||
{
|
||||
float k = Ease(_k);
|
||||
Vector3 p = (1f - k) * (1f - k) * _a + 2f * k * (1f - k) * _b + k * k * _c;
|
||||
p.y += 0.02f * Mathf.Sin(Time.time * 7f); // лёгкое дрожание огонька
|
||||
_go.transform.position = p;
|
||||
}
|
||||
|
||||
/// <summary>Окошко вылета - случайное из шести, влёта - напротив; точки в земле - снаружи от них, высота земли - лучом.</summary>
|
||||
bool Places(Transform _model)
|
||||
{
|
||||
Transform lid = _model.Find("lid");
|
||||
Renderer lr = lid != null ? lid.GetComponent<Renderer>() : null;
|
||||
if (lr == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Bounds b = lr.bounds;
|
||||
Vector3 axis = new Vector3(b.center.x, b.min.y + WindowUp * (b.size.x + b.size.z) * 0.5f, b.center.z);
|
||||
float half = (b.size.x + b.size.z) * 0.25f;
|
||||
int w = Random.Range(0, 6);
|
||||
Vector3 dOut = lid.rotation * Quaternion.Euler(0f, w * 60f, 0f) * Vector3.right;
|
||||
dOut.y = 0f;
|
||||
dOut.Normalize();
|
||||
Vector3 dIn = -dOut;
|
||||
_winOut = axis + dOut * (half * WindowR);
|
||||
_winIn = axis + dIn * (half * WindowR);
|
||||
_inOut = axis + dOut * (half * WindowR * 0.4f);
|
||||
_inIn = axis + dIn * (half * WindowR * 0.4f);
|
||||
_down = Ground(axis + dOut * (half + Out)) + Vector3.down * Under;
|
||||
_up = Ground(axis + dIn * (half + Out)) + Vector3.down * Under;
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector3 Ground(Vector3 _p)
|
||||
{
|
||||
if (Physics.Raycast(_p + Vector3.up * 1.5f, Vector3.down, out RaycastHit hit, 8f, SnakeControl.WallMask, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
_p.y = hit.point.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_p.y -= 1f;
|
||||
}
|
||||
return _p;
|
||||
}
|
||||
|
||||
bool Spawn(Transform _model)
|
||||
{
|
||||
GameObject prefab = OssuaryGlow.WispPrefab();
|
||||
if (prefab == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_go = Object.Instantiate(prefab, _model, false);
|
||||
_go.name = "NecroOssuaryWisp";
|
||||
_trail = _go.GetComponent<TrailRenderer>();
|
||||
_rs = _go.GetComponentsInChildren<Renderer>(true);
|
||||
_base = new Color[_rs.Length];
|
||||
for (int i = 0; i < _rs.Length; i++)
|
||||
{
|
||||
Material m = _rs[i].sharedMaterial;
|
||||
_base[i] = m == null ? Color.white : m.HasProperty(TintId) ? m.GetColor(TintId) : m.HasProperty(ColorId) ? m.GetColor(ColorId) : Color.white;
|
||||
}
|
||||
_go.SetActive(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Прозрачность - цветом материала через MaterialPropertyBlock (шейдер аддитивный: тусклее = прозрачнее),
|
||||
/// общий материал не трогается.</summary>
|
||||
void Alpha(float _a)
|
||||
{
|
||||
for (int i = 0; i < _rs.Length; i++)
|
||||
{
|
||||
Material m = _rs[i].sharedMaterial;
|
||||
if (m == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Color c = _base[i] * _a;
|
||||
_rs[i].GetPropertyBlock(_mpb);
|
||||
if (m.HasProperty(TintId))
|
||||
{
|
||||
_mpb.SetColor(TintId, c);
|
||||
}
|
||||
if (m.HasProperty(ColorId))
|
||||
{
|
||||
_mpb.SetColor(ColorId, c);
|
||||
}
|
||||
_rs[i].SetPropertyBlock(_mpb);
|
||||
}
|
||||
}
|
||||
|
||||
void Show(Vector3 _at)
|
||||
{
|
||||
_go.transform.position = _at;
|
||||
if (_trail != null)
|
||||
{
|
||||
_trail.Clear();
|
||||
}
|
||||
_go.SetActive(true);
|
||||
if (_phase != 1)
|
||||
{
|
||||
Alpha(1f);
|
||||
}
|
||||
}
|
||||
|
||||
void Hide()
|
||||
{
|
||||
if (_go != null)
|
||||
{
|
||||
_go.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (_go != null)
|
||||
{
|
||||
Object.Destroy(_go);
|
||||
_go = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Модель оссуария появилась в мире - повесить на неё OssuaryVisual.</summary>
|
||||
[HarmonyPatch(typeof(BlockCollector), nameof(BlockCollector.OnBlockEntityTransformBeforeActivated))]
|
||||
public static class Patch_BlockCollector_OnBlockEntityTransformBeforeActivated_OssuaryVisual
|
||||
{
|
||||
public static void Postfix(Vector3i _blockPos, BlockValue _blockValue, BlockEntityData _ebcd)
|
||||
{
|
||||
if (!GeoOssuary.Is(_blockValue.Block) || _ebcd?.transform == null || _ebcd.transform.Find("body") == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
OssuaryVisual v = _ebcd.transform.GetComponent<OssuaryVisual>() ?? _ebcd.transform.gameObject.AddComponent<OssuaryVisual>();
|
||||
v.Pos = _blockPos;
|
||||
}
|
||||
}
|
||||
|
||||
public class ConsoleCmdNecroOssuary : ConsoleCmdAbstract
|
||||
{
|
||||
public override bool IsExecuteOnClient => true;
|
||||
|
||||
public override bool AllowedInMainMenu => false;
|
||||
|
||||
public override string[] getCommands()
|
||||
{
|
||||
return new string[] { "necroossuary" };
|
||||
}
|
||||
|
||||
public override string getDescription()
|
||||
{
|
||||
return "Necromancer's Tome: cauldron ossuary - find, remove, sphere/glow switch.";
|
||||
}
|
||||
|
||||
public override string getHelp()
|
||||
{
|
||||
return "necroossuary find [r] - ossuaries around the player (r blocks, default 30)\n" +
|
||||
"necroossuary remove x y z - remove the ossuary at x y z (only if it is an ossuary)\n" +
|
||||
"necroossuary on|off - off: sphere and glow no longer follow the ossuary state\n" +
|
||||
"necroossuary wisp on|off - wild spirit wisp flying while the ossuary works\n" +
|
||||
"necroossuary info x y z - slots of the ossuary (samples, fuel, storage) and whether it works\n" +
|
||||
"necroossuary put x y z mod|fuel <slot 1..> <item> [count] - dev: put an item into a slot, hidden ones too";
|
||||
}
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
SdtdConsole con = SingletonMonoBehaviour<SdtdConsole>.Instance;
|
||||
World w = GameManager.Instance?.World;
|
||||
EntityPlayerLocal pl = w?.GetPrimaryPlayer();
|
||||
if (w == null || pl == null || _params.Count == 0)
|
||||
{
|
||||
con.Output(getHelp());
|
||||
return;
|
||||
}
|
||||
Vector3i me = World.worldToBlockPos(pl.position);
|
||||
switch (_params[0])
|
||||
{
|
||||
case "find":
|
||||
{
|
||||
int r = _params.Count > 1 ? int.Parse(_params[1]) : 30;
|
||||
List<Vector3i> found = OssuaryGlow.Find(w, me, r, 20);
|
||||
con.Output("necroossuary: найдено " + found.Count + (found.Count > 0 ? ": " + string.Join("; ", found) : ""));
|
||||
break;
|
||||
}
|
||||
case "remove":
|
||||
{
|
||||
var p = new Vector3i(int.Parse(_params[1]), int.Parse(_params[2]), int.Parse(_params[3]));
|
||||
BlockValue bv = w.GetBlock(p);
|
||||
if (!GeoOssuary.Is(bv.Block))
|
||||
{
|
||||
con.Output("necroossuary: в " + p + " не оссуарий (" + bv.Block?.GetBlockName() + ") - не трогаю");
|
||||
break;
|
||||
}
|
||||
if (bv.ischild)
|
||||
{
|
||||
p = p + bv.parent;
|
||||
}
|
||||
w.SetBlockRPC(p, BlockValue.Air);
|
||||
con.Output("necroossuary: убран оссуарий в " + p);
|
||||
break;
|
||||
}
|
||||
case "info":
|
||||
case "put":
|
||||
{
|
||||
var p = new Vector3i(int.Parse(_params[1]), int.Parse(_params[2]), int.Parse(_params[3]));
|
||||
BlockValue bv = w.GetBlock(p);
|
||||
if (bv.ischild)
|
||||
{
|
||||
p = p + bv.parent;
|
||||
}
|
||||
if (!(w.GetTileEntity(p) is TileEntityCollector te) || !GeoOssuary.Is(te))
|
||||
{
|
||||
con.Output("necroossuary: в " + p + " не оссуарий");
|
||||
break;
|
||||
}
|
||||
if (_params[0] == "put")
|
||||
{
|
||||
ItemStack[] slots = _params[4] == "fuel" ? te.FuelSlots : te.ModSlots;
|
||||
int i = int.Parse(_params[5]) - 1;
|
||||
ItemValue iv = ItemClass.GetItem(_params[6]);
|
||||
if (i < 0 || i >= slots.Length || iv == null || iv.IsEmpty())
|
||||
{
|
||||
con.Output("necroossuary: нет ячейки " + (i + 1) + " или предмета " + _params[6]);
|
||||
break;
|
||||
}
|
||||
slots[i] = new ItemStack(iv, _params.Count > 7 ? int.Parse(_params[7]) : 1);
|
||||
te.SetModified();
|
||||
}
|
||||
string S(ItemStack[] _a)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
for (int i = 0; i < _a.Length; i++)
|
||||
{
|
||||
parts.Add(_a[i] == null || _a[i].IsEmpty() ? "-" : _a[i].itemValue.ItemClass?.Name + " x" + _a[i].count);
|
||||
}
|
||||
return string.Join(", ", parts);
|
||||
}
|
||||
con.Output("necroossuary " + p + ": образцы [" + S(te.ModSlots) + "]; топливо [" + S(te.FuelSlots) + "]; хранилище [" +
|
||||
S(te.Items) + "]; работает " + te.anyEnabled());
|
||||
break;
|
||||
}
|
||||
case "wisp":
|
||||
if (_params.Count > 1)
|
||||
{
|
||||
OssuaryGlow.WispEnabled = _params[1] == "on";
|
||||
}
|
||||
con.Output("necroossuary: огонёк " + (OssuaryGlow.WispEnabled ? "летает" : "выключен"));
|
||||
break;
|
||||
case "on":
|
||||
case "off":
|
||||
OssuaryGlow.Enabled = _params[0] == "on";
|
||||
con.Output("necroossuary: сфера и свечение по работе " + (OssuaryGlow.Enabled ? "включены" : "выключены (модель как в префабе)"));
|
||||
break;
|
||||
default:
|
||||
con.Output(getHelp());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user