Череп: три режима как у мебели - предмет / декор по центру / декор у стены

R с черепом-предметом - меню; выбор декора превращает стопку в ячейке в блок (превью, повороты
блока). С декором в руке R - обычное меню блока плюс «Просто череп» и второй вид. Действие
PlaceAsBlock у предмета убрано (ПКМ из предмета больше не ставит). Стопка блоков 50, как у черепа.
Описания черепа и декора (13 языков) - под новую схему.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-15 15:37:39 +03:00
co-authored by Claude Opus 5.5
parent 4cc244a98a
commit 99c2f183ad
5 changed files with 71 additions and 69 deletions
+63 -56
View File
@@ -5,60 +5,77 @@ using UnityEngine;
namespace NecromancerTome
{
/// <summary>
/// Череп ставится декором прямо из руки (15.10.2026, указание: «Сейчас держу в руках именно череп. И он нормальный.
/// Но его нельзя поставить как декор. Может сделать возможность через R переключаться в режим декора и сразу
/// выбирать центральный или у стены?»; «А при поднятии снова делать просто черепом»).
/// Удержание R с черепом в руке - круговое меню: «Просто череп» / «Декор: по центру» / «Декор: у стены».
/// ПКМ - ставит блок выбранного вида (Action1 = PlaceAsBlock у necroSkull в items.xml); в «Просто череп» - ничего.
/// Поднятый блок (E) и разрушенный - снова череп-предмет (CanPickup param1 / drop в blocks.xml).
/// Предмет остаётся ItemClass: действие в ItemValue не пишется (ItemValue.Write: тип, износ, качество, Meta,
/// метаданные, статы, моды) - старые стаки черепа читаются как прежде.
/// R для черепа игра сама не обрабатывает (PlayerMoveController открывает меню только оружию, ближнему бою и
/// блокам) - поэтому нажатие ловится постфиксом к PlayerMoveController.Update. Выбор - cvar игрока
/// (necroSkullDecorMode, без точки - пишется в сейв).
/// Череп - предмет или декор (15.10.2026, вторая версия). Указание после теста: «Посмотри как ставится мебель. Например,
/// горшки с цветами... Три режима. Предмет, и два декора. Если выбран декор, хват меняется на тот который при установке
/// строительных блоков или мебели, и я начинаю видеть типа превью того как блок будет стоять»; поворот - как у блоков.
/// Череп-предмет в руке, удержание R - меню: «Просто череп» / «Череп: по центру» / «Череп: у стены». Выбор декора
/// превращает всю стопку в ячейке пояса в блок этого вида: в руке настоящий блок - превью, повороты блока.
/// Блок-декор в руке, R - обычное меню блока (короткое нажатие - поворот), к нему добавлены «Просто череп» (стопка
/// обратно в черепа) и второй вид декора.
/// Поднятый (E) и разрушенный декор - снова череп-предмет (blocks.xml: CanPickup param1 / drop).
/// Предмет necroSkull не меняется (ItemClass, без действий) - сейвы с черепом целы; блоки-декор - отдельные блоки.
/// R для предмета игра не обрабатывает (PlayerMoveController открывает меню только оружию, ближнему бою и блокам) -
/// нажатие ловится постфиксом к PlayerMoveController.Update.
/// </summary>
public static class SkullDecorPlace
{
public const string ModeCVar = "necroSkullDecorMode";
public const int Off = 0, Center = 1, Back = 2;
public const int ItemIdx = 0, CenterIdx = 1, BackIdx = 2;
/// <summary>Номера наших пунктов в меню блока - вне занятых игрой (-2..10).</summary>
public const int BlockMenuItem = 40, BlockMenuCenter = 41, BlockMenuBack = 42;
public const string CenterBlock = "necroSkullDecorCenter";
public const string BackBlock = "necroSkullDecorBack";
public static bool IsSkull(ItemClass _item) => _item != null && _item.Name == NecroSkull.ItemName;
public static bool IsDecor(ItemClass _item) => _item != null && (_item.Name == CenterBlock || _item.Name == BackBlock);
public static int Mode(EntityAlive _e)
/// <summary>Стопка в руке превращается в _name (тот же счёт).</summary>
public static void Convert(EntityPlayerLocal _pl, string _name)
{
if (_e == null) return Off;
int m = Mathf.RoundToInt(_e.GetCVar(ModeCVar));
return m >= Off && m <= Back ? m : Off;
if (_pl?.inventory == null) return;
ItemStack cur = _pl.inventory.holdingItemStack;
if (cur == null || cur.IsEmpty() || !(IsSkull(cur.itemValue.ItemClass) || IsDecor(cur.itemValue.ItemClass))) return;
if (cur.itemValue.ItemClass.Name == _name) return;
ItemValue iv = ItemClass.GetItem(_name);
if (iv == null || iv.IsEmpty()) return;
_pl.inventory.SetItem(_pl.inventory.holdingItemIdx, new ItemStack(iv, cur.count));
}
public static void OnRadialCommand(XUiC_Radial _sender, int _commandIndex, XUiC_Radial.RadialContextAbs _context)
public static void OnItemMenu(XUiC_Radial _sender, int _commandIndex, XUiC_Radial.RadialContextAbs _context)
{
EntityPlayerLocal pl = _sender?.xui?.playerUI?.entityPlayer;
if (pl != null && _commandIndex >= Off && _commandIndex <= Back)
{
pl.SetCVar(ModeCVar, _commandIndex);
}
if (_commandIndex == CenterIdx) Convert(pl, CenterBlock);
else if (_commandIndex == BackIdx) Convert(pl, BackBlock);
}
public static void OpenMenu(PlayerMoveController _pmc, EntityPlayerLocal _pl)
public static void OpenItemMenu(PlayerMoveController _pmc, EntityPlayerLocal _pl)
{
XUiC_Radial r = _pmc.playerUI?.xui?.RadialWindow;
if (r == null) return;
int cur = Mode(_pl);
r.Open();
r.ResetRadialEntries();
r.CreateRadialEntry(Off, "ui_game_symbol_hand", "UIAtlas", "", Localization.Get("necroSkullModeOff"), cur == Off);
r.CreateRadialEntry(Center, "ui_game_symbol_skull", "UIAtlas", "", Localization.Get("necroSkullDecorCenter"), cur == Center);
r.CreateRadialEntry(Back, "ui_game_symbol_skull", "UIAtlas", "", Localization.Get("necroSkullDecorBack"), cur == Back);
r.SetCommonData(UIUtils.GetButtonIconForAction(_pmc.playerInput.Reload), OnRadialCommand,
new XUiC_Radial.RadialContextHoldingSlotIndex(_pl.inventory.holdingItemIdx), cur, false,
r.CreateRadialEntry(ItemIdx, "necroSkull", "ItemIconAtlas", "", Localization.Get("necroSkullModeOff"), true);
r.CreateRadialEntry(CenterIdx, "ui_game_symbol_skull", "UIAtlas", "", Localization.Get("necroSkullDecorCenter"));
r.CreateRadialEntry(BackIdx, "ui_game_symbol_skull", "UIAtlas", "", Localization.Get("necroSkullDecorBack"));
r.SetCommonData(UIUtils.GetButtonIconForAction(_pmc.playerInput.Reload), OnItemMenu,
new XUiC_Radial.RadialContextHoldingSlotIndex(_pl.inventory.holdingItemIdx), ItemIdx, false,
XUiC_Radial.RadialValidSameHoldingSlotIndex);
}
/// <summary>Меню блока с декором в руке: наши пункты - сами, остальное - игре.</summary>
public static void OnBlockMenu(XUiC_Radial _sender, int _commandIndex, XUiC_Radial.RadialContextAbs _context)
{
EntityPlayerLocal pl = _sender?.xui?.playerUI?.entityPlayer;
switch (_commandIndex)
{
case BlockMenuItem: Convert(pl, NecroSkull.ItemName); return;
case BlockMenuCenter: Convert(pl, CenterBlock); return;
case BlockMenuBack: Convert(pl, BackBlock); return;
}
_sender.handleBlockShapeCommand(_sender, _commandIndex, _context);
}
}
/// <summary>R с черепом в руке - меню вида декора.</summary>
/// <summary>R с черепом-предметом в руке - меню «предмет / два декора».</summary>
[HarmonyPatch(typeof(PlayerMoveController), nameof(PlayerMoveController.Update))]
public static class Patch_PlayerMoveController_Update_SkullDecor
{
@@ -78,39 +95,29 @@ namespace NecromancerTome
{
return;
}
SkullDecorPlace.OpenMenu(__instance, pl);
SkullDecorPlace.OpenItemMenu(__instance, pl);
}
}
/// <summary>ПКМ с черепом: ставит выбранный вид; в «Просто череп» не ставит (и подсказывает про R).</summary>
[HarmonyPatch(typeof(ItemActionPlaceAsBlock), nameof(ItemActionPlaceAsBlock.ExecuteAction))]
public static class Patch_ItemActionPlaceAsBlock_ExecuteAction_SkullDecor
/// <summary>Меню блока с черепом-декором в руке: к пунктам игры - «Просто череп» и второй вид.</summary>
[HarmonyPatch(typeof(XUiC_Radial), nameof(XUiC_Radial.SetupBlockShapeData))]
public static class Patch_XUiC_Radial_SetupBlockShapeData_SkullDecor
{
static float _hintAt;
public static bool Prefix(ItemActionPlaceAsBlock __instance, ItemActionData _actionData, bool _bReleased)
public static void Postfix(XUiC_Radial __instance)
{
if (!SkullDecorPlace.IsSkull(__instance.item))
EntityPlayerLocal pl = __instance.xui?.playerUI?.entityPlayer;
ItemClass held = pl?.inventory?.holdingItem;
if (!SkullDecorPlace.IsDecor(held))
{
return true;
return;
}
EntityAlive e = _actionData?.invData?.holdingEntity;
int mode = SkullDecorPlace.Mode(e);
if (mode == SkullDecorPlace.Off)
{
if (_bReleased && e is EntityPlayerLocal pl && Time.time - _hintAt > 2f)
{
_hintAt = Time.time;
GameManager.ShowTooltip(pl, Localization.Get("necroSkullDecorHint"));
}
return false;
}
BlockValue bv = Block.GetBlockValue(mode == SkullDecorPlace.Back ? SkullDecorPlace.BackBlock : SkullDecorPlace.CenterBlock);
if (!bv.isair)
{
__instance.blockToPlace = bv;
}
return true;
__instance.CreateRadialEntry(SkullDecorPlace.BlockMenuItem, "necroSkull", "ItemIconAtlas", "", Localization.Get("necroSkullModeOff"));
if (held.Name == SkullDecorPlace.CenterBlock)
__instance.CreateRadialEntry(SkullDecorPlace.BlockMenuBack, "ui_game_symbol_skull", "UIAtlas", "", Localization.Get("necroSkullDecorBack"));
else
__instance.CreateRadialEntry(SkullDecorPlace.BlockMenuCenter, "ui_game_symbol_skull", "UIAtlas", "", Localization.Get("necroSkullDecorCenter"));
__instance.SetCommonData(UIUtils.GetButtonIconForAction(__instance.xui.playerUI.playerInput.Activate), SkullDecorPlace.OnBlockMenu,
new XUiC_Radial.RadialContextHoldingSlotIndex(pl.inventory.holdingItemIdx), -1, true, XUiC_Radial.RadialValidSameHoldingSlotIndex);
}
}
}