Files
necromants-tome-7d2d-3-2/HarmonySrc/NecroDevCommands.cs
T
AlexCubeandClaude Opus 5.5 a352a408a4 Урны ремонтника и лутера: большая зона, запуск кнопкой, кнопки-состояния
- necroCinerariumRepair/+Skull и necroCinerariumLoot/+Skull (пока на модели урны сборщика),
  рецепты (урна + ремкомплект / отмычка, на крови и на соке), открываются с урнами.
- Зона - сторона притязания сервера или 31/21/11 (пункт «Размер зоны»), весь столб мира,
  каркас по «Показать границы»; работа - по «Запустить», один проход, «Остановить».
- Ремонтник: обход столбов из общего бюджета клеток, очередь повреждённых блоков с
  RepairItems (зоны пересекаются - блок берёт первый), ремонт раз в 2.5 с материалами из
  ящиков по нитям (доля по повреждению), не хватает - ждёт с мигающей кнопкой.
- Лутер: предметы на земле, мешки с зомби, красные трупы (тем же путём, что игрок, с
  находкой черепа) - в ящик с любой нитью; рюкзаки игроков и аэродроп не трогает.
- Кнопки на модели (рендереры «button»): жёлтая - ждёт, мигает - нехватка/ящик полон,
  зелёная - работа, красная - прилив. Запись урны v6 (зона, проход, курсор, кто нажал).
- Тексты 13 языков; команды разработки necrodamage, necrodrop, necrobag, necrocorpse,
  necrourn run|stop|size.

Проверено в игре: ремонтник (нехватка -> материалы -> починил 2 блока), лутер (предметы
с земли). Мешки и красные трупы - не проверены.

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

