Некротехнолог: грядки; скакун крафтится 120 с
Дух последним делом собирает созревшее (метка crops, добыча Harvest) с идеальными бонусами и сразу сажает семя на то же место; засаживает пустые грядки случайными семенами из ящиков и сумки (можно ли сажать - решает сам блок семени). Описание урны - про универсального работника, 13 языков. Костяному скакуну задано craft_time 120 (было - сумма ингредиентов). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0890ffe1e9
commit
94bf0a5f5c
File diff suppressed because one or more lines are too long
+2
-2
@@ -493,7 +493,7 @@
|
|||||||
гео-оссуария: транспорт нужен, когда карта уже велика для пеших ходок. Состав - по
|
гео-оссуария: транспорт нужен, когда карта уже велика для пеших ходок. Состав - по
|
||||||
указанию пользователя: кости, гнилое мясо, прах, кожа жертвы, два флакона крови. -->
|
указанию пользователя: кости, гнилое мясо, прах, кожа жертвы, два флакона крови. -->
|
||||||
<append xpath="/recipes">
|
<append xpath="/recipes">
|
||||||
<recipe name="necroBoneSteedPlaceable" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
<recipe name="necroBoneSteedPlaceable" count="1" craft_time="120" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||||
<ingredient name="resourceBone" count="300"/>
|
<ingredient name="resourceBone" count="300"/>
|
||||||
<ingredient name="foodRottingFlesh" count="30"/>
|
<ingredient name="foodRottingFlesh" count="30"/>
|
||||||
<ingredient name="resourceZombieAsh" count="150"/>
|
<ingredient name="resourceZombieAsh" count="150"/>
|
||||||
@@ -587,7 +587,7 @@
|
|||||||
<ingredient name="resourceWood" count="100"/>
|
<ingredient name="resourceWood" count="100"/>
|
||||||
<ingredient name="resourceYuccaFibers" count="50"/>
|
<ingredient name="resourceYuccaFibers" count="50"/>
|
||||||
</recipe>
|
</recipe>
|
||||||
<recipe name="necroBoneSteedPlaceable" count="1" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
<recipe name="necroBoneSteedPlaceable" count="1" craft_time="120" craft_area="workbench" tags="learnable,workbenchCrafting,necroNecromancyJourneyman">
|
||||||
<ingredient name="resourceBone" count="300"/>
|
<ingredient name="resourceBone" count="300"/>
|
||||||
<ingredient name="foodRottingFlesh" count="30"/>
|
<ingredient name="foodRottingFlesh" count="30"/>
|
||||||
<ingredient name="resourceZombieAsh" count="150"/>
|
<ingredient name="resourceZombieAsh" count="150"/>
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ namespace NecromancerTome
|
|||||||
/// 2. забрать готовое из коллекторов (Items) и станций (TileEntityWorkstation.Output - верстак, кузня, химстанция,
|
/// 2. забрать готовое из коллекторов (Items) и станций (TileEntityWorkstation.Output - верстак, кузня, химстанция,
|
||||||
/// костёр, бетономешалка) и сложить в ящики зоны: сначала к таким же предметам, потом в свободное место; ящики полны -
|
/// костёр, бетономешалка) и сложить в ящики зоны: сначала к таким же предметам, потом в свободное место; ящики полны -
|
||||||
/// в сумку урны; некуда - дух чернеет и замирает над урной, оживает сам, когда место появится;
|
/// в сумку урны; некуда - дух чернеет и замирает над урной, оживает сам, когда место появится;
|
||||||
/// 3. грядки - последними (ещё не сделано).
|
/// 3. грядки - последними: собрать созревшее (блок с меткой crops и добычей Harvest) с «идеальными бонусами»
|
||||||
|
/// (06.10: все бонусные доли урожая и семя - всегда), семя сразу посадить на то же место; затем засадить пустые
|
||||||
|
/// грядки случайными семенами из ящиков и сумки (семя - предмет-блок стадии 1, BlockPlantGrowing; можно ли сажать,
|
||||||
|
/// решает сам блок семени - CanPlaceBlockAt проверяет плодородие под ним, то есть грядку).
|
||||||
/// Коллектор или станция подходят любые - будущий крольчатник обслуживается тем же кодом. Ящик - составной блок игрока
|
/// Коллектор или станция подходят любые - будущий крольчатник обслуживается тем же кодом. Ящик - составной блок игрока
|
||||||
/// с хранилищем (TEFeatureStorage, PlayerPlaced), кроме урн. Открытое у игрока (IsUserAccessing) не трогается.
|
/// с хранилищем (TEFeatureStorage, PlayerPlaced), кроме урн. Открытое у игрока (IsUserAccessing) не трогается.
|
||||||
/// Работает на сервере: зовётся из TEFeatureCinerarium.UpdateTick.
|
/// Работает на сервере: зовётся из TEFeatureCinerarium.UpdateTick.
|
||||||
@@ -29,8 +32,13 @@ namespace NecromancerTome
|
|||||||
public readonly List<TileEntityWorkstation> Stations = new List<TileEntityWorkstation>();
|
public readonly List<TileEntityWorkstation> Stations = new List<TileEntityWorkstation>();
|
||||||
public readonly Dictionary<TileEntity, Vector3i> Where = new Dictionary<TileEntity, Vector3i>();
|
public readonly Dictionary<TileEntity, Vector3i> Where = new Dictionary<TileEntity, Vector3i>();
|
||||||
public TEFeatureStorage Bag;
|
public TEFeatureStorage Bag;
|
||||||
|
public readonly List<Vector3i> Ripe = new List<Vector3i>();
|
||||||
|
public readonly List<Vector3i> Empty = new List<Vector3i>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static readonly FastTags<TagGroup.Global> CropTag = FastTags<TagGroup.Global>.Parse("crops");
|
||||||
|
static readonly GameRandom _rnd = GameRandomManager.Instance.CreateGameRandom();
|
||||||
|
|
||||||
static readonly HashSet<TileEntity> _seen = new HashSet<TileEntity>();
|
static readonly HashSet<TileEntity> _seen = new HashSet<TileEntity>();
|
||||||
|
|
||||||
/// <summary>Одно дело. Возвращает, где дух работал (для полёта модели), или null; _stuck - есть что забрать, но некуда.</summary>
|
/// <summary>Одно дело. Возвращает, где дух работал (для полёта модели), или null; _stuck - есть что забрать, но некуда.</summary>
|
||||||
@@ -68,10 +76,170 @@ namespace NecromancerTome
|
|||||||
return z.Where[w];
|
return z.Where[w];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 3. Грядки.
|
||||||
|
foreach (Vector3i p in z.Ripe)
|
||||||
|
{
|
||||||
|
if (Harvest(_world, p, z, ref blocked))
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (z.Empty.Count > 0)
|
||||||
|
{
|
||||||
|
Vector3i? planted = PlantRandom(_world, z);
|
||||||
|
if (planted.HasValue)
|
||||||
|
{
|
||||||
|
return planted;
|
||||||
|
}
|
||||||
|
}
|
||||||
_stuck = blocked;
|
_stuck = blocked;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool IsRipe(Block _b)
|
||||||
|
{
|
||||||
|
return _b != null && !(_b is BlockPlantGrowing) && _b.HasAnyFastTags(CropTag) &&
|
||||||
|
_b.itemsToDrop.TryGetValue(EnumDropEvent.Harvest, out List<Block.SItemDropProb> l) && l != null && l.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsSeed(ItemValue _iv, out BlockValue _bv)
|
||||||
|
{
|
||||||
|
_bv = BlockValue.Air;
|
||||||
|
if (_iv == null || _iv.type == 0 || !_iv.ItemClass.IsBlock())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_bv = _iv.ToBlockValue();
|
||||||
|
return _bv.Block is BlockPlantGrowing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Урожай: все доли добычи Harvest (бонусы - тоже, вероятность только у обычных долей), множители сервера на
|
||||||
|
/// урожай и семена. Одно семя - сразу в землю на то же место, остальное - в ящики/сумку. Нет места ни для чего -
|
||||||
|
/// грядку не трогаем, дух чернеет.</summary>
|
||||||
|
static bool Harvest(World _world, Vector3i _p, Zone _z, ref bool _blocked)
|
||||||
|
{
|
||||||
|
Block crop = _world.GetBlock(_p).Block;
|
||||||
|
List<ItemStack> got = new List<ItemStack>();
|
||||||
|
BlockValue replant = BlockValue.Air;
|
||||||
|
foreach (Block.SItemDropProb d in crop.itemsToDrop[EnumDropEvent.Harvest])
|
||||||
|
{
|
||||||
|
ItemValue iv = ItemClass.GetItem(d.name);
|
||||||
|
if (iv == null || iv.type == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bool seed = IsSeed(iv, out BlockValue seedBv);
|
||||||
|
bool bonus = d.tag != null && (d.tag.Contains("Bonus") || d.tag.Contains("bonus"));
|
||||||
|
if (!bonus && d.prob < 1f && _rnd.RandomFloat > d.prob)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int n = _rnd.RandomRange(d.minCount, d.maxCount + 1);
|
||||||
|
n = Mathf.RoundToInt(n * (seed ? XUiM_Recipes.SeedDropOutputModifier : XUiM_Recipes.CropOutputModifier));
|
||||||
|
if (seed && replant.isair && n > 0)
|
||||||
|
{
|
||||||
|
replant = seedBv;
|
||||||
|
n--;
|
||||||
|
}
|
||||||
|
if (n > 0)
|
||||||
|
{
|
||||||
|
got.Add(new ItemStack(iv, n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (got.Count > 0 && !HasRoom(got[0], _z))
|
||||||
|
{
|
||||||
|
_blocked = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_world.SetBlockRPC(_p, BlockValue.Air);
|
||||||
|
if (!replant.isair)
|
||||||
|
{
|
||||||
|
replant.rotation = (byte)_rnd.RandomRange(0, 4);
|
||||||
|
_world.SetBlockRPC(_p, replant);
|
||||||
|
}
|
||||||
|
foreach (ItemStack s in got)
|
||||||
|
{
|
||||||
|
Place(s, _z.Boxes);
|
||||||
|
if (s.count > 0 && _z.Bag != null)
|
||||||
|
{
|
||||||
|
Place(s, new[] { _z.Bag });
|
||||||
|
}
|
||||||
|
if (s.count > 0)
|
||||||
|
{
|
||||||
|
// Влезло не всё (проверяли только первую долю) - остаток падает у урны, не пропадает.
|
||||||
|
GameManager.Instance.ItemDropServer(s.Clone(), World.blockToTransformPos(_p) + Vector3.up * 0.5f, Vector3.zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Debug.Log("[NecromancerTome] Cinerarium: дух собрал " + crop.GetBlockName() + " в " + _p + (replant.isair ? "" : ", посадил снова"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool HasRoom(ItemStack _s, Zone _z)
|
||||||
|
{
|
||||||
|
foreach (TEFeatureStorage t in _z.Boxes)
|
||||||
|
{
|
||||||
|
if (Fits(t, _s))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _z.Bag != null && Fits(_z.Bag, _s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Fits(TEFeatureStorage _t, ItemStack _s)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _t.ItemGrid.Length; i++)
|
||||||
|
{
|
||||||
|
ItemStack x = _t.ItemGrid[i];
|
||||||
|
if (x == null || x.IsEmpty())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
int n = _s.count;
|
||||||
|
if (x.itemValue.type == _s.itemValue.type && x.CanStackPartly(ref n))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Засадить одну пустую грядку случайным семенем из сумки и ящиков (06.10: «случайно сажает имеющиеся семена»).</summary>
|
||||||
|
static Vector3i? PlantRandom(World _world, Zone _z)
|
||||||
|
{
|
||||||
|
List<(TEFeatureStorage src, int slot, BlockValue bv)> seeds = new List<(TEFeatureStorage, int, BlockValue)>();
|
||||||
|
foreach (TEFeatureStorage src in Sources(_z))
|
||||||
|
{
|
||||||
|
for (int k = 0; k < src.ItemGrid.Length; k++)
|
||||||
|
{
|
||||||
|
ItemStack st = src.ItemGrid[k];
|
||||||
|
if (st != null && !st.IsEmpty() && !Locked(src, k) && IsSeed(st.itemValue, out BlockValue bv))
|
||||||
|
{
|
||||||
|
seeds.Add((src, k, bv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (seeds.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
foreach (Vector3i p in _z.Empty)
|
||||||
|
{
|
||||||
|
var pick = seeds[_rnd.RandomRange(0, seeds.Count)];
|
||||||
|
BlockValue bv = pick.bv;
|
||||||
|
bv.rotation = (byte)_rnd.RandomRange(0, 4);
|
||||||
|
if (!bv.Block.CanPlaceBlockAt(_world, p, bv))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Take(pick.src, pick.slot, 1);
|
||||||
|
_world.SetBlockRPC(p, bv);
|
||||||
|
Debug.Log("[NecromancerTome] Cinerarium: дух посадил " + bv.Block.GetBlockName() + " в " + p);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
static Zone Scan(World _world, TEFeatureCinerarium _urn)
|
static Zone Scan(World _world, TEFeatureCinerarium _urn)
|
||||||
{
|
{
|
||||||
Zone z = new Zone();
|
Zone z = new Zone();
|
||||||
@@ -93,6 +261,16 @@ namespace NecromancerTome
|
|||||||
BlockValue bv = _world.GetBlock(p);
|
BlockValue bv = _world.GetBlock(p);
|
||||||
if (bv.isair)
|
if (bv.isair)
|
||||||
{
|
{
|
||||||
|
// Пустая клетка над плодородным блоком - кандидат в грядку (окончательно решит блок семени).
|
||||||
|
if (_world.GetBlock(p - Vector3i.up).Block?.blockMaterial?.FertileLevel > 0)
|
||||||
|
{
|
||||||
|
z.Empty.Add(p);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!bv.ischild && IsRipe(bv.Block))
|
||||||
|
{
|
||||||
|
z.Ripe.Add(p);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Vector3i owner = bv.ischild ? p + bv.parent : p; // часть составного блока (верстак 2x1) - к главной клетке
|
Vector3i owner = bv.ischild ? p + bv.parent : p; // часть составного блока (верстак 2x1) - к главной клетке
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user