1.7.0: охрана питомцами, один питомец за раз, прицел книг, гео-оссуарий
Питомцы: - охрана хозяина: враг ближе 20 м (в режиме змеи - к телу) атакуется сам, поводок 40 м, охраняют все кроме крысы; приказ прицелом охрана не перебивает - IsFoe исключает существ мода (necro*): питомец выбирал целью себя - призыв нового спутника отзывает прежнего - прицел книг призыва - как у арбалета, краснеет на годной цели Гео-оссуарий (necroGeoOssuary): станция добычи на кровавой сфере - основа - коллектор росы; в 3 ячейки модов любой из 5 ресурсов (камень, уголь, селитра, железо, сланец), 1 сфера = стак 6000 за 5-8 ч - работает без открытого неба; верстак, Подмастерье (ур. 25) - своя модель (бандл necroossuary): кости, корни, сосуды, клыки, черепа с глазами-углями; шейдер и теги коллайдера берутся у ванильного коллектора Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b737dd43d0
commit
f2dfeed126
@@ -0,0 +1,202 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Гео-оссуарий (1.7.0, 2026-09-30): станция добычи ресурсов на кровавой сфере. Блок -
|
||||
/// necroGeoOssuary в blocks.xml, наследник ванильного коллектора росы; всё, что коллектор
|
||||
/// умеет сам (окно, топливо, таймеры, работа без игрока рядом), остаётся его. Здесь только то,
|
||||
/// чего ваниль не умеет, - разобрано по декомпилятору 3.3:
|
||||
///
|
||||
/// - ЯЧЕЙКА МОДИФИКАЦИИ ПРИНИМАЕТ ОДИН ПРЕДМЕТ. XUiC_DewCollectorModGrid.SetTileEntity
|
||||
/// зовёт SetAllowedItemClassSingle(RequiredMods[i]). У оссуария в любую из трёх ячеек идёт
|
||||
/// любой из пяти ресурсов, по одной штуке (OverrideStackCount = 1). Окно у всех
|
||||
/// коллекторов одно и то же, поэтому для чужих коллекторов предел штук сбрасывается
|
||||
/// обратно - иначе коллектор росы после оссуария тоже брал бы по одной.
|
||||
/// - ВАНИЛЬ НЕ СМОТРИТ, ЧТО ЛЕЖИТ В ЯЧЕЙКЕ. ModTypes=Type - просто флаг HasModConvert
|
||||
/// (OutputItem -> OutputItemModded), одна замена на весь блок. У оссуария три OutputType
|
||||
/// os1..os3, по одному на ячейку продукции; os(i+1) выдаёт полный стак ресурса из ячейки
|
||||
/// модификации i. Пустая или чужая модификация - этот OutputType выключен (isDisabled),
|
||||
/// значит таймер стоит и сферу он не ест.
|
||||
/// - ПРОВЕРКА НЕБА. HandleSkyCheck (7 блоков вверх) у ванили безусловна для всех
|
||||
/// коллекторов; оссуарий работает где угодно, в том числе под землёй (решение 30.09).
|
||||
/// </summary>
|
||||
public static class GeoOssuary
|
||||
{
|
||||
public const string BlockName = "necroGeoOssuary";
|
||||
|
||||
/// <summary>Камень, уголь, селитра, железо (его дают железные жилы), сланец.</summary>
|
||||
public static readonly string[] ResourceNames =
|
||||
{
|
||||
"resourceRockSmall", "resourceCoal", "resourcePotassiumNitratePowder",
|
||||
"resourceScrapIron", "resourceOilShale",
|
||||
};
|
||||
|
||||
static ItemClass[] resourceClasses;
|
||||
|
||||
public static ItemClass[] ResourceClasses
|
||||
{
|
||||
get
|
||||
{
|
||||
if (resourceClasses == null)
|
||||
{
|
||||
resourceClasses = new ItemClass[ResourceNames.Length];
|
||||
for (int i = 0; i < ResourceNames.Length; i++)
|
||||
resourceClasses[i] = ItemClass.GetItemClass(ResourceNames[i]);
|
||||
}
|
||||
return resourceClasses;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Is(Block block) => block != null && block.GetBlockName() == BlockName;
|
||||
|
||||
public static bool Is(TileEntityCollector te) => te != null && Is(te.blockValue.Block);
|
||||
|
||||
/// <summary>os1 -> 0, os2 -> 1, os3 -> 2; иначе -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';
|
||||
}
|
||||
|
||||
/// <summary>Ресурс в ячейке модификации, если он из списка; иначе null.</summary>
|
||||
public static ItemClass ResourceIn(TileEntityCollector te, int modSlot)
|
||||
{
|
||||
ItemStack[] mods = te.ModSlots;
|
||||
if (modSlot < 0 || mods == null || modSlot >= mods.Length)
|
||||
return null;
|
||||
ItemStack stack = mods[modSlot];
|
||||
if (stack == null || stack.IsEmpty())
|
||||
return null;
|
||||
ItemClass cls = stack.itemValue.ItemClass;
|
||||
return cls != null && System.Array.IndexOf(ResourceNames, cls.Name) >= 0 ? cls : null;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(XUiC_DewCollectorModGrid), nameof(XUiC_DewCollectorModGrid.SetTileEntity))]
|
||||
public static class Patch_XUiC_DewCollectorModGrid_SetTileEntity_GeoOssuary
|
||||
{
|
||||
public static void Postfix(XUiC_DewCollectorModGrid __instance, TileEntityCollector te)
|
||||
{
|
||||
bool ossuary = GeoOssuary.Is(te);
|
||||
foreach (XUiC_ItemStack slot in __instance.itemControllers)
|
||||
{
|
||||
if (!(slot is XUiC_RequiredItemStack required))
|
||||
continue;
|
||||
required.OverrideStackCount = ossuary ? 1 : -1;
|
||||
if (ossuary)
|
||||
{
|
||||
required.SetAllowedItemClasses(GeoOssuary.ResourceClasses);
|
||||
required.RequiredItemOnly = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(TileEntityCollector), nameof(TileEntityCollector.isDisabled))]
|
||||
public static class Patch_TileEntityCollector_isDisabled_GeoOssuary
|
||||
{
|
||||
public static void Postfix(TileEntityCollector __instance, BlockCollector.OutputType _outputType, ref bool __result)
|
||||
{
|
||||
if (__result || !GeoOssuary.Is(__instance))
|
||||
return;
|
||||
if (GeoOssuary.ResourceIn(__instance, GeoOssuary.ModSlotOf(_outputType)) == null)
|
||||
__result = true;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(TileEntityCollector), nameof(TileEntityCollector.newItem))]
|
||||
public static class Patch_TileEntityCollector_newItem_GeoOssuary
|
||||
{
|
||||
public static void Postfix(TileEntityCollector __instance, BlockCollector.OutputType outputType, ref ItemStack __result)
|
||||
{
|
||||
if (__result == null || !GeoOssuary.Is(__instance))
|
||||
return;
|
||||
ItemClass resource = GeoOssuary.ResourceIn(__instance, GeoOssuary.ModSlotOf(outputType));
|
||||
if (resource == null)
|
||||
{
|
||||
// isDisabled не пускает сюда без ресурса; если всё же дошло - сфера уже списана,
|
||||
// и отдать хотя бы заглушку честнее, чем молча съесть топливо.
|
||||
Debug.LogWarning("[NecromancerTome] GeoOssuary: " + outputType.Name + " сработал без ресурса в ячейке модификации");
|
||||
return;
|
||||
}
|
||||
__result = new ItemStack(new ItemValue(resource.Id), resource.MaxCount);
|
||||
Debug.Log("[NecromancerTome] GeoOssuary: " + outputType.Name + " -> " + resource.Name + " x" + resource.MaxCount + " в " + __instance.ToWorldPos());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Шейдер модели. Своя модель оссуария лежит в нашем бандле с запасным шейдером
|
||||
/// NecromancerTome/OssuaryProxy (Standard-поверхность), а у пирамиды 10.09 именно
|
||||
/// Standard из бандла на расстоянии рисовался маджентой. Поэтому при первой загрузке модели
|
||||
/// материалу ставится тот шейдер, которым игра рисует ванильный коллектор росы: имена текстур
|
||||
/// у них общие (_MainTex, _Normal, _RMOM, _Emissive), модель выросла из его префаба. Префаб из
|
||||
/// бандла - общий ассет, так что правка одна на все экземпляры.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(BlockShapeModelEntity), nameof(BlockShapeModelEntity.getPrefab))]
|
||||
public static class Patch_BlockShapeModelEntity_getPrefab_GeoOssuaryShader
|
||||
{
|
||||
const string ModelName = "necroOssuaryPrefab";
|
||||
const string VanillaModel = "@:Entities/Furniture/collectorDewPrefab.prefab";
|
||||
static bool done;
|
||||
|
||||
public static void Postfix(Transform __result)
|
||||
{
|
||||
if (done || __result == null || __result.name != ModelName)
|
||||
return;
|
||||
done = true;
|
||||
Transform vanilla = DataLoader.LoadAsset<Transform>(VanillaModel);
|
||||
Renderer src = vanilla != null ? vanilla.GetComponentInChildren<Renderer>(true) : null;
|
||||
Shader shader = src != null && src.sharedMaterial != null ? src.sharedMaterial.shader : null;
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] GeoOssuary: шейдер коллектора росы не найден - остаётся запасной");
|
||||
return;
|
||||
}
|
||||
int n = 0;
|
||||
foreach (Renderer r in __result.GetComponentsInChildren<Renderer>(true))
|
||||
foreach (Material m in r.sharedMaterials)
|
||||
if (m != null && m.shader != shader) { m.shader = shader; m.enableInstancing = true; n++; }
|
||||
Debug.Log("[NecromancerTome] GeoOssuary: материалам модели (" + n + ") поставлен шейдер '" + shader.name + "'");
|
||||
CopyTags(vanilla, __result);
|
||||
}
|
||||
|
||||
/// <summary>Теги коллайдера. У ванильного префаба корень и объект collision помечены
|
||||
/// игровым тегом (AssetRipper записал его как unknown_20004 - имени он не знал), и именно
|
||||
/// по тегу игра понимает, что луч попал в блок, с которым можно взаимодействовать. В нашем
|
||||
/// Unity-проекте такого тега нет, при сборке он стал Untagged - и оссуарий не реагировал
|
||||
/// на наведение (тест 30.09). Берём теги у ванильного коллектора по именам объектов.</summary>
|
||||
static void CopyTags(Transform vanilla, Transform ours)
|
||||
{
|
||||
var byName = new System.Collections.Generic.Dictionary<string, Transform>();
|
||||
foreach (Transform t in vanilla.GetComponentsInChildren<Transform>(true))
|
||||
byName[t == vanilla ? "<root>" : t.name] = t;
|
||||
foreach (Transform t in ours.GetComponentsInChildren<Transform>(true))
|
||||
{
|
||||
Transform src;
|
||||
if (!byName.TryGetValue(t == ours ? "<root>" : t.name, out src))
|
||||
continue;
|
||||
if (t.tag != src.tag || t.gameObject.layer != src.gameObject.layer)
|
||||
{
|
||||
Debug.Log("[NecromancerTome] GeoOssuary: '" + t.name + "' тег " + t.tag + " -> " + src.tag +
|
||||
", слой " + t.gameObject.layer + " -> " + src.gameObject.layer);
|
||||
t.tag = src.tag;
|
||||
t.gameObject.layer = src.gameObject.layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(TileEntityCollector), nameof(TileEntityCollector.HandleSkyCheck))]
|
||||
public static class Patch_TileEntityCollector_HandleSkyCheck_GeoOssuary
|
||||
{
|
||||
public static void Postfix(TileEntityCollector __instance, ref bool __result)
|
||||
{
|
||||
if (__result && GeoOssuary.Is(__instance))
|
||||
__result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user