Files
necromants-tome-7d2d-3-2/HarmonySrc/NecroDevCommands.cs
T
AlexCubeandClaude Opus 5.5 6693d96d5e Логист - латунное кадило: два вида, дух 1/3 из окон, мигание; команды разработки
- Блоки логиста на модели кадила: по центру на подставке (necroCinerariumLogist) и новый вид у
  стены на кронштейне (necroCinerariumLogistWall/+Skull); выбор вида - R с урной в руке.
- Дух логиста втрое меньше, в покое сидит в кадиле, на перевозку вылетает через окно крышки
  (у стены - не в стену) и возвращается тем же окном.
- Застрял (ящик «куда» полон) - урна мигает (CinerariumBlink) вместо почерневшего духа;
  прежняя зелёная подмена свечения старой модели убрана.
- Команды разработки без мыши: necrospawn, necroexit [quit], necrolook, necroaim, necroplace,
  necrohold, necrobox, necrourn, necroclick.

Проверено в игре (test8): перевозка обоими видами, вылет духа, вид у стены от стены.

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

279 lines
11 KiB
C#

using System.Collections.Generic;
using System.Globalization;
using System.Text;
using UnityEngine;
using UnityEngine.Scripting;
namespace NecromancerTome
{
/// <summary>
/// Команды разработки для проверок без мыши (18.10.2026: «И управление персонажем тоже, чтобы ты мог его повернуть как надо,
/// довести куда надо»). Набираются через почтовый ящик (NecroMailbox), ответы - в лог. Перемещение - teleport игры
/// (tp x y z), время - settime, предметы - giveself; здесь - то, чего у игры нет.
/// necrolook yaw [pitch] | necrolook at x y z - повернуть взгляд (градусы; север 0, восток 90; pitch вверх +, вниз -)
/// necroaim - позиция игрока и блок под прицелом
/// necroplace блок x y z [поворот 0-3] - поставить блок (как игрок: OnBlockAdded, TE)
/// necrohold предмет [число] - положить в текущую ячейку пояса и взять в руку
/// necrobox x y z list|clear|add предмет число - содержимое хранилища (ящик, сумка урны)
/// necrourn x y z info|skull 0|1|from|to|out bx by bz|clear|sample предмет число|group все|нет - урна
/// necroclick 0|1 - отпущенная ЛКМ/ПКМ ножом для нитей по блоку под прицелом
/// </summary>
public static class NecroDev
{
static void Out(string _s) => SdtdConsole.Instance.Output(_s);
static float F(string _s) => float.Parse(_s, CultureInfo.InvariantCulture);
static Vector3i P(List<string> _p, int _i) => new Vector3i(int.Parse(_p[_i]), int.Parse(_p[_i + 1]), int.Parse(_p[_i + 2]));
/// <summary>Общий вход всех команд: игрок есть - выполнить, ошибка - строкой в ответ.</summary>
public static void Run(string _cmd, List<string> _params)
{
World w = GameManager.Instance.World;
EntityPlayerLocal pl = w?.GetPrimaryPlayer();
if (pl == null)
{
Out(_cmd + ": нет игрока");
return;
}
try
{
switch (_cmd)
{
case "necrolook": Look(pl, _params); break;
case "necroaim": Aim(w, pl); break;
case "necroplace": PlaceBlock(w, _params); break;
case "necrohold": Hold(pl, _params); break;
case "necrobox": Box(w, _params); break;
case "necrourn": Urn(w, _params); break;
case "necroclick": CinerariumThreads.Click(pl, _params.Count > 0 && _params[0] == "1"); Out("necroclick: ok"); break;
}
}
catch (System.Exception e)
{
Out(_cmd + ": ошибка - " + e.Message);
}
}
static void Look(EntityPlayerLocal _pl, List<string> _p)
{
float yaw, pitch = 0f;
if (_p.Count >= 4 && _p[0] == "at")
{
Vector3 eye = _pl.getHeadPosition();
Vector3 t = new Vector3(F(_p[1]) + 0.5f, F(_p[2]) + 0.5f, F(_p[3]) + 0.5f);
Vector3 d = t - eye;
yaw = Mathf.Atan2(d.x, d.z) * Mathf.Rad2Deg;
pitch = Mathf.Atan2(d.y, new Vector2(d.x, d.z).magnitude) * Mathf.Rad2Deg; // в игре + вверх
}
else
{
yaw = F(_p[0]);
if (_p.Count > 1)
{
pitch = F(_p[1]);
}
}
_pl.TeleportToPosition(_pl.position, false, new Vector3(pitch, yaw, 0f));
Out("necrolook: yaw " + yaw.ToString("F0") + ", pitch " + pitch.ToString("F0"));
}
static void Aim(World _w, EntityPlayerLocal _pl)
{
StringBuilder sb = new StringBuilder("necroaim: игрок ").Append(_pl.position.ToString("F1")).Append(", взгляд ").Append(_pl.rotation.ToString("F0"));
WorldRayHitInfo hi = _pl.HitInfo;
if (hi != null && hi.bHitValid)
{
BlockValue bv = _w.GetBlock(hi.hit.blockPos);
sb.Append(", под прицелом ").Append(hi.hit.blockPos).Append(' ').Append(bv.Block?.GetBlockName()).Append(" rot ").Append(bv.rotation)
.Append(", дальность ").Append(Mathf.Sqrt(hi.hit.distanceSq).ToString("F1"));
}
else
{
sb.Append(", под прицелом ничего");
}
Out(sb.ToString());
}
static void PlaceBlock(World _w, List<string> _p)
{
Block b = Block.GetBlockByName(_p[0], true);
if (b == null)
{
Out("necroplace: нет блока " + _p[0]);
return;
}
BlockValue bv = b.ToBlockValue();
if (_p.Count > 4)
{
bv.rotation = (byte)int.Parse(_p[4]);
}
Vector3i pos = P(_p, 1);
_w.SetBlockRPC(pos, bv);
// Как поставленный игроком (хозяин - мы): ящики без хозяина нити и духи не считают ящиками игрока.
if (_w.GetTileEntity(pos) is TileEntityComposite comp && comp.Owner == null)
{
comp.SetOwner(Platform.PlatformManager.InternalLocalUserIdentifier);
}
Out("necroplace: " + b.GetBlockName() + " в " + pos + " rot " + bv.rotation);
}
static void Hold(EntityPlayerLocal _pl, List<string> _p)
{
ItemValue iv = ItemClass.GetItem(_p[0], true);
if (iv == null || iv.IsEmpty())
{
Out("necrohold: нет предмета " + _p[0]);
return;
}
int n = _p.Count > 1 ? int.Parse(_p[1]) : 1;
if (iv.ItemClass.HasQuality)
{
iv = new ItemValue(iv.type, 1, 1, true);
}
_pl.inventory.SetItem(_pl.inventory.holdingItemIdx, new ItemStack(iv, n));
Out("necrohold: " + iv.ItemClass.GetItemName() + " x" + n + " в ячейке " + _pl.inventory.holdingItemIdx);
}
static TEFeatureStorage StorageAt(World _w, Vector3i _p)
{
BlockValue bv = _w.GetBlock(_p);
Vector3i owner = bv.ischild ? _p + bv.parent : _p;
return _w.GetTileEntity(owner) is TileEntityComposite c && c.TryGetSelfOrFeature<TEFeatureStorage>(out TEFeatureStorage s) ? s : null;
}
static void Box(World _w, List<string> _p)
{
TEFeatureStorage s = StorageAt(_w, P(_p, 0));
if (s == null)
{
Out("necrobox: нет хранилища в " + P(_p, 0));
return;
}
string op = _p.Count > 3 ? _p[3] : "list";
if (op == "clear")
{
for (int i = 0; i < s.ItemGrid.Length; i++)
{
s.UpdateSlot(i, ItemStack.Empty.Clone());
}
s.SetModified();
}
else if (op == "add")
{
ItemValue iv = ItemClass.GetItem(_p[4], true);
ItemStack st = new ItemStack(iv, int.Parse(_p[5]));
CinerariumWork.Place(st, new[] { s });
s.SetModified();
if (st.count > 0)
{
Out("necrobox: не влезло " + st.count);
}
}
StringBuilder sb = new StringBuilder("necrobox: ").Append(P(_p, 0)).Append(':');
for (int i = 0; i < s.ItemGrid.Length; i++)
{
ItemStack st = s.ItemGrid[i];
if (st != null && !st.IsEmpty())
{
sb.Append(' ').Append(st.itemValue.ItemClass?.GetItemName()).Append('x').Append(st.count);
}
}
Out(sb.ToString());
}
static void Urn(World _w, List<string> _p)
{
Vector3i pos = P(_p, 0);
TEFeatureCinerarium u = CinerariumThreads.UrnAt(_w, pos);
if (u == null)
{
Out("necrourn: нет урны в " + pos);
return;
}
string op = _p.Count > 3 ? _p[3] : "info";
switch (op)
{
case "skull": u.SetSkull(_p[4] == "1"); break;
case "from": u.AddThreadFrom(P(_p, 4)); break;
case "to": u.SetThreadTo(P(_p, 4)); break;
case "out": u.AddThreadOut(P(_p, 4)); break;
case "clear": u.ClearThreads(); break;
case "sample":
u.SetSample(_p.Count > 5 ? new ItemStack(ItemClass.GetItem(_p[4], true), int.Parse(_p[5])) : ItemStack.Empty.Clone());
break;
case "group":
u.GroupMask = _p[4] == "нет" || _p[4] == "none" ? 0u : CinerariumGroups.All;
u.SetModified();
break;
}
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck +
", откуда " + u.ThreadFrom.Count + ", куда " + (u.HasThreadTo ? u.ThreadTo.ToString() : "нет") + ", от урны " + u.ThreadOut.Count +
", образец " + (u.Sample == null || u.Sample.IsEmpty() ? "нет" : u.Sample.itemValue.ItemClass?.GetItemName() + "x" + u.Sample.count));
}
}
[Preserve]
public class ConsoleCmdNecroLook : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrolook" };
public override string getDescription() => "NecromancerTome (разработка): повернуть взгляд: yaw [pitch] | at x y z";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrolook", _params);
}
[Preserve]
public class ConsoleCmdNecroAim : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necroaim" };
public override string getDescription() => "NecromancerTome (разработка): позиция игрока и блок под прицелом";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroaim", _params);
}
[Preserve]
public class ConsoleCmdNecroPlace : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necroplace" };
public override string getDescription() => "NecromancerTome (разработка): поставить блок: имя x y z [поворот]";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroplace", _params);
}
[Preserve]
public class ConsoleCmdNecroHold : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrohold" };
public override string getDescription() => "NecromancerTome (разработка): предмет в руку: имя [число]";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrohold", _params);
}
[Preserve]
public class ConsoleCmdNecroBox : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrobox" };
public override string getDescription() => "NecromancerTome (разработка): хранилище: x y z list|clear|add предмет число";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrobox", _params);
}
[Preserve]
public class ConsoleCmdNecroUrn : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrourn" };
public override string getDescription() => "NecromancerTome (разработка): урна: x y z info|skull|from|to|out|clear|sample|group";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrourn", _params);
}
[Preserve]
public class ConsoleCmdNecroClick : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necroclick" };
public override string getDescription() => "NecromancerTome (разработка): нож для нитей: 0 - ЛКМ, 1 - ПКМ по блоку под прицелом";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necroclick", _params);
}
}