Files
necromants-tome-7d2d-3-2/HarmonySrc/BoneSteedPatch.cs
T
AlexCubeandClaude Opus 5.5 11db1116b0 1.7.0: костяной скакун, книга Духа крысы, турели, расход сфер в оссуарии
- Костяной скакун (necroBoneSteed): мотоцикл из костей, сухожилий и сосудов
  с черепом-циклопом вместо фары; своя модель (бандл necrobonesteed),
  ванильная система предмета-транспорта (necroBoneSteedPlaceable, предпросмотр
  при установке); топливо - кровавая сфера, одна сфера = полный бак; теги
  коллайдеров и шейдер копируются с ванильного мотоцикла; рецепт на верстаке,
  Подмастерье
- Книга Духа крысы: своя модель (бандл necrobooks) - чёрная кожа, светящийся
  узор со скелетом крысы и семиконечной звездой, ржавые уголки, ремешок
- Турели не стреляют по существам мода (питомцы, рой, змея)
- Гео-оссуарий тратит 1/10 прочности сферы на стак вместо целой сферы

В игре не проверено.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:58:18 +03:00

149 lines
5.8 KiB
C#

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;
}
}
}