Книга: главы собаки, стервятника, волка, медведя; гримуары; чертоги 25x13x25 с нумерованными дверями
- Главы питомцев с чертежами (Fig. III-VI), команда necroobj для выгрузки моделей - Книги призыва питомцев переименованы в гримуары (13 языков) - Чертоги: второй этаж; таблички necroDoorNumber (только в редакторе) нумеруют двери, дверь с номером закрыта до события (SetDoorOpen, necropalace door N open|close), с начала открыты 2, 4, 5, 6 - Дух: плавный шаг по ступенькам, проход в проёмы с порогом - Стервятник: снижение только C, без Ctrl-переключателя Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
32065dacac
commit
bdca1bdba5
+208
-20
@@ -47,8 +47,18 @@ namespace NecromancerTome
|
||||
public const string BedBlock = "necroPalaceBed";
|
||||
public const string ShellFrom = "woodShapes:cube";
|
||||
public const string ShellTo = "terrBedrock";
|
||||
/// <summary>Межкомнатные двери - их чертоги открывают сами при входе (кроме входной).</summary>
|
||||
/// <summary>Межкомнатные двери - их чертоги открывают сами при входе (кроме входной и дверей с номером).</summary>
|
||||
public const string PassageDoor = "exteriorHouseDoorWhite";
|
||||
/// <summary>Табличка с номером двери (указание 09.10): ставится в редакторе рядом с дверью или над ней,
|
||||
/// в ней пишется номер (до двух цифр). В чертоги не ставится (вместо неё воздух) - видна только в редакторе.
|
||||
/// Дверь с номером закрыта, пока событие не откроет её (SetDoorOpen).</summary>
|
||||
public const string DoorTagBlock = "necroDoorNumber";
|
||||
/// <summary>Двери с номером, открытые с самого начала (указание 09.10): 2 туалет, 4 ванная, 5 прихожая,
|
||||
/// 6 лаборатория. Остальные закрыты до события: 1 входная - бутафорская, не открывается никогда; 3 лестница,
|
||||
/// 7 шкаф-тайник, 8 за ним, 9 решётка, 10-11 сейфовые, 12, 14-17 второй этаж.</summary>
|
||||
public static readonly HashSet<int> DefaultOpenDoors = new HashSet<int> { 2, 4, 5, 6 };
|
||||
/// <summary>Как далеко от таблички искать её дверь (клеток по каждой оси; над дверью - 2).</summary>
|
||||
public const int DoorTagReach = 2;
|
||||
/// <summary>Одиночная книга на обеденном столе (cntBookPile09 в префабе).</summary>
|
||||
public static readonly Vector3i BookAt = new Vector3i(12, 2, 9);
|
||||
/// <summary>Место ножа на столе с реактивами (указание пользователя).</summary>
|
||||
@@ -317,6 +327,12 @@ namespace NecromancerTome
|
||||
_bv = bed;
|
||||
_d = MarchingCubes.DensityAir;
|
||||
}
|
||||
else if (!_bv.isair && _bv.Block.GetBlockName() == DoorTagBlock)
|
||||
{
|
||||
// Табличка номера двери - только для редактора.
|
||||
_bv = BlockValue.Air;
|
||||
_d = MarchingCubes.DensityAir;
|
||||
}
|
||||
else if (_bv.isair || !_bv.Block.shape.IsTerrain())
|
||||
{
|
||||
_d = MarchingCubes.DensityAir;
|
||||
@@ -412,36 +428,194 @@ namespace NecromancerTome
|
||||
return Mismatches(_world, _o) == 0;
|
||||
}
|
||||
|
||||
/// <summary>Межкомнатные двери чертогов (PassageDoor) - открыты (в редакторе сохранились закрытыми, а дух
|
||||
/// дверей не открывает), входная - закрыта. Прочие двери (шкаф-тайник, стальная - указание 09.10: «закрыты
|
||||
/// и откроются только по событию») не трогаем: их открывает событие. Состояние двери - в её TEFeatureDoor.</summary>
|
||||
/// <summary>Номера дверей из табличек префаба: локальная клетка таблички -> номер.</summary>
|
||||
static Dictionary<Vector3i, int> DoorTags(Prefab _p)
|
||||
{
|
||||
var tags = new Dictionary<Vector3i, int>();
|
||||
foreach (var kv in _p.tileEntities)
|
||||
{
|
||||
Vector3i lp = kv.Key;
|
||||
if (_p.GetBlock(lp.x, lp.y, lp.z).Block?.GetBlockName() != DoorTagBlock ||
|
||||
!(kv.Value is TileEntityComposite tec) || !tec.TryGetSelfOrFeature(out TEFeatureSignable sign))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string text = (sign.GetAuthoredText()?.Text ?? "").Trim();
|
||||
if (int.TryParse(text, out int n))
|
||||
{
|
||||
tags[lp] = n;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] MindPalace: табличка двери " + lp + " без номера: «" + text + "»");
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/// <summary>Двери с номерами: номер -> локальная клетка двери (ближайшая к табличке дверь в пределах DoorTagReach).</summary>
|
||||
public static Dictionary<int, Vector3i> NumberedDoors(World _world, Vector3i _o)
|
||||
{
|
||||
var doors = new Dictionary<int, Vector3i>();
|
||||
Prefab p = LoadPrefab();
|
||||
if (p == null)
|
||||
{
|
||||
return doors;
|
||||
}
|
||||
foreach (var kv in DoorTags(p))
|
||||
{
|
||||
Vector3i best = default;
|
||||
int bestD = int.MaxValue;
|
||||
for (int dy = -DoorTagReach; dy <= DoorTagReach; dy++)
|
||||
for (int dz = -DoorTagReach; dz <= DoorTagReach; dz++)
|
||||
for (int dx = -DoorTagReach; dx <= DoorTagReach; dx++)
|
||||
{
|
||||
Vector3i lp = kv.Key + new Vector3i(dx, dy, dz);
|
||||
int d = dx * dx + dy * dy + dz * dz;
|
||||
if (d < bestD && _world.GetTileEntity(_o + lp) is TileEntityComposite tec && tec.GetFeature<TEFeatureDoor>() != null)
|
||||
{
|
||||
best = lp;
|
||||
bestD = d;
|
||||
}
|
||||
}
|
||||
if (bestD == int.MaxValue)
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] MindPalace: у таблички " + kv.Key + " (дверь " + kv.Value + ") рядом нет двери");
|
||||
}
|
||||
else if (doors.ContainsKey(kv.Value))
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] MindPalace: номер двери " + kv.Value + " повторяется (" + doors[kv.Value] + " и " + best + ")");
|
||||
}
|
||||
else
|
||||
{
|
||||
doors[kv.Value] = best;
|
||||
}
|
||||
}
|
||||
return doors;
|
||||
}
|
||||
|
||||
/// <summary>Состояние дверей чертогов при каждом входе. Дверь с номером (табличка DoorTagBlock) открыта, только
|
||||
/// если событие её открыло (SetDoorOpen). Межкомнатные двери без номера (PassageDoor) открыты (в редакторе
|
||||
/// сохранились закрытыми, а дух дверей не открывает), входная - закрыта. Прочие двери без номера не трогаем.
|
||||
/// Состояние двери - в её TEFeatureDoor.</summary>
|
||||
public static void OpenDoors(World _world, Vector3i _o)
|
||||
{
|
||||
var numbered = new Dictionary<Vector3i, int>();
|
||||
foreach (var kv in NumberedDoors(_world, _o))
|
||||
{
|
||||
numbered[kv.Value] = kv.Key;
|
||||
}
|
||||
HashSet<int> opened = OpenedDoors(_world.GetPrimaryPlayer());
|
||||
Vector3i s = Size;
|
||||
int opened = 0;
|
||||
int switched = 0;
|
||||
for (int y = 0; y < s.y; y++)
|
||||
for (int z = 0; z < s.z; z++)
|
||||
for (int x = 0; x < s.x; x++)
|
||||
{
|
||||
if (_world.GetTileEntity(_o + new Vector3i(x, y, z)) is TileEntityComposite tec)
|
||||
Vector3i lp = new Vector3i(x, y, z);
|
||||
if (!(_world.GetTileEntity(_o + lp) is TileEntityComposite tec) || !(tec.GetFeature<TEFeatureDoor>() is TEFeatureDoor door))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool wantOpen;
|
||||
if (numbered.TryGetValue(lp, out int n))
|
||||
{
|
||||
// В файле: n - открыта событием, -n - закрыта событием (для открытых с начала).
|
||||
wantOpen = opened.Contains(n) || DefaultOpenDoors.Contains(n) && !opened.Contains(-n);
|
||||
}
|
||||
else if (_world.GetBlock(_o + lp).Block.GetBlockName() == PassageDoor)
|
||||
{
|
||||
TEFeatureDoor door = tec.GetFeature<TEFeatureDoor>();
|
||||
if (door == null || _world.GetBlock(_o + new Vector3i(x, y, z)).Block.GetBlockName() != PassageDoor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Входная - закрыть (могла остаться открытой от прежних входов), остальные - открыть.
|
||||
bool wantOpen = new Vector3i(x, y, z) != EntranceDoorAt;
|
||||
if (door != null && door.IsOpen() != wantOpen)
|
||||
{
|
||||
door.SetOpen(wantOpen);
|
||||
opened++;
|
||||
}
|
||||
wantOpen = lp != EntranceDoorAt;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (door.IsOpen() != wantOpen)
|
||||
{
|
||||
door.SetOpen(wantOpen);
|
||||
switched++;
|
||||
}
|
||||
}
|
||||
if (opened > 0)
|
||||
if (switched > 0)
|
||||
{
|
||||
Debug.Log("[NecromancerTome] MindPalace: дверей переключено " + opened);
|
||||
Debug.Log("[NecromancerTome] MindPalace: дверей переключено " + switched + ", с номерами " + numbered.Count + ", открыты по событиям: " + string.Join(",", opened));
|
||||
}
|
||||
}
|
||||
|
||||
// Открытые событиями двери - по игрокам, в файле в папке сохранения мира: «игрок номер номер ...».
|
||||
static string DoorsFile => Path.Combine(GameIO.GetSaveGameDir(), "NecromancerTome_palaceDoors.txt");
|
||||
static readonly Dictionary<string, HashSet<int>> _openedDoors = new Dictionary<string, HashSet<int>>();
|
||||
static string _openedFor;
|
||||
|
||||
static HashSet<int> OpenedDoors(EntityPlayer _player)
|
||||
{
|
||||
string dir = GameIO.GetSaveGameDir();
|
||||
if (_openedFor != dir)
|
||||
{
|
||||
_openedFor = dir;
|
||||
_openedDoors.Clear();
|
||||
try
|
||||
{
|
||||
if (File.Exists(DoorsFile))
|
||||
{
|
||||
foreach (string line in File.ReadAllLines(DoorsFile))
|
||||
{
|
||||
string[] parts = line.Split(' ');
|
||||
var set = new HashSet<int>();
|
||||
for (int i = 1; i < parts.Length; i++)
|
||||
{
|
||||
if (int.TryParse(parts[i], out int n))
|
||||
{
|
||||
set.Add(n);
|
||||
}
|
||||
}
|
||||
_openedDoors[parts[0]] = set;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] MindPalace: не прочитать двери - " + ex.Message);
|
||||
}
|
||||
}
|
||||
string key = _player != null ? PlayerKey(_player) : "local";
|
||||
if (!_openedDoors.TryGetValue(key, out HashSet<int> opened))
|
||||
{
|
||||
opened = new HashSet<int>();
|
||||
_openedDoors[key] = opened;
|
||||
}
|
||||
return opened;
|
||||
}
|
||||
|
||||
/// <summary>Открыть/закрыть дверь чертогов с номером (для событий). Запоминается в сейве; если игрок сейчас
|
||||
/// в чертогах - дверь переключается сразу.</summary>
|
||||
public static void SetDoorOpen(EntityPlayer _player, int _number, bool _open)
|
||||
{
|
||||
HashSet<int> opened = OpenedDoors(_player);
|
||||
bool changed = _open ? opened.Remove(-_number) | opened.Add(_number) : opened.Remove(_number) | DefaultOpenDoors.Contains(_number) && opened.Add(-_number);
|
||||
if (!changed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
var lines = new List<string>();
|
||||
foreach (var kv in _openedDoors)
|
||||
{
|
||||
lines.Add(kv.Key + (kv.Value.Count > 0 ? " " + string.Join(" ", kv.Value) : ""));
|
||||
}
|
||||
File.WriteAllLines(DoorsFile, lines);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[NecromancerTome] MindPalace: не записать двери - " + ex.Message);
|
||||
}
|
||||
World world = GameManager.Instance.World;
|
||||
if (_stage == Stage.Ready && world != null && _player == world.GetPrimaryPlayer())
|
||||
{
|
||||
OpenDoors(world, _origin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,7 +1074,7 @@ namespace NecromancerTome
|
||||
public class ConsoleCmdNecroPalace : ConsoleCmdAbstract
|
||||
{
|
||||
public override string[] getCommands() => new[] { "necropalace" };
|
||||
public override string getDescription() => "NecromancerTome: чертоги разума - where | scan | rebuild | test | knife N";
|
||||
public override string getDescription() => "NecromancerTome: чертоги разума - where | scan | rebuild | test | knife N | door [N open|close]";
|
||||
|
||||
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
|
||||
{
|
||||
@@ -936,6 +1110,20 @@ namespace NecromancerTome
|
||||
GameManager.Instance.StartCoroutine(MindPalace.StressTest(world, o));
|
||||
SdtdConsole.Instance.Output("проверка: заливка гравием, через 2 с - постановка (итог - в логе)");
|
||||
}
|
||||
else if (cmd == "door" && _params.Count > 2 && int.TryParse(_params[1], out int dn))
|
||||
{
|
||||
MindPalace.SetDoorOpen(player, dn, _params[2] == "open");
|
||||
SdtdConsole.Instance.Output("дверь " + dn + ": " + (_params[2] == "open" ? "открыта" : "закрыта"));
|
||||
}
|
||||
else if (cmd == "door")
|
||||
{
|
||||
var doors = MindPalace.NumberedDoors(world, o);
|
||||
SdtdConsole.Instance.Output("дверей с номерами: " + doors.Count);
|
||||
foreach (var kv in doors)
|
||||
{
|
||||
SdtdConsole.Instance.Output(" " + kv.Key + " - " + world.GetBlock(o + kv.Value).Block.GetBlockName() + " " + kv.Value);
|
||||
}
|
||||
}
|
||||
else if (cmd == "rebuild")
|
||||
{
|
||||
MindPalace.Stamp(world, o);
|
||||
|
||||
Reference in New Issue
Block a user