using System.Collections.Generic; using HarmonyLib; using UnityEngine; namespace NecromancerTome { /// /// Живой кролик в мешке (указание 22.10.2026: «Я бы хватал кролика, но в отличие от курицы пусть он сразу /// оказывается в сумке»; предмет - «Живой кролик в мешке»). Ваниль даёт схватить любое существо, у которого в /// описании есть PickupItem (EntityAlive: команда grab голой рукой, предмет кладётся в слот руки, существо /// уходит из мира через Collect) - так ловят курицу. Кролику PickupItem дан в entityclasses.xml, а здесь его /// мешок кладётся сразу в рюкзак (нет места - на пояс, иначе под ноги). /// /// Выпустить - ЛКМ с мешком в руке (ItemActionSpawnEntity, Entity=animalRabbit): ваниль предмет не тратит, тратим /// здесь. Разделать - «Разобрать» в рюкзаке. Стервятник кролика по-прежнему несёт под лапами (Possession.Carryable). Выключатель - necrorabbit off: /// тогда захват идёт ванильно, мешок - в руку, как курица. /// public static class RabbitGrab { public const string SackItem = "necroRabbitSack"; public static bool Enabled = true; public static bool IsSackAnimal(EntityAlive _e) => _e != null && _e.EntityClass != null && _e.EntityClass.PickupItem == SackItem; public static bool IsSack(ItemValue _iv) => _iv != null && _iv.ItemClass != null && _iv.ItemClass.GetItemName() == SackItem; } /// Захват кролика - мешок сразу в рюкзак. [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.OnEntityActivated))] public static class Patch_EntityAlive_OnEntityActivated_RabbitGrab { public static bool Prefix(EntityAlive __instance, EntityActivationCommand _command, EntityPlayerLocal _playerFocusing) { if (!RabbitGrab.Enabled || _command.commandId != "grab" || _playerFocusing == null || !RabbitGrab.IsSackAnimal(__instance)) { return true; } ItemClass ic = ItemClass.GetItemClass(RabbitGrab.SackItem); if (ic == null) { Debug.LogWarning("[NecromancerTome] RabbitGrab: нет предмета " + RabbitGrab.SackItem + " - захват ванильный"); return true; } ItemStack sack = new ItemStack(new ItemValue(ic.Id), 1); string where = "рюкзак"; if (_playerFocusing.bag == null || !_playerFocusing.bag.AddItem(sack)) { where = "пояс"; if (_playerFocusing.inventory == null || !_playerFocusing.inventory.AddItem(sack)) { where = "под ноги"; _playerFocusing.world.gameManager.ItemDropServer(sack, _playerFocusing.GetPosition() + Vector3.up * 0.5f, Vector3.zero); } } _playerFocusing.MinEventContext.Other = __instance; _playerFocusing.FireEvent(MinEventTypes.onSelfPickupOther); __instance.MinEventContext.Other = _playerFocusing; __instance.FireEvent(MinEventTypes.onSelfPickedUpByOther); Audio.Manager.PlayInsidePlayerHead(ic.Properties.GetString("SoundPickup")); __instance.Collect(_playerFocusing.entityId); Debug.Log("[NecromancerTome] RabbitGrab: кролик " + __instance.entityId + " пойман, мешок - " + where); return false; } } /// Подсказка над кроликом - своя (ваниль пишет «схватить курицу» для любого существа с PickupItem). [HarmonyPatch(typeof(EntityAlive), nameof(EntityAlive.GetActivationText))] public static class Patch_EntityAlive_GetActivationText_RabbitGrab { public static void Postfix(EntityAlive __instance, ref string __result) { if (RabbitGrab.Enabled && RabbitGrab.IsSackAnimal(__instance)) { __result = Localization.Get("necroRabbitGrab"); } } } /// Выпуск из мешка тратит мешок (ванильный SpawnEntity предмет не трогает). [HarmonyPatch(typeof(ItemActionSpawnEntity), "Spawn", new System.Type[] { typeof(ItemActionData) })] public static class Patch_ItemActionSpawnEntity_Spawn_RabbitSack { public static void Postfix(ItemActionData _actionData) { ItemInventoryData inv = _actionData?.invData; if (inv?.holdingEntity == null || !RabbitGrab.IsSack(inv.itemValue)) { return; } Inventory pack = inv.holdingEntity.inventory; ItemStack held = pack.GetItem(inv.slotIdx); if (held == null || held.IsEmpty() || !RabbitGrab.IsSack(held.itemValue)) { return; } held = held.Clone(); held.count--; pack.SetItem(inv.slotIdx, held.count > 0 ? held : ItemStack.Empty.Clone()); Debug.Log("[NecromancerTome] RabbitGrab: кролик выпущен из мешка, осталось " + Mathf.Max(0, held.count)); } } /// Разделать кролика - «Разобрать» в рюкзаке (указание 22.10: «Кролика можно будет разобрать на кожу, кости и /// мясо»; «Разделывать будем по команде сломать»). Пункт ваниль показывает сама: мешок из кожи (Mleather, forge_category /// leather), вес не меньше кожи. Но ванильная разборка даёт один материал через очередь крафта - у мешка она наша: /// вся стопка сразу, на каждого кролика добыча разделки ножом (entityclasses.xml animalRabbit, butcherHarvest). [HarmonyPatch(typeof(ItemActionEntryScrap), nameof(ItemActionEntryScrap.OnActivated))] public static class Patch_ItemActionEntryScrap_OnActivated_RabbitSack { static readonly string[] Yield = { "foodRawMeat", "resourceLeather", "resourceBone" }; static readonly int[] YieldCount = { 10, 2, 5 }; public static bool Prefix(ItemActionEntryScrap __instance) { if (!(__instance.ItemController is IItemStackController isc)) { return true; } ItemStack st = isc.ItemStack; if (st == null || st.IsEmpty() || !RabbitGrab.IsSack(st.itemValue)) { return true; } EntityPlayerLocal player = __instance.ItemController.xui.playerUI.entityPlayer; XUiM_PlayerInventory pack = __instance.ItemController.xui.PlayerInventory; int n = st.count; isc.ItemStack = ItemStack.Empty.Clone(); for (int i = 0; i < Yield.Length; i++) { ItemClass ic = ItemClass.GetItemClass(Yield[i]); if (ic == null) { continue; } ItemStack s = new ItemStack(new ItemValue(ic.Id), YieldCount[i] * n); if (!pack.AddItem(s)) { GameManager.Instance.ItemDropServer(s, player.GetPosition(), new Vector3(0.5f, 0f, 0.5f)); } } isc.WindowGroup?.Controller?.SetAllChildrenDirty(); Audio.Manager.PlayInsidePlayerHead("rabbitdeath"); Debug.Log("[NecromancerTome] RabbitGrab: разобрано мешков с кроликами - " + n); return false; } } public class ConsoleCmdNecroRabbit : ConsoleCmdAbstract { public override bool IsExecuteOnClient => true; public override bool AllowedInMainMenu => false; public override string[] getCommands() { return new string[] { "necrorabbit" }; } public override string getDescription() { return "Necromancer's Tome: grabbed rabbit goes straight to the backpack - switch."; } public override string getHelp() { return "necrorabbit - status\n" + "necrorabbit on|off - off: vanilla grab, the sack goes to the hand like a chicken"; } public override void Execute(List _params, CommandSenderInfo _senderInfo) { SdtdConsole con = SingletonMonoBehaviour.Instance; if (_params.Count >= 1 && (_params[0] == "on" || _params[0] == "off")) { RabbitGrab.Enabled = _params[0] == "on"; } con.Output("necrorabbit: " + (RabbitGrab.Enabled ? "on - rabbit sack goes to the backpack" : "off - vanilla grab")); } } }