Урна necroCinerariumProspector (и ...Skull): в «Содержимом» череп и образец - один из девяти ресурсов (камень, глина, песок, древесина, железо, свинец, уголь, селитра, сланец), нить к ящику. Дух отлетает на соседнюю свободную клетку и уходит на 5 блоков под землю, растворяясь; раз в игровой час поднимается так же, кладёт добычу в ящик и уходит снова. За сутки: камень/глина/песок/древесина 30000, руды 18000, ходка ±20%. Из руды - шанс на алмаз, серебро, золото по шансам игры на каждый «блок» руды. Ящик полон - дух ждёт с добычей. Без зоны и вне лимита урн. Рецепт - кузня: латунь 40, прах 100, кости 10, кровь/сок, железная кирка; открывается с остальными урнами. Консоль necroprospector: on/off, trip, yield, spread, depth, состояние. Тексты - 13 языков; глава книги «Цинерарии» - лист Fig. VIII с шестой урной и фраза про prospektisto. Проверено в игре (test8): ходка железа 625 в ящике, спуск и подъём духа, лист и страница книги помещаются. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
651 lines
22 KiB
C#
651 lines
22 KiB
C#
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using UnityEngine;
|
||
using UnityEngine.Scripting;
|
||
|
||
namespace NecromancerTome
|
||
{
|
||
/// <summary>
|
||
/// Цинерарий-старатель (#64, решения 20.10 и 22.10.2026): дух добывает ископаемые. В «Содержимом» две ячейки, как у рабочего:
|
||
/// череп и образец - что добывать (один из Ores). Нить к ящику (любая, как у лутера) - куда класть. Есть всё - дух отлетает на
|
||
/// ближайшую свободную клетку рядом с урной и уходит на ProspectorDepth блоков вниз, растворяясь; раз в игровой час поднимается
|
||
/// так же, кладёт добычу в ящик и уходит снова. Без зоны и вне лимита урн, как остальные некротехнологи.
|
||
///
|
||
/// Добыча (20.10: «цена - скоростью, а не ресурсом»): за игровые сутки камень, глина, песок, древесина - 5 стопок по 6000,
|
||
/// железо, свинец, уголь, селитра, сланец - 3 стопки; ходка - сутки / 24, разброс ±20%. Находки (20.10: «шанс добыть алмаз,
|
||
/// серебро и золото, как и в игре»; 22.10 - всегда, без книги «Удачный удар», латуни нет): ходка руды = столько блоков, сколько
|
||
/// даёт её выход с блока в игре (железо 105, прочие 50), на каждый - шансы игры (алмаз 0.5%, серебро 1%, золото 0.7%).
|
||
///
|
||
/// Время - игровое (worldTime), чтобы добыча за сутки не зависела от их длины. Ходка засчитывается, только пока чанк урны
|
||
/// загружен; время перескочило больше чем на час (сон, прилив, settime) - ходка не засчитана, отсчёт заново. Ящик полон или
|
||
/// открыт игроком - дух ждёт дома с добычей (почернел, если полон) и докладывает, как появится место; новой ходки нет.
|
||
/// Добыча в руках духа не сохраняется: урну подняли или чанк выгрузился - ходка пропала.
|
||
/// </summary>
|
||
public partial class TEFeatureCinerarium
|
||
{
|
||
public const string ProspectorBlockName = "necroCinerariumProspector";
|
||
public const string ProspectorSkullBlockName = "necroCinerariumProspectorSkull";
|
||
|
||
public static bool IsProspectorBlock(Block _b)
|
||
{
|
||
string n = _b?.GetBlockName();
|
||
return n == ProspectorBlockName || n == ProspectorSkullBlockName;
|
||
}
|
||
|
||
bool _prospector;
|
||
|
||
public bool IsProspector
|
||
{
|
||
get
|
||
{
|
||
_ = IsLogist; // узнаёт тип блока
|
||
return _prospector;
|
||
}
|
||
}
|
||
|
||
/// <summary>Что добывает старатель: предмет, добыча за игровые сутки, выход с блока руды в игре (0 - находок нет).</summary>
|
||
public static readonly (string item, int perDay, int perBlock)[] Ores =
|
||
{
|
||
("resourceRockSmall", 30000, 0),
|
||
("resourceClayLump", 30000, 0),
|
||
("resourceCrushedSand", 30000, 0),
|
||
("resourceWood", 30000, 0),
|
||
("resourceScrapIron", 18000, 105),
|
||
("resourceScrapLead", 18000, 50),
|
||
("resourceCoal", 18000, 50),
|
||
("resourcePotassiumNitratePowder", 18000, 50),
|
||
("resourceOilShale", 18000, 50),
|
||
};
|
||
|
||
/// <summary>Находки с блока руды - как в blocks.xml игры (тег artOfMiningLuckyStrikeHarvest).</summary>
|
||
public static readonly (string item, float prob)[] Finds =
|
||
{
|
||
("resourceRawDiamond", 0.005f),
|
||
("resourceSilverNugget", 0.01f),
|
||
("resourceGoldNugget", 0.007f),
|
||
};
|
||
|
||
/// <summary>Выключатель (консоль necroprospector off): духи остаются дома, ходок нет.</summary>
|
||
public static bool ProspectorOn = true;
|
||
/// <summary>Множитель добычи за ходку (консоль, для проверки баланса).</summary>
|
||
public static float ProspectorYield = 1f;
|
||
public static float ProspectorSpread = 0.2f;
|
||
/// <summary>На сколько блоков дух уходит вниз.</summary>
|
||
public static int ProspectorDepth = 5;
|
||
/// <summary>Ходка - игровой час (1000 единиц worldTime).</summary>
|
||
public const ulong TripTicks = 1000UL;
|
||
/// <summary>necroprospector trip - номер «внеочередной ходки»: урна, ещё не видевшая его, ходит сразу.</summary>
|
||
public static int ForceTrip;
|
||
|
||
public enum ProspectorStatus
|
||
{
|
||
NoSkull,
|
||
Tide,
|
||
Off,
|
||
NoSample,
|
||
NoChest,
|
||
Full,
|
||
Digging
|
||
}
|
||
|
||
bool _under;
|
||
ulong _tripAt;
|
||
float _nextProspect;
|
||
float _deliverAt;
|
||
int _forceSeen;
|
||
readonly List<ItemStack> _carry = new List<ItemStack>();
|
||
|
||
public static int OreIndex(ItemValue _iv)
|
||
{
|
||
string n = _iv?.ItemClass?.GetItemName();
|
||
if (n == null)
|
||
{
|
||
return -1;
|
||
}
|
||
for (int i = 0; i < Ores.Length; i++)
|
||
{
|
||
if (Ores[i].item == n)
|
||
{
|
||
return i;
|
||
}
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
public static bool IsOreSample(ItemStack _s) => _s != null && !_s.IsEmpty() && OreIndex(_s.itemValue) >= 0;
|
||
|
||
/// <summary>Сколько игровых минут до конца ходки (для консоли), -1 - дух не под землёй.</summary>
|
||
public int TripMinutesLeft(World _w)
|
||
{
|
||
if (!_under || _w == null)
|
||
{
|
||
return -1;
|
||
}
|
||
return _tripAt > _w.worldTime ? (int)((_tripAt - _w.worldTime) * 60UL / TripTicks) : 0;
|
||
}
|
||
|
||
public int CarryCount
|
||
{
|
||
get
|
||
{
|
||
int n = 0;
|
||
foreach (ItemStack s in _carry)
|
||
{
|
||
n += s.count;
|
||
}
|
||
return n;
|
||
}
|
||
}
|
||
|
||
public ProspectorStatus ProspectorNow(World _world)
|
||
{
|
||
if (!HasSkull)
|
||
{
|
||
return ProspectorStatus.NoSkull;
|
||
}
|
||
if (CinerariumLoad.Tide)
|
||
{
|
||
return ProspectorStatus.Tide;
|
||
}
|
||
if (!ProspectorOn)
|
||
{
|
||
return ProspectorStatus.Off;
|
||
}
|
||
if (!IsOreSample(Sample))
|
||
{
|
||
return ProspectorStatus.NoSample;
|
||
}
|
||
if (!HasThreads)
|
||
{
|
||
return ProspectorStatus.NoChest;
|
||
}
|
||
return Stuck ? ProspectorStatus.Full : ProspectorStatus.Digging;
|
||
}
|
||
|
||
string ProspectorStateText(World _world)
|
||
{
|
||
string key = ProspectorNow(_world) switch
|
||
{
|
||
ProspectorStatus.Tide => "necroZoneUrnTide",
|
||
ProspectorStatus.NoSample => "necroProspectorNoSample",
|
||
ProspectorStatus.NoChest => "necroProspectorNoChest",
|
||
ProspectorStatus.Full => "necroZoneUrnFull",
|
||
ProspectorStatus.Digging => "necroProspectorDigging",
|
||
_ => null
|
||
};
|
||
return key != null ? ", " + Localization.Get(key) : "";
|
||
}
|
||
|
||
/// <summary>Работа старателя (сервер, раз в секунду): донести добычу, уйти под землю, ходка по часам игры.</summary>
|
||
void ProspectorWork(World _world)
|
||
{
|
||
if (Time.time < _nextProspect)
|
||
{
|
||
return;
|
||
}
|
||
_nextProspect = Time.time + 1f;
|
||
CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent<CinerariumGenieMotion>() : null;
|
||
ulong now = _world.worldTime;
|
||
if (_carry.Count > 0)
|
||
{
|
||
if (Time.time < _deliverAt)
|
||
{
|
||
return; // дух ещё поднимается
|
||
}
|
||
Deliver(_world);
|
||
bool full = _carry.Count > 0;
|
||
if (full != Stuck)
|
||
{
|
||
Stuck = full;
|
||
Debug.Log("[NecromancerTome] Cinerarium: старатель " + ToWorldPos() + (full ? " - ящик полон, дух ждёт с добычей" : " - добыча в ящике"));
|
||
}
|
||
motion?.SetStuck(full);
|
||
if (full)
|
||
{
|
||
return;
|
||
}
|
||
}
|
||
ProspectorStatus st = ProspectorNow(_world);
|
||
if (st != ProspectorStatus.Digging)
|
||
{
|
||
if (_under)
|
||
{
|
||
_under = false;
|
||
motion?.Rise();
|
||
Debug.Log("[NecromancerTome] Cinerarium: дух старателя " + ToWorldPos() + " вернулся без добычи - " + st);
|
||
}
|
||
return;
|
||
}
|
||
if (!_under)
|
||
{
|
||
_under = true;
|
||
if (_tripAt <= now)
|
||
{
|
||
_tripAt = now + TripTicks;
|
||
}
|
||
_forceSeen = ForceTrip;
|
||
motion?.Dive(DigStand(_world) - Origin.position, ProspectorDepth);
|
||
Debug.Log("[NecromancerTome] Cinerarium: дух старателя " + ToWorldPos() + " ушёл под землю за " + Sample.itemValue.ItemClass?.GetItemName());
|
||
return;
|
||
}
|
||
if (motion != null && !motion.Digging)
|
||
{
|
||
motion.Bury(DigStand(_world) - Origin.position, ProspectorDepth); // дух появился заново (чанк, прилив) - сразу под землёй
|
||
}
|
||
bool forced = _forceSeen != ForceTrip;
|
||
if (now < _tripAt && !forced)
|
||
{
|
||
return;
|
||
}
|
||
_forceSeen = ForceTrip;
|
||
if (!forced && now > _tripAt + TripTicks)
|
||
{
|
||
_tripAt = now + TripTicks; // время перескочило - ходка не засчитана
|
||
Debug.Log("[NecromancerTome] Cinerarium: у старателя " + ToWorldPos() + " время перескочило - ходка заново");
|
||
return;
|
||
}
|
||
DigTrip(OreIndex(Sample.itemValue));
|
||
_tripAt = forced ? now + TripTicks : _tripAt + TripTicks;
|
||
_under = false; // поднимается; донесёт - уйдёт снова
|
||
_deliverAt = Time.time + (motion != null ? motion.Rise() : 0f);
|
||
}
|
||
|
||
/// <summary>Добыча одной ходки - в руки духа (_carry), стопками не больше игровых.</summary>
|
||
void DigTrip(int _ore)
|
||
{
|
||
if (_ore < 0)
|
||
{
|
||
return;
|
||
}
|
||
(string item, int perDay, int perBlock) o = Ores[_ore];
|
||
float mean = o.perDay / 24f * ProspectorYield;
|
||
int n = Mathf.Max(1, Mathf.RoundToInt(mean * Random.Range(1f - ProspectorSpread, 1f + ProspectorSpread)));
|
||
Carry(o.item, n);
|
||
string log = o.item + " x" + n;
|
||
if (o.perBlock > 0)
|
||
{
|
||
int blocks = Mathf.Max(1, Mathf.RoundToInt(n / (float)o.perBlock));
|
||
foreach ((string item, float prob) f in Finds)
|
||
{
|
||
int k = 0;
|
||
for (int b = 0; b < blocks; b++)
|
||
{
|
||
if (Random.value < f.prob)
|
||
{
|
||
k++;
|
||
}
|
||
}
|
||
if (k > 0)
|
||
{
|
||
Carry(f.item, k);
|
||
log += ", " + f.item + " x" + k;
|
||
}
|
||
}
|
||
log += " (блоков руды " + blocks + ")";
|
||
}
|
||
Debug.Log("[NecromancerTome] Cinerarium: старатель " + ToWorldPos() + " - ходка: " + log);
|
||
}
|
||
|
||
void Carry(string _item, int _n)
|
||
{
|
||
ItemValue iv = ItemClass.GetItem(_item);
|
||
if (iv == null || iv.type <= 0)
|
||
{
|
||
Debug.LogWarning("[NecromancerTome] Cinerarium: старатель - нет предмета " + _item);
|
||
return;
|
||
}
|
||
int max = Mathf.Max(1, iv.ItemClass.Stacknumber.Value);
|
||
while (_n > 0)
|
||
{
|
||
int k = Mathf.Min(_n, max);
|
||
_carry.Add(new ItemStack(iv.Clone(), k));
|
||
_n -= k;
|
||
}
|
||
}
|
||
|
||
/// <summary>Добычу - в ящики по нитям; что не влезло, остаётся у духа.</summary>
|
||
void Deliver(World _world)
|
||
{
|
||
List<TEFeatureStorage> boxes = new List<TEFeatureStorage>();
|
||
void Add(Vector3i _p)
|
||
{
|
||
TEFeatureStorage s = CinerariumWork.BoxAt(_world, _p);
|
||
if (s != null && !boxes.Contains(s))
|
||
{
|
||
boxes.Add(s);
|
||
}
|
||
}
|
||
if (HasThreadTo)
|
||
{
|
||
Add(ThreadTo);
|
||
}
|
||
foreach (Vector3i p in ThreadOut)
|
||
{
|
||
Add(p);
|
||
}
|
||
foreach (Vector3i p in ThreadFrom)
|
||
{
|
||
Add(p);
|
||
}
|
||
if (boxes.Count == 0)
|
||
{
|
||
return; // ящик открыт игроком или нитей нет - подождать
|
||
}
|
||
for (int i = _carry.Count - 1; i >= 0; i--)
|
||
{
|
||
ItemStack s = _carry[i];
|
||
CinerariumWork.Place(s, boxes);
|
||
if (s.count <= 0)
|
||
{
|
||
_carry.RemoveAt(i);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>Куда дух спускается (20.10: «отлетает от урны на ближайший свободный блок, опускается на 5 блоков вниз»): из
|
||
/// восьми соседних клеток - первая свободная (воздух или растение), сначала по сторонам; нет - над урной. Низ клетки, мир.</summary>
|
||
Vector3 DigStand(World _world)
|
||
{
|
||
Vector3i urn = ToWorldPos();
|
||
Vector3i[] dirs =
|
||
{
|
||
Vector3i.forward, Vector3i.right, Vector3i.back, Vector3i.left,
|
||
new Vector3i(1, 0, 1), new Vector3i(1, 0, -1), new Vector3i(-1, 0, -1), new Vector3i(-1, 0, 1)
|
||
};
|
||
foreach (Vector3i d in dirs)
|
||
{
|
||
BlockValue bv = _world.GetBlock(urn + d);
|
||
if (bv.isair || bv.Block is BlockPlant)
|
||
{
|
||
return World.blockToTransformPos(urn + d);
|
||
}
|
||
}
|
||
return World.blockToTransformPos(urn + Vector3i.up);
|
||
}
|
||
|
||
/// <summary>Урну убрали или выгрузили - добыча в руках духа пропадает.</summary>
|
||
void ProspectorReset()
|
||
{
|
||
if (_carry.Count > 0)
|
||
{
|
||
Debug.Log("[NecromancerTome] Cinerarium: старатель " + ToWorldPos() + " - недонесённая добыча пропала (" + CarryCount + ")");
|
||
}
|
||
_carry.Clear();
|
||
_under = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>Спуск духа старателя под землю и подъём (20.10: «опускается на 5 блоков вниз, постепенно становясь полностью
|
||
/// прозрачным; обратно - так же»). Яркость - MaterialPropertyBlock на рендерерах духа (материалы общие не трогаются): дух
|
||
/// светится сложением, поэтому «прозрачнее» = тусклее цвет и свечение; глаза гаснут на половине.</summary>
|
||
public partial class CinerariumGenieMotion
|
||
{
|
||
/// <summary>0 - нет; 1 - летит к месту и спускается; 2 - под землёй (не виден); 3 - поднимается и летит домой.</summary>
|
||
int _digMode;
|
||
float _digT;
|
||
Vector3 _digFrom, _digHover;
|
||
float _digDepth;
|
||
public static float SinkSeconds = 2.5f;
|
||
List<Renderer> _fadeRenderers;
|
||
static readonly MaterialPropertyBlock _fadeBlock = new MaterialPropertyBlock();
|
||
static readonly int ColorId = Shader.PropertyToID("_Color");
|
||
static readonly int EmissionId = Shader.PropertyToID("_EmissionColor");
|
||
float _fade = 1f;
|
||
|
||
/// <summary>Под землёй или на пути туда/обратно.</summary>
|
||
public bool Digging => _digMode != 0;
|
||
|
||
bool DigHidden => _digMode == 2;
|
||
|
||
/// <summary>Лететь к клетке _stand (низ клетки, координаты сцены) и уйти на _depth блоков вниз.</summary>
|
||
public void Dive(Vector3 _stand, int _depth)
|
||
{
|
||
if (transform.parent == null)
|
||
{
|
||
return;
|
||
}
|
||
_t = -1f;
|
||
Gesture(0f);
|
||
_digFrom = transform.localPosition;
|
||
_digHover = DigHover(_stand);
|
||
_digDepth = _depth;
|
||
_digMode = 1;
|
||
_digT = 0f;
|
||
}
|
||
|
||
/// <summary>Сразу под землёй (дух появился заново, а работа идёт).</summary>
|
||
public void Bury(Vector3 _stand, int _depth)
|
||
{
|
||
if (transform.parent == null)
|
||
{
|
||
return;
|
||
}
|
||
_digHover = DigHover(_stand);
|
||
_digDepth = _depth;
|
||
_digMode = 2;
|
||
transform.localPosition = _digHover + Vector3.down * _digDepth;
|
||
}
|
||
|
||
/// <summary>Подняться и вернуться к урне; через сколько секунд будет дома.</summary>
|
||
public float Rise()
|
||
{
|
||
if (_digMode == 0)
|
||
{
|
||
return 0f;
|
||
}
|
||
_digMode = 3;
|
||
_digT = 0f;
|
||
return SinkSeconds + Mathf.Max(BackSeconds, Vector3.Distance(_digHover, Home()) / FlySpeed);
|
||
}
|
||
|
||
Vector3 DigHover(Vector3 _stand)
|
||
{
|
||
return transform.parent.InverseTransformPoint(_stand) + new Vector3(0f, CinerariumGenie.OffsetY + Drop + 0.15f, 0f);
|
||
}
|
||
|
||
/// <summary>Шаг спуска/подъёма; false - дух не копает, обычное поведение.</summary>
|
||
bool DigUpdate(bool _asleep)
|
||
{
|
||
if (_digMode == 0)
|
||
{
|
||
return false;
|
||
}
|
||
if (_asleep)
|
||
{
|
||
// Уснул - без полёта: спуск сразу закончен, подъём - сразу дома.
|
||
if (_digMode == 1)
|
||
{
|
||
_digMode = 2;
|
||
}
|
||
else if (_digMode == 3)
|
||
{
|
||
_digMode = 0;
|
||
SetFade(1f);
|
||
transform.localPosition = Home();
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
_digT += Time.deltaTime;
|
||
Vector3 bottom = _digHover + Vector3.down * _digDepth;
|
||
float fly = Mathf.Max(OutSeconds, Vector3.Distance(_digFrom, _digHover) / FlySpeed);
|
||
if (_digMode == 1)
|
||
{
|
||
if (_digT < fly)
|
||
{
|
||
transform.localPosition = Vector3.Lerp(_digFrom, _digHover, Mathf.SmoothStep(0f, 1f, _digT / fly));
|
||
Face(_digHover - _digFrom);
|
||
SetFade(1f);
|
||
}
|
||
else if (_digT < fly + SinkSeconds)
|
||
{
|
||
float s = (_digT - fly) / SinkSeconds;
|
||
transform.localPosition = Vector3.Lerp(_digHover, bottom, Mathf.SmoothStep(0f, 1f, s));
|
||
SetFade(1f - s);
|
||
}
|
||
else
|
||
{
|
||
_digMode = 2;
|
||
transform.localPosition = bottom;
|
||
}
|
||
}
|
||
else if (_digMode == 2)
|
||
{
|
||
transform.localPosition = bottom;
|
||
}
|
||
else
|
||
{
|
||
Vector3 home = Home();
|
||
float back = Mathf.Max(BackSeconds, Vector3.Distance(_digHover, home) / FlySpeed);
|
||
if (_digT < SinkSeconds)
|
||
{
|
||
float s = _digT / SinkSeconds;
|
||
transform.localPosition = Vector3.Lerp(bottom, _digHover, Mathf.SmoothStep(0f, 1f, s));
|
||
SetFade(s);
|
||
}
|
||
else if (_digT < SinkSeconds + back)
|
||
{
|
||
SetFade(1f);
|
||
transform.localPosition = Vector3.Lerp(_digHover, home, Mathf.SmoothStep(0f, 1f, (_digT - SinkSeconds) / back));
|
||
Face(home - _digHover);
|
||
}
|
||
else
|
||
{
|
||
_digMode = 0;
|
||
SetFade(1f);
|
||
return false;
|
||
}
|
||
}
|
||
transform.localRotation = Quaternion.Euler(0f, _yaw, 0f);
|
||
return true;
|
||
}
|
||
|
||
/// <summary>Яркость духа долей _k (1 - как обычно, свойства сняты).</summary>
|
||
void SetFade(float _k)
|
||
{
|
||
_k = Mathf.Clamp01(_k);
|
||
if (Mathf.Abs(_k - _fade) < 0.01f && (_k < 1f || _fade >= 1f))
|
||
{
|
||
return;
|
||
}
|
||
_fade = _k;
|
||
if (_fadeRenderers == null)
|
||
{
|
||
_fadeRenderers = new List<Renderer>(GetComponentsInChildren<Renderer>(true));
|
||
}
|
||
foreach (Renderer r in _fadeRenderers)
|
||
{
|
||
if (r == null)
|
||
{
|
||
continue;
|
||
}
|
||
if (r.name.Contains("eye"))
|
||
{
|
||
r.forceRenderingOff = _k < 0.5f;
|
||
continue;
|
||
}
|
||
if (_k >= 1f)
|
||
{
|
||
r.SetPropertyBlock(null);
|
||
continue;
|
||
}
|
||
Material m = r.sharedMaterial;
|
||
if (m == null)
|
||
{
|
||
continue;
|
||
}
|
||
r.GetPropertyBlock(_fadeBlock);
|
||
if (m.HasProperty(ColorId))
|
||
{
|
||
_fadeBlock.SetColor(ColorId, m.GetColor(ColorId) * _k);
|
||
}
|
||
if (m.HasProperty(EmissionId))
|
||
{
|
||
_fadeBlock.SetColor(EmissionId, m.GetColor(EmissionId) * _k);
|
||
}
|
||
r.SetPropertyBlock(_fadeBlock);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>Консоль старателя: выключатель, внеочередная ходка, множитель добычи, состояние урн.</summary>
|
||
[Preserve]
|
||
public class ConsoleCmdNecroProspector : ConsoleCmdAbstract
|
||
{
|
||
public override bool IsExecuteOnClient => true;
|
||
|
||
public override bool AllowedInMainMenu => false;
|
||
|
||
public override string[] getCommands()
|
||
{
|
||
return new string[] { "necroprospector" };
|
||
}
|
||
|
||
public override string getDescription()
|
||
{
|
||
return "Necromancer's Tome: Prospector Cinerarium - switch, extra trip, yield multiplier, status.";
|
||
}
|
||
|
||
public override string getHelp()
|
||
{
|
||
return "necroprospector - status of every loaded prospector urn\n" +
|
||
"necroprospector on|off - off: spirits stay home, no trips\n" +
|
||
"necroprospector trip - every digging spirit comes back with a trip right now\n" +
|
||
"necroprospector yield <x> - trip size multiplier (1 = as designed)\n" +
|
||
"necroprospector spread <x> - random spread of a trip (0.2 = ±20%)\n" +
|
||
"necroprospector depth <n> - how many blocks the spirit sinks";
|
||
}
|
||
|
||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||
{
|
||
SdtdConsole con = SingletonMonoBehaviour<SdtdConsole>.Instance;
|
||
World w = GameManager.Instance?.World;
|
||
if (_params.Count >= 1)
|
||
{
|
||
string cmd = _params[0].ToLowerInvariant();
|
||
float v = 0f;
|
||
bool num = _params.Count >= 2 && float.TryParse(_params[1], NumberStyles.Float, CultureInfo.InvariantCulture, out v);
|
||
switch (cmd)
|
||
{
|
||
case "on":
|
||
case "off":
|
||
TEFeatureCinerarium.ProspectorOn = cmd == "on";
|
||
break;
|
||
case "trip":
|
||
TEFeatureCinerarium.ForceTrip++;
|
||
break;
|
||
case "yield" when num:
|
||
TEFeatureCinerarium.ProspectorYield = Mathf.Max(0f, v);
|
||
break;
|
||
case "spread" when num:
|
||
TEFeatureCinerarium.ProspectorSpread = Mathf.Clamp01(v);
|
||
break;
|
||
case "depth" when num:
|
||
TEFeatureCinerarium.ProspectorDepth = Mathf.Clamp((int)v, 1, 30);
|
||
break;
|
||
default:
|
||
con.Output("necroprospector: unknown '" + _params[0] + "' - see 'help necroprospector'");
|
||
return;
|
||
}
|
||
}
|
||
con.Output("necroprospector: " + (TEFeatureCinerarium.ProspectorOn ? "on" : "OFF") + ", yield x" + TEFeatureCinerarium.ProspectorYield.ToString("F2", CultureInfo.InvariantCulture) +
|
||
", spread " + TEFeatureCinerarium.ProspectorSpread.ToString("F2", CultureInfo.InvariantCulture) + ", depth " + TEFeatureCinerarium.ProspectorDepth);
|
||
if (w == null)
|
||
{
|
||
return;
|
||
}
|
||
foreach (Vector3i p in CinerariumRegistry.All())
|
||
{
|
||
TEFeatureCinerarium u = CinerariumThreads.UrnAt(w, p);
|
||
if (u == null || !u.IsProspector)
|
||
{
|
||
continue;
|
||
}
|
||
int left = u.TripMinutesLeft(w);
|
||
con.Output(" " + p + ": " + u.ProspectorNow(w) + ", sample " + (u.Sample == null || u.Sample.IsEmpty() ? "-" : u.Sample.itemValue.ItemClass?.GetItemName()) +
|
||
(left >= 0 ? ", trip in " + left + " game min" : "") + (u.CarryCount > 0 ? ", carrying " + u.CarryCount : ""));
|
||
}
|
||
}
|
||
}
|
||
}
|