1.7.0: костяной скакун, книга Духа крысы, турели, расход сфер в оссуарии
- Костяной скакун (necroBoneSteed): мотоцикл из костей, сухожилий и сосудов с черепом-циклопом вместо фары; своя модель (бандл necrobonesteed), ванильная система предмета-транспорта (necroBoneSteedPlaceable, предпросмотр при установке); топливо - кровавая сфера, одна сфера = полный бак; теги коллайдеров и шейдер копируются с ванильного мотоцикла; рецепт на верстаке, Подмастерье - Книга Духа крысы: своя модель (бандл necrobooks) - чёрная кожа, светящийся узор со скелетом крысы и семиконечной звездой, ржавые уголки, ремешок - Турели не стреляют по существам мода (питомцы, рой, змея) - Гео-оссуарий тратит 1/10 прочности сферы на стак вместо целой сферы В игре не проверено. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f2dfeed126
commit
11db1116b0
@@ -0,0 +1,148 @@
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Костяной скакун (1.7.0, 01.10.2026): мотоцикл из костей, призывается книгой, ездит на
|
||||
/// кровавых сферах. Сущность necroBoneSteed в entityclasses.xml - наследник vehicleMotorcycle
|
||||
/// со своей моделью (бандл necrobonesteed, собран из копии ванильного VMotorcycleP.prefab).
|
||||
///
|
||||
/// Топливо у транспорта игры в XML не задаётся: Vehicle.GetFuelItem() возвращает "ammoGasCan"
|
||||
/// строкой в коде. Отсюда два патча: предмет топлива - сфера, и заправка - одна сфера на полный
|
||||
/// бак (ваниль берёт по канистре на 1/25 единицы бака - 120 сфер на бак было бы абсурдом).
|
||||
/// Без сфер скакун глохнет как обычный транспорт (решение 01.10) - это ваниль, кода не нужно.
|
||||
///
|
||||
/// Модель выросла из рипнутого префаба, и у неё те же две беды, что у оссуария: игровые теги
|
||||
/// коллайдеров потеряны (без них на скакуна не сесть и по нему не попасть) и шейдер-заглушка.
|
||||
/// Оба лечатся с ванильного мотоцикла - теги по путям объектов, шейдер у его материала.
|
||||
/// </summary>
|
||||
public static class BoneSteed
|
||||
{
|
||||
public const string EntityClassName = "necroBoneSteed";
|
||||
public const string FuelItem = "resourceBloodSphere";
|
||||
const string VanillaPrefab = "@:Entities/Vehicles/VMotorcycle/VMotorcycleP.prefab";
|
||||
|
||||
public static bool Is(Entity e)
|
||||
{
|
||||
return e != null && EntityClass.list.TryGetValue(e.entityClass, out EntityClass cls) &&
|
||||
cls != null && cls.entityClassName == EntityClassName;
|
||||
}
|
||||
|
||||
static Transform vanilla;
|
||||
static Shader vanillaShader;
|
||||
|
||||
public static void FixModel(EntityVehicle ev)
|
||||
{
|
||||
if (vanilla == null)
|
||||
{
|
||||
vanilla = DataLoader.LoadAsset<Transform>(VanillaPrefab);
|
||||
if (vanilla == null)
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] BoneSteed: ванильный мотоцикл не найден - теги и шейдер не поправлены");
|
||||
return;
|
||||
}
|
||||
foreach (Renderer r in vanilla.GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
if (r.sharedMaterial != null && r.sharedMaterial.HasProperty("_RMOM"))
|
||||
{
|
||||
vanillaShader = r.sharedMaterial.shader;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var byPath = new Dictionary<string, Transform>();
|
||||
foreach (Transform t in vanilla.GetComponentsInChildren<Transform>(true))
|
||||
byPath[PathOf(t, vanilla)] = t;
|
||||
int tags = 0, mats = 0;
|
||||
foreach (Transform t in ev.transform.GetComponentsInChildren<Transform>(true))
|
||||
{
|
||||
if (!byPath.TryGetValue(PathOf(t, ev.transform), out Transform src))
|
||||
continue;
|
||||
if (t.tag != src.tag || t.gameObject.layer != src.gameObject.layer)
|
||||
{
|
||||
t.tag = src.tag;
|
||||
t.gameObject.layer = src.gameObject.layer;
|
||||
tags++;
|
||||
}
|
||||
}
|
||||
if (vanillaShader != null)
|
||||
{
|
||||
foreach (Renderer r in ev.transform.GetComponentsInChildren<Renderer>(true))
|
||||
{
|
||||
foreach (Material m in r.sharedMaterials)
|
||||
{
|
||||
if (m != null && m.HasProperty("_RMOM") && m.shader != vanillaShader)
|
||||
{
|
||||
m.shader = vanillaShader;
|
||||
m.enableInstancing = true;
|
||||
mats++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Debug.Log("[NecromancerTome] BoneSteed: теги поправлены у " + tags + " объектов, шейдер '" +
|
||||
(vanillaShader != null ? vanillaShader.name : "-") + "' поставлен " + mats + " материалам");
|
||||
}
|
||||
|
||||
/// <summary>Путь объекта от корня префаба. Корень - пустая строка: у экземпляра корень
|
||||
/// называется иначе (necroBoneSteedP(Clone)), чем у ванильного префаба.</summary>
|
||||
static string PathOf(Transform t, Transform root)
|
||||
{
|
||||
if (t == root)
|
||||
return "";
|
||||
string p = t.name;
|
||||
for (Transform x = t.parent; x != null && x != root; x = x.parent)
|
||||
p = x.name + "/" + p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(EntityVehicle), nameof(EntityVehicle.PostInit))]
|
||||
public static class Patch_EntityVehicle_PostInit_BoneSteed
|
||||
{
|
||||
public static void Postfix(EntityVehicle __instance)
|
||||
{
|
||||
if (BoneSteed.Is(__instance))
|
||||
BoneSteed.FixModel(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Vehicle), nameof(Vehicle.GetFuelItem))]
|
||||
public static class Patch_Vehicle_GetFuelItem_BoneSteed
|
||||
{
|
||||
public static void Postfix(Vehicle __instance, ref string __result)
|
||||
{
|
||||
if (__result != "" && BoneSteed.Is(__instance.entity))
|
||||
__result = BoneSteed.FuelItem;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Одна сфера - полный бак.</summary>
|
||||
[HarmonyPatch(typeof(EntityVehicle), nameof(EntityVehicle.AddFuelFromInventory))]
|
||||
public static class Patch_EntityVehicle_AddFuelFromInventory_BoneSteed
|
||||
{
|
||||
public static bool Prefix(EntityVehicle __instance, EntityAlive entity, ref bool __result)
|
||||
{
|
||||
if (!BoneSteed.Is(__instance))
|
||||
return true;
|
||||
__result = false;
|
||||
Vehicle v = __instance.GetVehicle();
|
||||
if (v.GetFuelPercent() >= 1f || !(entity is EntityPlayer player))
|
||||
return false;
|
||||
ItemValue sphere = ItemClass.GetItem(BoneSteed.FuelItem);
|
||||
int taken = player.inventory.DecItem(sphere, 1);
|
||||
if (taken == 0)
|
||||
taken = player.bag.DecItem(sphere, 1);
|
||||
if (taken == 0)
|
||||
return false;
|
||||
v.AddFuel(v.GetMaxFuelLevel() - v.GetFuelLevel());
|
||||
__instance.PlayOneShot("useactions/gas_refill");
|
||||
Debug.Log("[NecromancerTome] BoneSteed: сфера залита - полный бак");
|
||||
__result = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +190,50 @@ namespace NecromancerTome
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Расход сферы - по прочности, как в пространственном хранилище (указание 01.10: «одна
|
||||
/// сфера на стак - это жирно; 1/10 прочности на стак»). Ваниль в removeFuel вычитает ШТУКИ
|
||||
/// топлива, то есть съедала бы сферу целиком за каждый стак. Здесь вместо этого у первой
|
||||
/// сферы в отсеке списывается ChargePerStack прочности (DegradationMax сферы 500 -> 10 стаков
|
||||
/// со сферы); выработанная до нуля сфера исчезает из ячейки, как из браслета. Наличие топлива
|
||||
/// ваниль по-прежнему считает штуками (getFuelCount) - любая сфера с остатком годится.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(TileEntityCollector), nameof(TileEntityCollector.removeFuel))]
|
||||
public static class Patch_TileEntityCollector_removeFuel_GeoOssuary
|
||||
{
|
||||
public const int ChargePerStack = 50;
|
||||
|
||||
public static bool Prefix(TileEntityCollector __instance)
|
||||
{
|
||||
if (!GeoOssuary.Is(__instance))
|
||||
return true;
|
||||
ItemStack[] fuel = __instance.FuelSlots;
|
||||
for (int i = 0; i < fuel.Length; i++)
|
||||
{
|
||||
ItemStack stack = fuel[i];
|
||||
if (stack == null || stack.IsEmpty() || stack.itemValue.ItemClass == null ||
|
||||
stack.itemValue.ItemClass.Name != SpatialVaultPickup.ChargeItemName)
|
||||
continue;
|
||||
int max = stack.itemValue.MaxUseTimes;
|
||||
if (max <= 0)
|
||||
{
|
||||
// Прочность не задалась (DegradationMax) - ведём себя как ваниль: сфера за стак.
|
||||
fuel[i] = ItemStack.Empty;
|
||||
Debug.LogWarning("[NecromancerTome] GeoOssuary: у сферы MaxUseTimes 0 - списана целиком");
|
||||
return false;
|
||||
}
|
||||
stack.itemValue.UseTimes += ChargePerStack;
|
||||
if (stack.itemValue.UseTimes >= max)
|
||||
{
|
||||
fuel[i] = ItemStack.Empty;
|
||||
Debug.Log("[NecromancerTome] GeoOssuary: сфера в ячейке " + i + " выработана и исчезла");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(TileEntityCollector), nameof(TileEntityCollector.HandleSkyCheck))]
|
||||
public static class Patch_TileEntityCollector_HandleSkyCheck_GeoOssuary
|
||||
{
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Турели не стреляют по существам мода. Жалоба 01.10: «мои питомцы враги, их атакуют
|
||||
/// турели». Питомцы, рой и змея сделаны на зомби-основе и для игры остаются EntityEnemy, а
|
||||
/// обе турели (стационарная AutoTurretFireController и переносная MiniTurretFireController)
|
||||
/// решают в shouldIgnoreTarget: EntityEnemy - стрелять, если включено «по зомби». Здесь
|
||||
/// ответ «пропустить» для всех necro*-сущностей (SnakeControl.IsNecroEntity) - тот же признак,
|
||||
/// по которому их не выбирает целью охрана.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AutoTurretFireController), nameof(AutoTurretFireController.shouldIgnoreTarget))]
|
||||
public static class Patch_AutoTurretFireController_shouldIgnoreTarget_Pets
|
||||
{
|
||||
public static void Postfix(Entity _target, ref bool __result)
|
||||
{
|
||||
if (!__result && _target is EntityAlive alive && SnakeControl.IsNecroEntity(alive))
|
||||
__result = true;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(MiniTurretFireController), nameof(MiniTurretFireController.shouldIgnoreTarget))]
|
||||
public static class Patch_MiniTurretFireController_shouldIgnoreTarget_Pets
|
||||
{
|
||||
public static void Postfix(Entity _target, ref bool __result)
|
||||
{
|
||||
if (!__result && _target is EntityAlive alive && SnakeControl.IsNecroEntity(alive))
|
||||
__result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user