429 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 "necrodamage":
{
// necrodamage x y z урон - повредить блок (проверка ремонтника)
Vector3i dp = P(_params, 0);
BlockValue dbv = w.GetBlock(dp);
dbv.damage = Mathf.Min(dbv.Block.MaxDamage - 1, dbv.damage + int.Parse(_params[3]));
w.SetBlockRPC(dp, dbv);
Out("necrodamage: " + dbv.Block.GetBlockName() + " в " + dp + " повреждение " + dbv.damage + "/" + dbv.Block.MaxDamage);
break;
}
case "necrodrop":
{
// necrodrop предмет число x y z - бросить на землю (проверка лутера)
ItemValue div = ItemClass.GetItem(_params[0], true);
GameManager.Instance.ItemDropServer(new ItemStack(div, int.Parse(_params[1])), P(_params, 2).ToVector3() + new Vector3(0.5f, 0.5f, 0.5f), Vector3.zero);
Out("necrodrop: " + _params[0] + " x" + _params[1]);
break;
}
case "necrobag":
{
// necrobag x y z - мешок с зомби (EntityLootContainer со списком cntDropBag), нетронутый
Entity bag = EntityFactory.CreateEntity(EntityClass.FromString("DroppedLootContainer"), P(_params, 0).ToVector3() + new Vector3(0.5f, 0.3f, 0.5f));
bag.lootList = "cntDropBag";
w.SpawnEntityInWorld(bag);
Out("necrobag: " + bag.entityId);
break;
}
case "necrocorpse":
{
// necrocorpse x y z [класс] - зомби, убитый и помеченный красным трупом (как VictimPatch)
string cls = _params.Count > 3 ? _params[3] : "zombieArlene";
EntityAlive z = EntityFactory.CreateEntity(EntityClass.FromString(cls), P(_params, 0).ToVector3() + new Vector3(0.5f, 0.1f, 0.5f)) as EntityAlive;
w.SpawnEntityInWorld(z);
ThreadManager.StartCoroutine(KillAndMark(z));
Out("necrocorpse: " + z.entityId + " " + cls);
break;
}
case "necrogod": God(pl, _params.Count == 0 || _params[0] != "off"); break;
case "necroclick":
// necroclick 0|1 [x y z] - по блоку под прицелом или по указанной клетке (луч не всегда попадает в низкий череп).
if (_params.Count >= 4)
{
CinerariumThreads.ClickAt(pl, w, P(_params, 1), _params[0] == "1");
}
else
{
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"));
}
/// <summary>Режим бога и невидимость для ИИ: пока ассистент проверяет, персонаж стоит - зомби тестового мира его не трогают
/// (18.10: «пока персонаж просто стоит, его скушают»; «Я обычно ставлю режим Бога»).</summary>
static void God(EntityPlayerLocal _pl, bool _on)
{
_pl.IsGodMode.Value = _on;
_pl.SetIgnoredByAI(_on);
Out("necrogod: " + (_on ? "бог, ИИ не видит" : "обычный"));
}
static System.Collections.IEnumerator KillAndMark(EntityAlive _z)
{
yield return new WaitForSeconds(1f);
_z.DamageEntity(new DamageSource(EnumDamageSource.Internal, EnumDamageTypes.Suicide), 99999, false);
yield return new WaitForSeconds(1f);
if (_z.IsDead())
{
CorpseLoot.MarkCorpse(_z);
}
UnityEngine.Debug.Log("[NecromancerTome] mailbox: necrocorpse: " + _z.entityId + (_z.IsDead() ? " убит и помечен" : " НЕ умер"));
}
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 == "fill")
{
// Все пустые ячейки - полной стопкой предмета (ящик «полон» по-честному).
ItemValue iv = ItemClass.GetItem(_p[4], true);
for (int i = 0; i < s.ItemGrid.Length; i++)
{
if (s.ItemGrid[i] == null || s.ItemGrid[i].IsEmpty())
{
s.UpdateSlot(i, new ItemStack(iv.Clone(), iv.ItemClass.Stacknumber.Value));
}
}
s.SetModified();
}
else 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());
}
/// <summary>Черепа на пути нити: тройки координат после конца нити, по порядку от урны.</summary>
static List<Vector3i> Via(List<string> _p, int _i)
{
List<Vector3i> l = new List<Vector3i>();
for (int k = _i; k + 2 < _p.Count; k += 3)
{
l.Add(P(_p, k));
}
return l;
}
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), Via(_p, 7)); break;
case "to": u.SetThreadTo(P(_p, 4), Via(_p, 7)); break;
case "out": u.AddThreadOut(P(_p, 4), Via(_p, 7)); 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 "run": u.ZoneCommand("zurn_run", GameManager.Instance.World.GetPrimaryPlayer()); break;
case "stop": u.ZoneCommand("zurn_stop", null); break;
case "size": u.ZoneSide = int.Parse(_p[4]); u.SetModified(); break;
case "group":
u.GroupMask = _p[4] == "нет" || _p[4] == "none" ? 0u : CinerariumGroups.All;
u.SetModified();
break;
}
string blink = "нет";
Chunk ch = _w.GetChunkFromWorldPos(pos) as Chunk;
BlockEntityData be = ch?.GetBlockEntity(pos);
if (be != null && be.bHasTransform)
{
CinerariumBlink cb = be.transform.GetComponent<CinerariumBlink>();
blink = cb == null ? "нет" : (cb.enabled ? "мигает" : "выкл") + " (" + cb.Renderers.Count + " рендер.)";
}
Out("necrourn: " + pos + " " + _w.GetBlock(pos).Block?.GetBlockName() + ", череп " + u.HasSkull + ", застрял " + u.Stuck + ", мигание " + blink +
", откуда " + u.ThreadFrom.Count + ", куда " + (u.HasThreadTo ? u.ThreadTo.ToString() : "нет") + ", от урны " + u.ThreadOut.Count +
", зона " + (u.IsBigZone ? u.ZoneSideNow + (u.ZoneRunning ? " идёт" : " ждёт") + (u.ZoneLacking ? " нехватка" : "") + " курсор " + u.ZoneCursor : "-") + ", образец " + (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);
}
[Preserve]
public class ConsoleCmdNecroGod : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrogod" };
public override string getDescription() => "NecromancerTome (разработка): режим бога и невидимость для ИИ [off]";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrogod", _params);
}
[Preserve]
public class ConsoleCmdNecroDamage : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrodamage" };
public override string getDescription() => "NecromancerTome (разработка): повредить блок x y z урон";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrodamage", _params);
}
[Preserve]
public class ConsoleCmdNecroDrop : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrodrop" };
public override string getDescription() => "NecromancerTome (разработка): бросить предмет: имя число x y z";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrodrop", _params);
}
[Preserve]
public class ConsoleCmdNecroBag : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrobag" };
public override string getDescription() => "NecromancerTome (разработка): мешок с зомби x y z";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrobag", _params);
}
[Preserve]
public class ConsoleCmdNecroCorpse : ConsoleCmdAbstract
{
public override string[] getCommands() => new[] { "necrocorpse" };
public override string getDescription() => "NecromancerTome (разработка): красный труп x y z [класс]";
public override bool AllowedInMainMenu => false;
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) => NecroDev.Run("necrocorpse", _params);
}
}