1.7.0: скакун на сферах в ячейках, ремонт костями; правки моделей скакуна и книги крысы
- Топливо скакуна - кровавые сферы в 4 ячейках модификаций, тратятся по очереди (одна сфера = 120 ед., бак 480); уровень бака считается по прочности сфер, выработанная сфера исчезает. Заправка канистрой убрана. В ячейки - только сферы. - Причина «не едет» и «-2147483648%»: Extends не наследует effect_group, у предмета скакуна не было ни ячеек, ни прочности. Своя группа: ModSlots 4, DegradationMax 4000; здоровье закреплено кодом (DegradationMax сфер в ячейках его перебивал бы). - Ремонт - 50 костей за ход вместо ремкомплекта; подсказка при нехватке, 13 языков. - Модель скакуна: сиденье и рама прогнуты по ванили, рога по оси руля, череп меньше и короче, свет фары в глазнице, крыло над передним колесом, зубы под черепом убраны. - Книга крысы: повёрнута как ванильная (палец на корешке), без металлических уголков. В игре не проверено. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
11db1116b0
commit
24d6e9f675
@@ -0,0 +1,260 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NecromancerTome
|
||||
{
|
||||
/// <summary>
|
||||
/// Топливо костяного скакуна - кровавые сферы в ячейках модификаций (указание 01.10.2026:
|
||||
/// «4 ячейки под кровавые сферы, которые будут постепенно по очереди тратиться; заправку
|
||||
/// через канистру убрать»). Ванильный бак (VPFuelTank) остаётся частью машины - двигатель,
|
||||
/// HUD и глохнутие читают его, - но для скакуна его уровень не хранится, а СЧИТАЕТСЯ по
|
||||
/// сферам: каждая сфера - FuelPerSphere единиц, пропорционально остатку прочности.
|
||||
///
|
||||
/// Расход: двигатель снимает топливо событием FuelRemove -> VPFuelTank.AddFuel ->
|
||||
/// SetFuelLevel(меньше). Префикс SetFuelLevel переводит разницу в прочность первой сферы
|
||||
/// с остатком (ячейки по порядку); выработанная сфера исчезает из ячейки, как в браслете и
|
||||
/// оссуарии. Рост уровня (ванильная заправка, загрузка из Meta) игнорируется - кровь
|
||||
/// приходит только сферами.
|
||||
///
|
||||
/// Ячейки: у ванили в слот модификации идёт только модификация с подходящими тегами и не
|
||||
/// больше MaxModsAllowed одного вида. Сфера - модификация браслета, поэтому для скакуна
|
||||
/// CanSwap решает сам: только сфера с остатком прочности, прочие моды не ставятся.
|
||||
///
|
||||
/// Здоровье: Extends не наследует effect_group (ItemClassesFromXml парсит эффекты только
|
||||
/// со своего узла), поэтому у necroBoneSteedPlaceable свой DegradationMax в items.xml. Но
|
||||
/// эффекты установленных модификаций применяются к хозяину, а у сферы DegradationMax
|
||||
/// base_set 500 - со сферами здоровье скакуна упало бы до 500 и прыгало бы при их смене.
|
||||
/// Поэтому MaxUseTimesBase скакуна закреплён кодом.
|
||||
/// </summary>
|
||||
public static class BoneSteedFuel
|
||||
{
|
||||
public const int Slots = 4;
|
||||
public const float FuelPerSphere = 120f;
|
||||
public const float MaxFuel = Slots * FuelPerSphere;
|
||||
public const int MaxHealth = 4000;
|
||||
|
||||
static int steedItemType = -1;
|
||||
|
||||
public static int SteedItemType
|
||||
{
|
||||
get
|
||||
{
|
||||
if (steedItemType < 0)
|
||||
{
|
||||
ItemValue iv = ItemClass.GetItem(BoneSteed.ItemName);
|
||||
steedItemType = iv != null ? iv.type : 0;
|
||||
}
|
||||
return steedItemType;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSteedItem(ItemValue iv) =>
|
||||
iv != null && iv.type != 0 && iv.type == SteedItemType;
|
||||
|
||||
public static bool IsSphere(ItemValue iv) =>
|
||||
iv != null && !iv.IsEmpty() && iv.ItemClass != null && iv.ItemClass.Name == BoneSteed.FuelItem;
|
||||
|
||||
/// <summary>Остаток сферы 0..1.</summary>
|
||||
static float Left(ItemValue sphere)
|
||||
{
|
||||
int max = sphere.MaxUseTimes;
|
||||
if (max <= 0)
|
||||
return 1f;
|
||||
return Mathf.Clamp01(1f - sphere.UseTimes / max);
|
||||
}
|
||||
|
||||
public static float FuelOf(Vehicle v)
|
||||
{
|
||||
ItemValue item = v?.itemValue;
|
||||
if (item == null)
|
||||
return 0f;
|
||||
float fuel = 0f;
|
||||
for (int i = 0; i < item.ModificationCount; i++)
|
||||
{
|
||||
ItemValue m = item.GetModification(i);
|
||||
if (IsSphere(m))
|
||||
fuel += Left(m) * FuelPerSphere;
|
||||
}
|
||||
return fuel;
|
||||
}
|
||||
|
||||
/// <summary>Снимает топливо со сфер по очереди. Возвращает остаток топлива.</summary>
|
||||
public static float Burn(Vehicle v, float amount)
|
||||
{
|
||||
ItemValue item = v.itemValue;
|
||||
for (int i = 0; i < item.ModificationCount && amount > 0f; i++)
|
||||
{
|
||||
ItemValue m = item.GetModification(i);
|
||||
if (!IsSphere(m))
|
||||
continue;
|
||||
int max = m.MaxUseTimes;
|
||||
if (max <= 0)
|
||||
{
|
||||
item.SetModification(i, ItemValue.None.Clone());
|
||||
amount -= FuelPerSphere;
|
||||
continue;
|
||||
}
|
||||
float have = Left(m) * FuelPerSphere;
|
||||
float take = Mathf.Min(have, amount);
|
||||
m.UseTimes += take / FuelPerSphere * max;
|
||||
amount -= take;
|
||||
if (m.UseTimes >= max - 0.001f)
|
||||
{
|
||||
item.SetModification(i, ItemValue.None.Clone());
|
||||
Debug.Log("[NecromancerTome] BoneSteed: сфера в ячейке " + i + " выработана и исчезла");
|
||||
}
|
||||
}
|
||||
return FuelOf(v);
|
||||
}
|
||||
|
||||
public static bool IsSteedTank(VPFuelTank tank) =>
|
||||
tank?.vehicle != null && BoneSteed.Is(tank.vehicle.entity);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(VPFuelTank), nameof(VPFuelTank.GetFuelLevel))]
|
||||
public static class Patch_VPFuelTank_GetFuelLevel_BoneSteed
|
||||
{
|
||||
public static bool Prefix(VPFuelTank __instance, ref float __result)
|
||||
{
|
||||
if (!BoneSteedFuel.IsSteedTank(__instance))
|
||||
return true;
|
||||
__result = BoneSteedFuel.FuelOf(__instance.vehicle);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(VPFuelTank), nameof(VPFuelTank.GetMaxFuelLevel))]
|
||||
public static class Patch_VPFuelTank_GetMaxFuelLevel_BoneSteed
|
||||
{
|
||||
public static bool Prefix(VPFuelTank __instance, ref float __result)
|
||||
{
|
||||
if (!BoneSteedFuel.IsSteedTank(__instance))
|
||||
return true;
|
||||
__result = BoneSteedFuel.MaxFuel;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(VPFuelTank), nameof(VPFuelTank.GetFuelLevelPercent))]
|
||||
public static class Patch_VPFuelTank_GetFuelLevelPercent_BoneSteed
|
||||
{
|
||||
public static bool Prefix(VPFuelTank __instance, ref float __result)
|
||||
{
|
||||
if (!BoneSteedFuel.IsSteedTank(__instance))
|
||||
return true;
|
||||
__result = Mathf.Clamp01(BoneSteedFuel.FuelOf(__instance.vehicle) / BoneSteedFuel.MaxFuel);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(VPFuelTank), nameof(VPFuelTank.SetFuelLevel))]
|
||||
public static class Patch_VPFuelTank_SetFuelLevel_BoneSteed
|
||||
{
|
||||
public static bool Prefix(VPFuelTank __instance, float _fuelLevel)
|
||||
{
|
||||
if (!BoneSteedFuel.IsSteedTank(__instance))
|
||||
return true;
|
||||
Vehicle v = __instance.vehicle;
|
||||
float have = BoneSteedFuel.FuelOf(v);
|
||||
// Загрузка (SetItemValue -> Meta/50) приходит, пока LoadingItem; её не трогаем.
|
||||
if (Patch_Vehicle_SetItemValue_BoneSteed.Loading || _fuelLevel >= have)
|
||||
{
|
||||
__instance.fuelLevel = have;
|
||||
return false;
|
||||
}
|
||||
float left = BoneSteedFuel.Burn(v, have - Mathf.Max(0f, _fuelLevel));
|
||||
__instance.fuelLevel = left;
|
||||
if (left <= 0f && EntityVehicle.VehicleFuelUsageModifier > 0f)
|
||||
v.FireEvent(VehiclePart.Event.FuelEmpty, __instance, 0f);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Vehicle), nameof(Vehicle.SetItemValue))]
|
||||
public static class Patch_Vehicle_SetItemValue_BoneSteed
|
||||
{
|
||||
public static bool Loading;
|
||||
|
||||
public static void Prefix() => Loading = true;
|
||||
|
||||
public static void Finalizer(Vehicle __instance)
|
||||
{
|
||||
Loading = false;
|
||||
if (BoneSteed.Is(__instance.entity))
|
||||
Debug.Log("[NecromancerTome] BoneSteed: ячеек " + __instance.itemValue.ModificationCount +
|
||||
", топливо " + BoneSteedFuel.FuelOf(__instance).ToString("0.#") + "/" + BoneSteedFuel.MaxFuel +
|
||||
", здоровье " + __instance.entity.Health + "/" + __instance.GetMaxHealth() +
|
||||
", расход x" + __instance.EffectFuelUsePer.ToString("0.##"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>В ячейки скакуна - только кровавые сферы с остатком прочности.</summary>
|
||||
[HarmonyPatch(typeof(XUiC_ItemPartStack), "CanSwap")]
|
||||
public static class Patch_XUiC_ItemPartStack_CanSwap_BoneSteed
|
||||
{
|
||||
public static bool Prefix(XUiC_ItemPartStack __instance, ItemStack stack, ref bool __result)
|
||||
{
|
||||
ItemStack current = __instance.xui?.AssembleItem?.CurrentItem;
|
||||
if (current == null || !BoneSteedFuel.IsSteedItem(current.itemValue))
|
||||
return true;
|
||||
ItemValue here = __instance.ItemValue;
|
||||
__result = BoneSteedFuel.IsSphere(stack.itemValue) &&
|
||||
stack.itemValue.UseTimes < stack.itemValue.MaxUseTimes &&
|
||||
(here == null || here.IsEmpty() || BoneSteedFuel.IsSphere(here));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Здоровье скакуна не зависит от сфер в ячейках (у сферы DegradationMax 500).</summary>
|
||||
[HarmonyPatch(typeof(ItemValue), nameof(ItemValue.MaxUseTimesBase), MethodType.Getter)]
|
||||
public static class Patch_ItemValue_MaxUseTimesBase_BoneSteed
|
||||
{
|
||||
public static void Postfix(ItemValue __instance, ref int __result)
|
||||
{
|
||||
if (BoneSteedFuel.IsSteedItem(__instance))
|
||||
__result = BoneSteedFuel.MaxHealth;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ремонт скакуна - костями вместо ремкомплекта (указание 01.10): BonesPerRepair
|
||||
/// костей за ход, как ванильный ремкомплект (1000 здоровья + 10% за уровень «Механика»).</summary>
|
||||
[HarmonyPatch(typeof(XUiM_Vehicle), nameof(XUiM_Vehicle.RepairVehicle))]
|
||||
public static class Patch_XUiM_Vehicle_RepairVehicle_BoneSteed
|
||||
{
|
||||
public const string BoneItem = "resourceBone";
|
||||
public const int BonesPerRepair = 50;
|
||||
|
||||
public static bool Prefix(XUi _xui, Vehicle _vehicle, ref bool __result)
|
||||
{
|
||||
Vehicle v = _vehicle ?? _xui.Vehicle.CurrentVehicle?.GetVehicle();
|
||||
if (v == null || !BoneSteed.Is(v.entity))
|
||||
return true;
|
||||
__result = false;
|
||||
EntityPlayerLocal player = _xui.playerUI.entityPlayer;
|
||||
ItemValue bone = ItemClass.GetItem(BoneItem);
|
||||
if (bone == null || bone.IsEmpty() || v.GetRepairAmountNeeded() <= 0)
|
||||
return false;
|
||||
int have = player.bag.GetItemCount(bone) + player.inventory.GetItemCount(bone);
|
||||
if (have < BonesPerRepair)
|
||||
{
|
||||
Audio.Manager.PlayInsidePlayerHead("misc/missingitemtorepair");
|
||||
GameManager.ShowTooltip(player, string.Format(Localization.Get("necroBoneSteedNeedBones"), BonesPerRepair),
|
||||
string.Empty, "ui_denied");
|
||||
return false;
|
||||
}
|
||||
float perk = 0f;
|
||||
ProgressionValue pv = player.Progression.GetProgressionValue("perkGreaseMonkey");
|
||||
if (pv != null)
|
||||
perk += pv.Level * 0.1f;
|
||||
v.RepairParts(1000, perk);
|
||||
int left = BonesPerRepair;
|
||||
left -= player.inventory.DecItem(bone, left);
|
||||
if (left > 0)
|
||||
player.bag.DecItem(bone, left);
|
||||
_xui.CollectedItemList.RemoveItemStack(new ItemStack(bone, BonesPerRepair));
|
||||
Audio.Manager.PlayInsidePlayerHead("craft_complete_item");
|
||||
__result = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user