Цинерарий-старатель (#64): дух добывает ископаемые
Урна 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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f1010f98b7
commit
0999fd3afc
File diff suppressed because one or more lines are too long
@@ -642,5 +642,21 @@
|
|||||||
<property name="CreativeMode" value="None"/>
|
<property name="CreativeMode" value="None"/>
|
||||||
<property name="DescriptionKey" value="necroCinerariumLootDesc"/>
|
<property name="DescriptionKey" value="necroCinerariumLootDesc"/>
|
||||||
</block>
|
</block>
|
||||||
|
<!-- Урна старателя (22.10.2026, #64; CinerariumProspector.cs): бронзовая катушка с золотой чеканкой на основе банки лутера
|
||||||
|
(BlenderWork/necroCinerariumProspector, necroCinerariumProspectorPrefab в бандле necrocinerarium). В «Содержимом» череп
|
||||||
|
и образец - что добывать; дух уходит под землю и раз в игровой час приносит добычу в ящик по нити. Без зоны и вне
|
||||||
|
лимита, как логист. -->
|
||||||
|
<block name="necroCinerariumProspector">
|
||||||
|
<property name="Extends" value="necroCinerarium"/>
|
||||||
|
<property name="CreativeMode" value="Player"/>
|
||||||
|
<property name="DescriptionKey" value="necroCinerariumProspectorDesc"/>
|
||||||
|
<property name="CustomIcon" value="necroCinerariumProspector"/>
|
||||||
|
<property name="Model" value="#@modfolder(NecromancerTome):Resources/necrocinerarium?necroCinerariumProspectorPrefab.prefab"/>
|
||||||
|
</block>
|
||||||
|
<block name="necroCinerariumProspectorSkull">
|
||||||
|
<property name="Extends" value="necroCinerariumProspector"/>
|
||||||
|
<property name="CreativeMode" value="None"/>
|
||||||
|
<property name="DescriptionKey" value="necroCinerariumProspectorDesc"/>
|
||||||
|
</block>
|
||||||
</append>
|
</append>
|
||||||
</config>
|
</config>
|
||||||
|
|||||||
@@ -181,7 +181,7 @@
|
|||||||
пользуется, вода сидит на necroNecromancyLvl20. -->
|
пользуется, вода сидит на necroNecromancyLvl20. -->
|
||||||
<unlock_entry item="necroModKnifeScavengersFeast,bookSummonRatSpirit" unlock_tier="3"/>
|
<unlock_entry item="necroModKnifeScavengersFeast,bookSummonRatSpirit" unlock_tier="3"/>
|
||||||
<unlock_entry item="necroModKnifeGravesRepose" unlock_tier="4"/>
|
<unlock_entry item="necroModKnifeGravesRepose" unlock_tier="4"/>
|
||||||
<unlock_entry item="necroModKnifeDarkSense,necroCinerarium,necroCinerariumLogist,necroCinerariumWorker,necroCinerariumRepair,necroCinerariumLoot" unlock_tier="5"/>
|
<unlock_entry item="necroModKnifeDarkSense,necroCinerarium,necroCinerariumLogist,necroCinerariumWorker,necroCinerariumRepair,necroCinerariumLoot,necroCinerariumProspector" unlock_tier="5"/>
|
||||||
</display_entry>
|
</display_entry>
|
||||||
<!-- Ступенчатая разблокировка внутри группы. ПЕРЕРАСПРЕДЕЛЕНО 2026-09-09: четыре из
|
<!-- Ступенчатая разблокировка внутри группы. ПЕРЕРАСПРЕДЕЛЕНО 2026-09-09: четыре из
|
||||||
шести модов ножа (вода/еда/покой/чутьё) уехали отсюда в группу 1 - см. большой
|
шести модов ножа (вода/еда/покой/чутьё) уехали отсюда в группу 1 - см. большой
|
||||||
|
|||||||
@@ -731,6 +731,36 @@
|
|||||||
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||||
<ingredient name="resourceLockPick" count="1"/>
|
<ingredient name="resourceLockPick" count="1"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
|
<!-- Урна старателя (22.10, #64) - бронзовая катушка: в кузне латунь 40 вместо железа и свинца, железная кирка вместо отмычки;
|
||||||
|
две версии кузни (плавильня / лом), на крови и на соке. Открывается вместе с остальными урнами. -->
|
||||||
|
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
||||||
|
<ingredient name="unit_brass" count="40"/>
|
||||||
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
|
<ingredient name="resourceBone" count="10"/>
|
||||||
|
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||||
|
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
||||||
|
</recipe>
|
||||||
|
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" material_based="true" tags="learnable,necroNecromancyLvl300,use_smelter">
|
||||||
|
<ingredient name="unit_brass" count="40"/>
|
||||||
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
|
<ingredient name="resourceBone" count="10"/>
|
||||||
|
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||||
|
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
||||||
|
</recipe>
|
||||||
|
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
||||||
|
<ingredient name="resourceScrapBrass" count="40"/>
|
||||||
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
|
<ingredient name="resourceBone" count="10"/>
|
||||||
|
<ingredient name="resourceNecromancerBlood" count="1"/>
|
||||||
|
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
||||||
|
</recipe>
|
||||||
|
<recipe name="necroCinerariumProspector" count="1" craft_area="forge" craft_time="60" tags="learnable,necroNecromancyLvl300,replace_smelter">
|
||||||
|
<ingredient name="resourceScrapBrass" count="40"/>
|
||||||
|
<ingredient name="resourceZombieAsh" count="100"/>
|
||||||
|
<ingredient name="resourceBone" count="10"/>
|
||||||
|
<ingredient name="resourceNecroBlackRoseJuice" count="1"/>
|
||||||
|
<ingredient name="meleeToolPickT1IronPickaxe" count="1"/>
|
||||||
|
</recipe>
|
||||||
</append>
|
</append>
|
||||||
<!-- Магическая пыль в ступке (18.10.2026): повреждённое хранилище - 4, целое - 30. -->
|
<!-- Магическая пыль в ступке (18.10.2026): повреждённое хранилище - 4, целое - 30. -->
|
||||||
<append xpath="/recipes">
|
<append xpath="/recipes">
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ namespace NecromancerTome
|
|||||||
|
|
||||||
/// <summary>Урна без зоны 5x5 (логист, рабочий, ремонтник, лутер): ставится вплотную к любым урнам, в лимит не входит,
|
/// <summary>Урна без зоны 5x5 (логист, рабочий, ремонтник, лутер): ставится вплотную к любым урнам, в лимит не входит,
|
||||||
/// работает по нитям. У ремонтника и лутера своя большая зона (CinerariumZone.cs) - зоны таких урн могут пересекаться.</summary>
|
/// работает по нитям. У ремонтника и лутера своя большая зона (CinerariumZone.cs) - зоны таких урн могут пересекаться.</summary>
|
||||||
public static bool IsZonelessBlock(Block _b) => IsLogistBlock(_b) || IsWorkerBlock(_b) || IsRepairBlock(_b) || IsLootBlock(_b);
|
public static bool IsZonelessBlock(Block _b) => IsLogistBlock(_b) || IsWorkerBlock(_b) || IsRepairBlock(_b) || IsLootBlock(_b) || IsProspectorBlock(_b);
|
||||||
public const string SkullItemName = "necroSkull";
|
public const string SkullItemName = "necroSkull";
|
||||||
public const int HalfWidth = 2;
|
public const int HalfWidth = 2;
|
||||||
public const int Below = 1;
|
public const int Below = 1;
|
||||||
@@ -103,7 +103,7 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
bool _worker;
|
bool _worker;
|
||||||
public bool IsZoneless => IsLogist || IsWorker || IsBigZone;
|
public bool IsZoneless => IsLogist || IsWorker || IsBigZone || IsProspector;
|
||||||
/// <summary>Урна-логист - по блоку (necroCinerariumLogist / ...Skull); узнаётся при постановке или по миру.</summary>
|
/// <summary>Урна-логист - по блоку (necroCinerariumLogist / ...Skull); узнаётся при постановке или по миру.</summary>
|
||||||
public bool IsLogist
|
public bool IsLogist
|
||||||
{
|
{
|
||||||
@@ -119,6 +119,7 @@ namespace NecromancerTome
|
|||||||
_worker = IsWorkerBlock(bv.Block);
|
_worker = IsWorkerBlock(bv.Block);
|
||||||
_repair = IsRepairBlock(bv.Block);
|
_repair = IsRepairBlock(bv.Block);
|
||||||
_loot = IsLootBlock(bv.Block);
|
_loot = IsLootBlock(bv.Block);
|
||||||
|
_prospector = IsProspectorBlock(bv.Block);
|
||||||
_logistKnown = true;
|
_logistKnown = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,16 +169,17 @@ namespace NecromancerTome
|
|||||||
_worker = IsWorkerBlock(_blockValue.Block);
|
_worker = IsWorkerBlock(_blockValue.Block);
|
||||||
_repair = IsRepairBlock(_blockValue.Block);
|
_repair = IsRepairBlock(_blockValue.Block);
|
||||||
_loot = IsLootBlock(_blockValue.Block);
|
_loot = IsLootBlock(_blockValue.Block);
|
||||||
|
_prospector = IsProspectorBlock(_blockValue.Block);
|
||||||
_logistKnown = true;
|
_logistKnown = true;
|
||||||
string placed = _blockValue.Block?.GetBlockName();
|
string placed = _blockValue.Block?.GetBlockName();
|
||||||
if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == LogistWallSkullBlockName || placed == WorkerSkullBlockName ||
|
if (placed == SkullBlockName || placed == LogistSkullBlockName || placed == LogistWallSkullBlockName || placed == WorkerSkullBlockName ||
|
||||||
placed == RepairSkullBlockName || placed == LootSkullBlockName)
|
placed == RepairSkullBlockName || placed == LootSkullBlockName || placed == ProspectorSkullBlockName)
|
||||||
{
|
{
|
||||||
HasSkull = true; // поставили урну, поднятую с черепом
|
HasSkull = true; // поставили урну, поднятую с черепом
|
||||||
}
|
}
|
||||||
if (ConnectionManager.Instance.IsServer)
|
if (ConnectionManager.Instance.IsServer)
|
||||||
{
|
{
|
||||||
CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist || _worker || _repair || _loot);
|
CinerariumRegistry.Add(_blockPos, Parent?.Owner, _logist || _worker || _repair || _loot || _prospector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +192,7 @@ namespace NecromancerTome
|
|||||||
RemoveGenie(false);
|
RemoveGenie(false);
|
||||||
CinerariumContents.CloseIfShowing(this);
|
CinerariumContents.CloseIfShowing(this);
|
||||||
ZoneStop(false);
|
ZoneStop(false);
|
||||||
|
ProspectorReset();
|
||||||
if (!ConnectionManager.Instance.IsServer)
|
if (!ConnectionManager.Instance.IsServer)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -224,6 +227,7 @@ namespace NecromancerTome
|
|||||||
_threadsDirty = true; // чанк вернётся - нити построить заново
|
_threadsDirty = true; // чанк вернётся - нити построить заново
|
||||||
RemoveGenie(false);
|
RemoveGenie(false);
|
||||||
CinerariumContents.CloseIfShowing(this);
|
CinerariumContents.CloseIfShowing(this);
|
||||||
|
ProspectorReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(PooledBinaryWriter _bw, StreamModeWrite _eStreamMode)
|
public override void Write(PooledBinaryWriter _bw, StreamModeWrite _eStreamMode)
|
||||||
@@ -447,7 +451,7 @@ namespace NecromancerTome
|
|||||||
base.GetActivationText(_world, _blockPos, _blockValue, _entityFocusing, _activateHotkeyMarkup, _focusedTileEntityName);
|
base.GetActivationText(_world, _blockPos, _blockValue, _entityFocusing, _activateHotkeyMarkup, _focusedTileEntityName);
|
||||||
string s = _activateHotkeyMarkup + " " + _blockValue.Block.GetLocalizedBlockName() + " - " +
|
string s = _activateHotkeyMarkup + " " + _blockValue.Block.GetLocalizedBlockName() + " - " +
|
||||||
Localization.Get(HasSkull ? "necroCinerariumStateSkull" : "necroCinerariumStateEmpty");
|
Localization.Get(HasSkull ? "necroCinerariumStateSkull" : "necroCinerariumStateEmpty");
|
||||||
return IsBigZone ? s + ZoneStateText() : s;
|
return IsBigZone ? s + ZoneStateText() : IsProspector ? s + ProspectorStateText(_world as World) : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateTick(World _world)
|
public override void UpdateTick(World _world)
|
||||||
@@ -752,6 +756,11 @@ namespace NecromancerTome
|
|||||||
ZoneWork(_world); // ремонтник, лутер - CinerariumZone.cs
|
ZoneWork(_world); // ремонтник, лутер - CinerariumZone.cs
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (IsProspector)
|
||||||
|
{
|
||||||
|
ProspectorWork(_world); // старатель - CinerariumProspector.cs
|
||||||
|
return;
|
||||||
|
}
|
||||||
CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent<CinerariumGenieMotion>() : null;
|
CinerariumGenieMotion motion = _genie != null ? _genie.GetComponent<CinerariumGenieMotion>() : null;
|
||||||
if (_pending)
|
if (_pending)
|
||||||
{
|
{
|
||||||
@@ -929,7 +938,7 @@ namespace NecromancerTome
|
|||||||
logist = w != null && IsLogistWallBlock(w.GetBlock(ToWorldPos()).Block) ? 2 : 1;
|
logist = w != null && IsLogistWallBlock(w.GetBlock(ToWorldPos()).Block) ? 2 : 1;
|
||||||
}
|
}
|
||||||
_genie = CinerariumGenie.Spawn(ModelTransform(), IsWorker ? CinerariumGenie.WorkerDrop : 0f, logist);
|
_genie = CinerariumGenie.Spawn(ModelTransform(), IsWorker ? CinerariumGenie.WorkerDrop : 0f, logist);
|
||||||
if (_genie != null && IsLooter)
|
if (_genie != null && (IsLooter || IsProspector)) // старатель - на банке лутера
|
||||||
{
|
{
|
||||||
_genie.GetComponent<CinerariumGenieMotion>().ForwardOverride = CinerariumGenie.LootForward;
|
_genie.GetComponent<CinerariumGenieMotion>().ForwardOverride = CinerariumGenie.LootForward;
|
||||||
}
|
}
|
||||||
@@ -1089,7 +1098,7 @@ namespace NecromancerTome
|
|||||||
string n = _b?.GetBlockName();
|
string n = _b?.GetBlockName();
|
||||||
return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName ||
|
return n == BlockName || n == SkullBlockName || n == LogistBlockName || n == LogistSkullBlockName ||
|
||||||
n == LogistWallBlockName || n == LogistWallSkullBlockName || n == WorkerBlockName || n == WorkerSkullBlockName ||
|
n == LogistWallBlockName || n == LogistWallSkullBlockName || n == WorkerBlockName || n == WorkerSkullBlockName ||
|
||||||
IsRepairBlock(_b) || IsLootBlock(_b);
|
IsRepairBlock(_b) || IsLootBlock(_b) || IsProspectorBlock(_b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int UrnLimit(EntityPlayer _player)
|
public static int UrnLimit(EntityPlayer _player)
|
||||||
@@ -1486,7 +1495,10 @@ namespace NecromancerTome
|
|||||||
bool worker = TEFeatureCinerarium.IsWorkerBlock(bv.Block);
|
bool worker = TEFeatureCinerarium.IsWorkerBlock(bv.Block);
|
||||||
bool repairUrn = TEFeatureCinerarium.IsRepairBlock(bv.Block);
|
bool repairUrn = TEFeatureCinerarium.IsRepairBlock(bv.Block);
|
||||||
bool lootUrn = TEFeatureCinerarium.IsLootBlock(bv.Block);
|
bool lootUrn = TEFeatureCinerarium.IsLootBlock(bv.Block);
|
||||||
ItemValue iv = ItemClass.GetItem(repairUrn
|
bool prospector = TEFeatureCinerarium.IsProspectorBlock(bv.Block);
|
||||||
|
ItemValue iv = ItemClass.GetItem(prospector
|
||||||
|
? (urn.HasSkull ? TEFeatureCinerarium.ProspectorSkullBlockName : TEFeatureCinerarium.ProspectorBlockName)
|
||||||
|
: repairUrn
|
||||||
? (urn.HasSkull ? TEFeatureCinerarium.RepairSkullBlockName : TEFeatureCinerarium.RepairBlockName)
|
? (urn.HasSkull ? TEFeatureCinerarium.RepairSkullBlockName : TEFeatureCinerarium.RepairBlockName)
|
||||||
: lootUrn
|
: lootUrn
|
||||||
? (urn.HasSkull ? TEFeatureCinerarium.LootSkullBlockName : TEFeatureCinerarium.LootBlockName)
|
? (urn.HasSkull ? TEFeatureCinerarium.LootSkullBlockName : TEFeatureCinerarium.LootBlockName)
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ namespace NecromancerTome
|
|||||||
|
|
||||||
/// <summary>Покачивание и поворот джинна над урной. Только вид - никакой логики.</summary>
|
/// <summary>Покачивание и поворот джинна над урной. Только вид - никакой логики.</summary>
|
||||||
[Preserve]
|
[Preserve]
|
||||||
public class CinerariumGenieMotion : MonoBehaviour
|
public partial class CinerariumGenieMotion : MonoBehaviour
|
||||||
{
|
{
|
||||||
float _yaw;
|
float _yaw;
|
||||||
float _phase;
|
float _phase;
|
||||||
@@ -548,7 +548,7 @@ namespace NecromancerTome
|
|||||||
_nextSleepCheck = Time.time + 0.5f;
|
_nextSleepCheck = Time.time + 0.5f;
|
||||||
// Уснул - игрок далеко или дух не среди ближайших (CinerariumLoad.AnimateNearest): замирает дома, работа идёт.
|
// Уснул - игрок далеко или дух не среди ближайших (CinerariumLoad.AnimateNearest): замирает дома, работа идёт.
|
||||||
bool asleep = CinerariumGenie.PlayerFar(transform.position) || !CinerariumLoad.Animated(this);
|
bool asleep = CinerariumGenie.PlayerFar(transform.position) || !CinerariumLoad.Animated(this);
|
||||||
if (asleep && !_asleep)
|
if (asleep && !_asleep && !Digging) // старатель под землёй - не возвращать домой
|
||||||
{
|
{
|
||||||
_t = -1f; // полёт прерван - дело всё равно выполнится
|
_t = -1f; // полёт прерван - дело всё равно выполнится
|
||||||
Gesture(0f);
|
Gesture(0f);
|
||||||
@@ -558,7 +558,7 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
// Виден ли: не скрыт командой; дух логиста - только в полёте (дома сидит в кадиле). Рендереры - только при смене
|
// Виден ли: не скрыт командой; дух логиста - только в полёте (дома сидит в кадиле). Рендереры - только при смене
|
||||||
// (16.10, проверка на утечки: раньше список рендереров собирался каждые 0.5 с - мусор для GC).
|
// (16.10, проверка на утечки: раньше список рендереров собирался каждые 0.5 с - мусор для GC).
|
||||||
bool show = !CinerariumGenie.Hidden && (!Small || _t >= 0f);
|
bool show = !CinerariumGenie.Hidden && (!Small || _t >= 0f) && !DigHidden;
|
||||||
if (_hiddenApplied != !show)
|
if (_hiddenApplied != !show)
|
||||||
{
|
{
|
||||||
_hiddenApplied = !show;
|
_hiddenApplied = !show;
|
||||||
@@ -567,6 +567,10 @@ namespace NecromancerTome
|
|||||||
r.enabled = show;
|
r.enabled = show;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (DigUpdate(_asleep))
|
||||||
|
{
|
||||||
|
return; // старатель: спуск, под землёй, подъём (CinerariumProspector.cs)
|
||||||
|
}
|
||||||
if (_stuck || _asleep)
|
if (_stuck || _asleep)
|
||||||
{
|
{
|
||||||
return; // замер как был
|
return; // замер как был
|
||||||
|
|||||||
@@ -0,0 +1,650 @@
|
|||||||
|
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 : ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ namespace NecromancerTome
|
|||||||
/// в ячейке остаётся один череп, всё остальное (лишние черепа, чужие предметы) - назад в рюкзак, и урна узнаёт, есть ли
|
/// в ячейке остаётся один череп, всё остальное (лишние черепа, чужие предметы) - назад в рюкзак, и урна узнаёт, есть ли
|
||||||
/// в ней череп. Череп - обычный предмет (ItemClass): он уже в сейвах, менять ему класс на модификацию нельзя.
|
/// в ней череп. Череп - обычный предмет (ItemClass): он уже в сейвах, менять ему класс на модификацию нельзя.
|
||||||
/// У урны рабочего (18.10) ячеек две: вторая - образец (любой предмет, стопка целиком - сколько держать в запасе).
|
/// У урны рабочего (18.10) ячеек две: вторая - образец (любой предмет, стопка целиком - сколько держать в запасе).
|
||||||
|
/// У старателя (22.10) вторая ячейка - что добывать: один предмет из TEFeatureCinerarium.Ores, лишнее и чужое - назад.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CinerariumContents
|
public static class CinerariumContents
|
||||||
{
|
{
|
||||||
@@ -31,12 +32,12 @@ namespace NecromancerTome
|
|||||||
}
|
}
|
||||||
_urn = _u;
|
_urn = _u;
|
||||||
_player = _p;
|
_player = _p;
|
||||||
_bag = new Bag(new Vector2i(_u.IsWorker ? 2 : 1, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
|
_bag = new Bag(new Vector2i(_u.IsWorker || _u.IsProspector ? 2 : 1, 1), XUiC_ItemStack.StackLocationTypes.LootContainer, null);
|
||||||
if (_u.HasSkull && skull != null && skull.type > 0)
|
if (_u.HasSkull && skull != null && skull.type > 0)
|
||||||
{
|
{
|
||||||
_bag.SetSlot(0, new ItemStack(skull, 1));
|
_bag.SetSlot(0, new ItemStack(skull, 1));
|
||||||
}
|
}
|
||||||
if (_u.IsWorker && _u.Sample != null && !_u.Sample.IsEmpty())
|
if ((_u.IsWorker || _u.IsProspector) && _u.Sample != null && !_u.Sample.IsEmpty())
|
||||||
{
|
{
|
||||||
_bag.SetSlot(1, _u.Sample.Clone());
|
_bag.SetSlot(1, _u.Sample.Clone());
|
||||||
}
|
}
|
||||||
@@ -59,7 +60,7 @@ namespace NecromancerTome
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_urn.IsWorker && _bag.ItemGrid.items.Length > 1)
|
if ((_urn.IsWorker || _urn.IsProspector) && _bag.ItemGrid.items.Length > 1)
|
||||||
{
|
{
|
||||||
ItemStack smp = _bag.ItemGrid.items[1];
|
ItemStack smp = _bag.ItemGrid.items[1];
|
||||||
ItemStack first = _bag.ItemGrid.items[0];
|
ItemStack first = _bag.ItemGrid.items[0];
|
||||||
@@ -70,6 +71,29 @@ namespace NecromancerTome
|
|||||||
_bag.SetSlot(1, ItemStack.Empty.Clone());
|
_bag.SetSlot(1, ItemStack.Empty.Clone());
|
||||||
smp = _bag.ItemGrid.items[1];
|
smp = _bag.ItemGrid.items[1];
|
||||||
}
|
}
|
||||||
|
if (_urn.IsProspector && smp != null && !smp.IsEmpty())
|
||||||
|
{
|
||||||
|
// Старатель: образец - один предмет из списка добычи; чужое целиком, лишние штуки - назад.
|
||||||
|
if (!TEFeatureCinerarium.IsOreSample(smp))
|
||||||
|
{
|
||||||
|
GiveBack(smp.Clone());
|
||||||
|
_bag.SetSlot(1, ItemStack.Empty.Clone());
|
||||||
|
if (_player != null)
|
||||||
|
{
|
||||||
|
GameManager.ShowTooltip(_player, Localization.Get("necroProspectorBadSample"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (smp.count > 1)
|
||||||
|
{
|
||||||
|
ItemStack back = smp.Clone();
|
||||||
|
back.count = smp.count - 1;
|
||||||
|
GiveBack(back);
|
||||||
|
ItemStack one = smp.Clone();
|
||||||
|
one.count = 1;
|
||||||
|
_bag.SetSlot(1, one);
|
||||||
|
}
|
||||||
|
smp = _bag.ItemGrid.items[1];
|
||||||
|
}
|
||||||
_urn.SetSample(smp != null && !smp.IsEmpty() ? smp.Clone() : ItemStack.Empty.Clone());
|
_urn.SetSample(smp != null && !smp.IsEmpty() ? smp.Clone() : ItemStack.Empty.Clone());
|
||||||
}
|
}
|
||||||
ItemStack s = _bag.ItemGrid.items[0];
|
ItemStack s = _bag.ItemGrid.items[0];
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace NecromancerTome
|
|||||||
|
|
||||||
static bool Ours(string _name)
|
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" || _name.StartsWith("necroCinerariumLogist") || _name == "necroCinerariumRepairPrefab" || _name == "necroCinerariumLootPrefab";
|
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") || _name == "necroCinerariumRepairPrefab" || _name == "necroCinerariumLootPrefab" || _name == "necroCinerariumProspectorPrefab";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Postfix(Transform __result)
|
public static void Postfix(Transform __result)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 613 KiB After Width: | Height: | Size: 623 KiB |
Reference in New Issue
Block a user