- Костяной скакун (necroBoneSteed): мотоцикл из костей, сухожилий и сосудов с черепом-циклопом вместо фары; своя модель (бандл necrobonesteed), ванильная система предмета-транспорта (necroBoneSteedPlaceable, предпросмотр при установке); топливо - кровавая сфера, одна сфера = полный бак; теги коллайдеров и шейдер копируются с ванильного мотоцикла; рецепт на верстаке, Подмастерье - Книга Духа крысы: своя модель (бандл necrobooks) - чёрная кожа, светящийся узор со скелетом крысы и семиконечной звездой, ржавые уголки, ремешок - Турели не стреляют по существам мода (питомцы, рой, змея) - Гео-оссуарий тратит 1/10 прочности сферы на стак вместо целой сферы В игре не проверено. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
149 lines
5.8 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|