mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2025-05-18 19:31:17 +03:00
добавил tma в просмотр и отрефакторил код
This commit is contained in:
parent
b336f44b72
commit
e16b219854
@ -7,511 +7,3 @@ using System.Text;
|
|||||||
// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Tutorial.01\\data.tma";
|
// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Tutorial.01\\data.tma";
|
||||||
var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\CAMPAIGN\\CAMPAIGN.01\\Mission.02\\data.tma";
|
var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\CAMPAIGN\\CAMPAIGN.01\\Mission.02\\data.tma";
|
||||||
// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Single.01\\data.tma";
|
// var missionFilePath = "C:\\Program Files (x86)\\Nikita\\Iron Strategy\\MISSIONS\\Single.01\\data.tma";
|
||||||
var fs = new FileStream(missionFilePath, FileMode.Open);
|
|
||||||
|
|
||||||
var arealData = LoadAreals(fs);
|
|
||||||
|
|
||||||
var clansData = LoadClans(fs);
|
|
||||||
|
|
||||||
var gameObjectsData = LoadGameObjects(fs);
|
|
||||||
|
|
||||||
_ = 5;
|
|
||||||
|
|
||||||
ArealsFileData LoadAreals(FileStream fileStream)
|
|
||||||
{
|
|
||||||
var unusedHeader = fileStream.ReadInt32LittleEndian();
|
|
||||||
var arealCount = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
// В демо миссии нет ареалов, ровно как и в первой миссии кампании
|
|
||||||
// Span<byte> arealBuffer = stackalloc byte[12];
|
|
||||||
|
|
||||||
List<ArealInfo> infos = [];
|
|
||||||
for (var i = 0; i < arealCount; i++)
|
|
||||||
{
|
|
||||||
// игра читает 4 байта - видимо количество
|
|
||||||
var unknown4Bytes = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
var count = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
List<Vector3> vectors = [];
|
|
||||||
if (0 < count)
|
|
||||||
{
|
|
||||||
for (var i1 = 0; i1 < count; i1++)
|
|
||||||
{
|
|
||||||
// потом читает 12 байт разом (тут видимо какой-то вектор)
|
|
||||||
var unknownFloat1 = fileStream.ReadFloatLittleEndian();
|
|
||||||
var unknownFloat2 = fileStream.ReadFloatLittleEndian();
|
|
||||||
var unknownFloat3 = fileStream.ReadFloatLittleEndian();
|
|
||||||
|
|
||||||
vectors.Add(new Vector3(unknownFloat1, unknownFloat2, unknownFloat3));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infos.Add(new ArealInfo(unknown4Bytes, count, vectors));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArealsFileData(unusedHeader, arealCount, infos);
|
|
||||||
}
|
|
||||||
|
|
||||||
ClansFileData? LoadClans(FileStream fileStream)
|
|
||||||
{
|
|
||||||
var clanFeatureSet = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
if (clanFeatureSet is <= 0 or >= 7) return null;
|
|
||||||
|
|
||||||
var treeInfoCount = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
List<ClanInfo> infos = [];
|
|
||||||
for (var i = 0; i < treeInfoCount; i++)
|
|
||||||
{
|
|
||||||
var clanTreeInfo = new ClanInfo();
|
|
||||||
|
|
||||||
clanTreeInfo.ClanName = fileStream.ReadLengthPrefixedString();
|
|
||||||
clanTreeInfo.UnkInt1 = fileStream.ReadInt32LittleEndian();
|
|
||||||
clanTreeInfo.X = fileStream.ReadFloatLittleEndian();
|
|
||||||
clanTreeInfo.Y = fileStream.ReadFloatLittleEndian();
|
|
||||||
clanTreeInfo.ClanType = (ClanType)fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
if (1 < clanFeatureSet)
|
|
||||||
{
|
|
||||||
// MISSIONS\SCRIPTS\default
|
|
||||||
// MISSIONS\SCRIPTS\tut1_pl
|
|
||||||
// MISSIONS\SCRIPTS\tut1_en
|
|
||||||
clanTreeInfo.UnkString2 = fileStream.ReadLengthPrefixedString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (2 < clanFeatureSet)
|
|
||||||
{
|
|
||||||
clanTreeInfo.UnknownClanPartCount = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
// тут игра читает число, затем 12 байт и ещё 2 числа
|
|
||||||
|
|
||||||
List<UnknownClanTreeInfoPart> unknownClanTreeInfoParts = [];
|
|
||||||
for (var i1 = 0; i1 < clanTreeInfo.UnknownClanPartCount; i1++)
|
|
||||||
{
|
|
||||||
unknownClanTreeInfoParts.Add(
|
|
||||||
new UnknownClanTreeInfoPart(
|
|
||||||
fileStream.ReadInt32LittleEndian(),
|
|
||||||
new Vector3(
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian()
|
|
||||||
),
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
clanTreeInfo.UnknownParts = unknownClanTreeInfoParts;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (3 < clanFeatureSet)
|
|
||||||
{
|
|
||||||
// MISSIONS\SCRIPTS\auto.trf
|
|
||||||
// MISSIONS\SCRIPTS\data.trf
|
|
||||||
// указатель на NRes файл с данными
|
|
||||||
// может быть пустым, например у Ntrl в туториале
|
|
||||||
clanTreeInfo.ResearchNResPath = fileStream.ReadLengthPrefixedString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (4 < clanFeatureSet)
|
|
||||||
{
|
|
||||||
clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (5 < clanFeatureSet)
|
|
||||||
{
|
|
||||||
clanTreeInfo.AlliesMapCount = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
// тут какая-то мапа
|
|
||||||
// в демо миссии тут
|
|
||||||
// player -> 1
|
|
||||||
// player2 -> 0
|
|
||||||
|
|
||||||
// в туториале
|
|
||||||
// Plr -> 1
|
|
||||||
// Trgt -> 1
|
|
||||||
// Enm -> 0
|
|
||||||
// Ntrl -> 1
|
|
||||||
Dictionary<string, int> map = [];
|
|
||||||
for (var i1 = 0; i1 < clanTreeInfo.AlliesMapCount; i1++)
|
|
||||||
{
|
|
||||||
var keyIdString = fileStream.ReadLengthPrefixedString();
|
|
||||||
// это число всегда либо 0 либо 1
|
|
||||||
var unkNumber = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
map[keyIdString] = unkNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
clanTreeInfo.AlliesMap = map;
|
|
||||||
}
|
|
||||||
|
|
||||||
infos.Add(clanTreeInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
var clanInfo = new ClansFileData(clanFeatureSet, treeInfoCount, infos);
|
|
||||||
|
|
||||||
return clanInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
GameObjectsFileData LoadGameObjects(FileStream fileStream)
|
|
||||||
{
|
|
||||||
var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
var gameObjectsCount = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
Span<byte> settingVal1 = stackalloc byte[4];
|
|
||||||
Span<byte> settingVal2 = stackalloc byte[4];
|
|
||||||
Span<byte> settingVal3 = stackalloc byte[4];
|
|
||||||
|
|
||||||
List<GameObjectInfo> gameObjectInfos = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < gameObjectsCount; i++)
|
|
||||||
{
|
|
||||||
var gameObjectInfo = new GameObjectInfo();
|
|
||||||
// ReadGameObjectData
|
|
||||||
gameObjectInfo.Type = (GameObjectType)fileStream.ReadInt32LittleEndian();
|
|
||||||
gameObjectInfo.UnknownFlags = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
// UNITS\UNITS\HERO\hero_t.dat
|
|
||||||
gameObjectInfo.DatString = fileStream.ReadLengthPrefixedString();
|
|
||||||
|
|
||||||
if (2 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
gameObjectInfo.OwningClanIndex = fileStream.ReadInt32LittleEndian();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (3 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
gameObjectInfo.UnknownInt3 = fileStream.ReadInt32LittleEndian();
|
|
||||||
}
|
|
||||||
|
|
||||||
// читает 12 байт
|
|
||||||
gameObjectInfo.Position = new Vector3(
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian()
|
|
||||||
);
|
|
||||||
|
|
||||||
// ещё раз читает 12 байт
|
|
||||||
gameObjectInfo.Rotation = new Vector3(
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (gameObjectsFeatureSet < 10)
|
|
||||||
{
|
|
||||||
// если фичесет меньше 10, то игра забивает вектор единицами
|
|
||||||
gameObjectInfo.Scale = new Vector3(1, 1, 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// в противном случае читает ещё вектор из файла
|
|
||||||
gameObjectInfo.Scale = new Vector3(
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (6 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
// у HERO пустая строка
|
|
||||||
gameObjectInfo.UnknownString2 = fileStream.ReadLengthPrefixedString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (7 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
gameObjectInfo.UnknownInt4 = fileStream.ReadInt32LittleEndian();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (8 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
gameObjectInfo.UnknownInt5 = fileStream.ReadInt32LittleEndian();
|
|
||||||
gameObjectInfo.UnknownInt6 = fileStream.ReadInt32LittleEndian();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (5 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
// тут игра вызывает ещё одну функцию чтения файла - видимо это настройки объекта
|
|
||||||
|
|
||||||
var unused = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
var innerCount = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
List<GameObjectSetting> settings = [];
|
|
||||||
for (var i1 = 0; i1 < innerCount; i1++)
|
|
||||||
{
|
|
||||||
// судя по всему это тип настройки
|
|
||||||
// 0 - float, 1 - int, 2?
|
|
||||||
var settingType = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
settingVal1.Clear();
|
|
||||||
settingVal2.Clear();
|
|
||||||
settingVal3.Clear();
|
|
||||||
fileStream.ReadExactly(settingVal1);
|
|
||||||
fileStream.ReadExactly(settingVal2);
|
|
||||||
fileStream.ReadExactly(settingVal3);
|
|
||||||
|
|
||||||
IntFloatValue val1;
|
|
||||||
IntFloatValue val2;
|
|
||||||
IntFloatValue val3;
|
|
||||||
|
|
||||||
if (settingType == 0)
|
|
||||||
{
|
|
||||||
// float
|
|
||||||
val1 = new IntFloatValue(settingVal1);
|
|
||||||
val2 = new IntFloatValue(settingVal2);
|
|
||||||
val3 = new IntFloatValue(settingVal3);
|
|
||||||
// var innerFloat1 = fileStream.ReadFloatLittleEndian();
|
|
||||||
// var innerFloat2 = fileStream.ReadFloatLittleEndian();
|
|
||||||
// судя по всему это значение настройки
|
|
||||||
// var innerFloat3 = fileStream.ReadFloatLittleEndian();
|
|
||||||
}
|
|
||||||
else if (settingType == 1)
|
|
||||||
{
|
|
||||||
val1 = new IntFloatValue(settingVal1);
|
|
||||||
val2 = new IntFloatValue(settingVal2);
|
|
||||||
val3 = new IntFloatValue(settingVal3);
|
|
||||||
// var innerInt1 = fileStream.ReadInt32LittleEndian();
|
|
||||||
// var innerInt2 = fileStream.ReadInt32LittleEndian();
|
|
||||||
// судя по всему это значение настройки
|
|
||||||
// var innerInt3 = fileStream.ReadInt32LittleEndian();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Settings value type is not float or int");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invulnerability
|
|
||||||
// Life state
|
|
||||||
// LogicalID
|
|
||||||
// ClanID
|
|
||||||
// Type
|
|
||||||
// MaxSpeedPercent
|
|
||||||
// MaximumOre
|
|
||||||
// CurrentOre
|
|
||||||
var name = fileStream.ReadLengthPrefixedString();
|
|
||||||
|
|
||||||
settings.Add(new GameObjectSetting(settingType, val1, val2, val3, name));
|
|
||||||
}
|
|
||||||
|
|
||||||
gameObjectInfo.Settings = new GameObjectSettings(unused, innerCount, settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
gameObjectInfos.Add(gameObjectInfo);
|
|
||||||
|
|
||||||
// end ReadGameObjectData
|
|
||||||
}
|
|
||||||
|
|
||||||
// DATA\MAPS\KM_2\land
|
|
||||||
// DATA\MAPS\SC_3\land
|
|
||||||
var landString = fileStream.ReadLengthPrefixedString();
|
|
||||||
|
|
||||||
int unkInt7 = 0;
|
|
||||||
string? missionTechDescription = null;
|
|
||||||
if (1 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
unkInt7 = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
// ? - байт cd
|
|
||||||
|
|
||||||
// Mission??????????trm\Is.\Ir
|
|
||||||
// Skirmish 1. Full Base, One opponent?????
|
|
||||||
// New mission?????????????????M
|
|
||||||
missionTechDescription = fileStream.ReadLengthPrefixedString();
|
|
||||||
}
|
|
||||||
|
|
||||||
LodeData? lodeData = null;
|
|
||||||
if (4 < gameObjectsFeatureSet)
|
|
||||||
{
|
|
||||||
var unused = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
var lodeCount = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
List<LodeInfo> lodeInfos = [];
|
|
||||||
for (var i1 = 0; i1 < lodeCount; i1++)
|
|
||||||
{
|
|
||||||
var unkLodeVector = new Vector3(
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian(),
|
|
||||||
fileStream.ReadFloatLittleEndian()
|
|
||||||
);
|
|
||||||
|
|
||||||
var unkLodeInt1 = fileStream.ReadInt32LittleEndian();
|
|
||||||
var unkLodeFlags2 = fileStream.ReadInt32LittleEndian();
|
|
||||||
var unkLodeFloat3 = fileStream.ReadFloatLittleEndian();
|
|
||||||
var unkLodeInt4 = fileStream.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
lodeInfos.Add(
|
|
||||||
new LodeInfo(
|
|
||||||
unkLodeVector,
|
|
||||||
unkLodeInt1,
|
|
||||||
unkLodeFlags2,
|
|
||||||
unkLodeFloat3,
|
|
||||||
unkLodeInt4
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
lodeData = new LodeData(unused, lodeCount, lodeInfos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GameObjectsFileData(
|
|
||||||
gameObjectsFeatureSet,
|
|
||||||
gameObjectsCount,
|
|
||||||
gameObjectInfos,
|
|
||||||
landString,
|
|
||||||
unkInt7,
|
|
||||||
missionTechDescription,
|
|
||||||
lodeData
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Extensions
|
|
||||||
{
|
|
||||||
public static int ReadInt32LittleEndian(this FileStream fs)
|
|
||||||
{
|
|
||||||
Span<byte> buf = stackalloc byte[4];
|
|
||||||
fs.ReadExactly(buf);
|
|
||||||
|
|
||||||
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float ReadFloatLittleEndian(this FileStream fs)
|
|
||||||
{
|
|
||||||
Span<byte> buf = stackalloc byte[4];
|
|
||||||
fs.ReadExactly(buf);
|
|
||||||
|
|
||||||
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ReadLengthPrefixedString(this FileStream fs)
|
|
||||||
{
|
|
||||||
var len = fs.ReadInt32LittleEndian();
|
|
||||||
|
|
||||||
if (len == 0)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer = new byte[len];
|
|
||||||
|
|
||||||
fs.ReadExactly(buffer, 0, len);
|
|
||||||
|
|
||||||
return Encoding.ASCII.GetString(buffer, 0, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ArealsFileData(int UnusedHeader, int ArealCount, List<ArealInfo> ArealInfos);
|
|
||||||
|
|
||||||
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
|
||||||
|
|
||||||
public record Vector3(float X, float Y, float Z);
|
|
||||||
|
|
||||||
// ----
|
|
||||||
|
|
||||||
public record ClansFileData(int ClanFeatureSet, int ClanCount, List<ClanInfo> ClanInfos);
|
|
||||||
|
|
||||||
public class ClanInfo
|
|
||||||
{
|
|
||||||
public string ClanName { get; set; }
|
|
||||||
public int UnkInt1 { get; set; }
|
|
||||||
public float X { get; set; }
|
|
||||||
public float Y { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 1 - игрок, 2 AI, 3 - нейтральный
|
|
||||||
/// </summary>
|
|
||||||
public ClanType ClanType { get; set; }
|
|
||||||
|
|
||||||
public string UnkString2 { get; set; }
|
|
||||||
public int UnknownClanPartCount { get; set; }
|
|
||||||
public List<UnknownClanTreeInfoPart> UnknownParts { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Игра называет этот путь TreeName
|
|
||||||
/// </summary>
|
|
||||||
public string ResearchNResPath { get; set; }
|
|
||||||
public int UnkInt3 { get; set; }
|
|
||||||
public int AlliesMapCount { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// мапа союзников (ключ - имя клана, значение - число, всегда либо 0 либо 1)
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<string, int> AlliesMap { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
|
||||||
|
|
||||||
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
|
||||||
public class IntFloatValue(Span<byte> span)
|
|
||||||
{
|
|
||||||
public int AsInt { get; set; } = MemoryMarshal.Read<int>(span);
|
|
||||||
public float AsFloat { get; set; } = MemoryMarshal.Read<float>(span);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record GameObjectsFileData(int GameObjectsFeatureSet, int GameObjectsCount, List<GameObjectInfo> GameObjectInfos, string LandString, int UnknownInt, string? MissionTechDescription, LodeData? LodeData);
|
|
||||||
|
|
||||||
public class GameObjectInfo
|
|
||||||
{
|
|
||||||
// 0 - здание, 1 - бот, 2 - окружение
|
|
||||||
public GameObjectType Type { get; set; }
|
|
||||||
|
|
||||||
public int UnknownFlags { get; set; }
|
|
||||||
|
|
||||||
public string DatString { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Индекс клана, которому принадлежит объект
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// Некоторые объекты окружения иногда почему-то принадлежат клану отличному от -1
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// Может быть -1, если объект никому не принадлежит, я такое встречал только у объектов окружения
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
public int OwningClanIndex { get; set; }
|
|
||||||
|
|
||||||
public int UnknownInt3 { get; set; }
|
|
||||||
|
|
||||||
public Vector3 Position { get; set; }
|
|
||||||
public Vector3 Rotation { get; set; }
|
|
||||||
public Vector3 Scale { get; set; }
|
|
||||||
|
|
||||||
public string UnknownString2 { get; set; }
|
|
||||||
|
|
||||||
public int UnknownInt4 { get; set; }
|
|
||||||
public int UnknownInt5 { get; set; }
|
|
||||||
public int UnknownInt6 { get; set; }
|
|
||||||
|
|
||||||
public GameObjectSettings Settings { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public record GameObjectSettings(int Unused, int SettingsCount, List<GameObjectSetting> Settings);
|
|
||||||
|
|
||||||
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
|
|
||||||
|
|
||||||
public record LodeData(int Unused, int LodeCount, List<LodeInfo> Lodes);
|
|
||||||
|
|
||||||
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownInt2, float UnknownFloat, int UnknownInt3);
|
|
||||||
|
|
||||||
public enum GameObjectType
|
|
||||||
{
|
|
||||||
Building = 0,
|
|
||||||
Warbot = 1,
|
|
||||||
Tree = 2,
|
|
||||||
Stone = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ClanType
|
|
||||||
{
|
|
||||||
Environment = 0,
|
|
||||||
Player = 1,
|
|
||||||
AI = 2,
|
|
||||||
Neutral = 3
|
|
||||||
}
|
|
3
MissionTmaLib/ArealInfo.cs
Normal file
3
MissionTmaLib/ArealInfo.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record ArealInfo(int Index, int CoordsCount, List<Vector3> Coords);
|
4
MissionTmaLib/ArealsFileData.cs
Normal file
4
MissionTmaLib/ArealsFileData.cs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
|
||||||
|
public record ArealsFileData(int UnusedHeader, int ArealCount, List<ArealInfo> ArealInfos);
|
30
MissionTmaLib/ClanInfo.cs
Normal file
30
MissionTmaLib/ClanInfo.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public class ClanInfo
|
||||||
|
{
|
||||||
|
public string ClanName { get; set; }
|
||||||
|
public int UnkInt1 { get; set; }
|
||||||
|
public float X { get; set; }
|
||||||
|
public float Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1 - игрок, 2 AI, 3 - нейтральный
|
||||||
|
/// </summary>
|
||||||
|
public ClanType ClanType { get; set; }
|
||||||
|
|
||||||
|
public string UnkString2 { get; set; }
|
||||||
|
public int UnknownClanPartCount { get; set; }
|
||||||
|
public List<UnknownClanTreeInfoPart> UnknownParts { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Игра называет этот путь TreeName
|
||||||
|
/// </summary>
|
||||||
|
public string ResearchNResPath { get; set; }
|
||||||
|
public int UnkInt3 { get; set; }
|
||||||
|
public int AlliesMapCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// мапа союзников (ключ - имя клана, значение - число, всегда либо 0 либо 1)
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, int> AlliesMap { get; set; }
|
||||||
|
}
|
24
MissionTmaLib/ClanType.cs
Normal file
24
MissionTmaLib/ClanType.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public enum ClanType
|
||||||
|
{
|
||||||
|
Environment = 0,
|
||||||
|
Player = 1,
|
||||||
|
AI = 2,
|
||||||
|
Neutral = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static string ToReadableString(this ClanType clanType)
|
||||||
|
{
|
||||||
|
return clanType switch
|
||||||
|
{
|
||||||
|
ClanType.Environment => $"Окружение ({clanType:D})",
|
||||||
|
ClanType.Player => $"Игрок ({clanType:D})",
|
||||||
|
ClanType.AI => $"AI ({clanType:D})",
|
||||||
|
ClanType.Neutral => $"Нейтральный ({clanType:D})",
|
||||||
|
_ => $"Неизвестный ({clanType:D})"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
3
MissionTmaLib/ClansFileData.cs
Normal file
3
MissionTmaLib/ClansFileData.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record ClansFileData(int ClanFeatureSet, int ClanCount, List<ClanInfo> ClanInfos);
|
38
MissionTmaLib/GameObjectInfo.cs
Normal file
38
MissionTmaLib/GameObjectInfo.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public class GameObjectInfo
|
||||||
|
{
|
||||||
|
// 0 - здание, 1 - бот, 2 - окружение
|
||||||
|
public GameObjectType Type { get; set; }
|
||||||
|
|
||||||
|
public int UnknownFlags { get; set; }
|
||||||
|
|
||||||
|
public string DatString { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Индекс клана, которому принадлежит объект
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// Некоторые объекты окружения иногда почему-то принадлежат клану отличному от -1
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Может быть -1, если объект никому не принадлежит, я такое встречал только у объектов окружения
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public int OwningClanIndex { get; set; }
|
||||||
|
|
||||||
|
public int UnknownInt3 { get; set; }
|
||||||
|
|
||||||
|
public Vector3 Position { get; set; }
|
||||||
|
public Vector3 Rotation { get; set; }
|
||||||
|
public Vector3 Scale { get; set; }
|
||||||
|
|
||||||
|
public string UnknownString2 { get; set; }
|
||||||
|
|
||||||
|
public int UnknownInt4 { get; set; }
|
||||||
|
public int UnknownInt5 { get; set; }
|
||||||
|
public int UnknownInt6 { get; set; }
|
||||||
|
|
||||||
|
public GameObjectSettings Settings { get; set; }
|
||||||
|
}
|
3
MissionTmaLib/GameObjectSetting.cs
Normal file
3
MissionTmaLib/GameObjectSetting.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record GameObjectSetting(int SettingType, IntFloatValue Unk1, IntFloatValue Unk2, IntFloatValue Unk3, string Name);
|
3
MissionTmaLib/GameObjectSettings.cs
Normal file
3
MissionTmaLib/GameObjectSettings.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record GameObjectSettings(int Unused, int SettingsCount, List<GameObjectSetting> Settings);
|
9
MissionTmaLib/GameObjectType.cs
Normal file
9
MissionTmaLib/GameObjectType.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public enum GameObjectType
|
||||||
|
{
|
||||||
|
Building = 0,
|
||||||
|
Warbot = 1,
|
||||||
|
Tree = 2,
|
||||||
|
Stone = 3
|
||||||
|
}
|
3
MissionTmaLib/GameObjectsFileData.cs
Normal file
3
MissionTmaLib/GameObjectsFileData.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record GameObjectsFileData(int GameObjectsFeatureSet, int GameObjectsCount, List<GameObjectInfo> GameObjectInfos, string LandString, int UnknownInt, string? MissionTechDescription, LodeData? LodeData);
|
11
MissionTmaLib/IntFloatValue.cs
Normal file
11
MissionTmaLib/IntFloatValue.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
[DebuggerDisplay("AsInt = {AsInt}, AsFloat = {AsFloat}")]
|
||||||
|
public class IntFloatValue(Span<byte> span)
|
||||||
|
{
|
||||||
|
public int AsInt { get; set; } = MemoryMarshal.Read<int>(span);
|
||||||
|
public float AsFloat { get; set; } = MemoryMarshal.Read<float>(span);
|
||||||
|
}
|
3
MissionTmaLib/LodeData.cs
Normal file
3
MissionTmaLib/LodeData.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record LodeData(int Unused, int LodeCount, List<LodeInfo> Lodes);
|
3
MissionTmaLib/LodeInfo.cs
Normal file
3
MissionTmaLib/LodeInfo.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record LodeInfo(Vector3 UnknownVector, int UnknownInt1, int UnknownInt2, float UnknownFloat, int UnknownInt3);
|
9
MissionTmaLib/MissionTmaLib.csproj
Normal file
9
MissionTmaLib/MissionTmaLib.csproj
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
39
MissionTmaLib/Parsing/Extensions.cs
Normal file
39
MissionTmaLib/Parsing/Extensions.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace MissionTmaLib.Parsing;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static int ReadInt32LittleEndian(this FileStream fs)
|
||||||
|
{
|
||||||
|
Span<byte> buf = stackalloc byte[4];
|
||||||
|
fs.ReadExactly(buf);
|
||||||
|
|
||||||
|
return BinaryPrimitives.ReadInt32LittleEndian(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float ReadFloatLittleEndian(this FileStream fs)
|
||||||
|
{
|
||||||
|
Span<byte> buf = stackalloc byte[4];
|
||||||
|
fs.ReadExactly(buf);
|
||||||
|
|
||||||
|
return BinaryPrimitives.ReadSingleLittleEndian(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadLengthPrefixedString(this FileStream fs)
|
||||||
|
{
|
||||||
|
var len = fs.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer = new byte[len];
|
||||||
|
|
||||||
|
fs.ReadExactly(buffer, 0, len);
|
||||||
|
|
||||||
|
return Encoding.ASCII.GetString(buffer, 0, len);
|
||||||
|
}
|
||||||
|
}
|
3
MissionTmaLib/Parsing/MissionTmaParseResult.cs
Normal file
3
MissionTmaLib/Parsing/MissionTmaParseResult.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib.Parsing;
|
||||||
|
|
||||||
|
public record MissionTmaParseResult(MissionTma? Mission, string? Error);
|
383
MissionTmaLib/Parsing/MissionTmaParser.cs
Normal file
383
MissionTmaLib/Parsing/MissionTmaParser.cs
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
namespace MissionTmaLib.Parsing;
|
||||||
|
|
||||||
|
public class MissionTmaParser
|
||||||
|
{
|
||||||
|
public static MissionTmaParseResult ReadFile(string filePath)
|
||||||
|
{
|
||||||
|
var fs = new FileStream(filePath, FileMode.Open);
|
||||||
|
|
||||||
|
var arealData = LoadAreals(fs);
|
||||||
|
|
||||||
|
var clansData = LoadClans(fs);
|
||||||
|
|
||||||
|
if (clansData is null) return new MissionTmaParseResult(null, "Не обнаружена информация о кланах");
|
||||||
|
|
||||||
|
var gameObjectsData = LoadGameObjects(fs);
|
||||||
|
|
||||||
|
var missionDat = new MissionTma(arealData, clansData, gameObjectsData);
|
||||||
|
return new MissionTmaParseResult(missionDat, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ArealsFileData LoadAreals(FileStream fileStream)
|
||||||
|
{
|
||||||
|
var unusedHeader = fileStream.ReadInt32LittleEndian();
|
||||||
|
var arealCount = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
// В демо миссии нет ареалов, ровно как и в первой миссии кампании
|
||||||
|
// Span<byte> arealBuffer = stackalloc byte[12];
|
||||||
|
|
||||||
|
List<ArealInfo> infos = [];
|
||||||
|
for (var i = 0; i < arealCount; i++)
|
||||||
|
{
|
||||||
|
// игра читает 4 байта - видимо количество
|
||||||
|
var unknown4Bytes = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
var count = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
List<Vector3> vectors = [];
|
||||||
|
if (0 < count)
|
||||||
|
{
|
||||||
|
for (var i1 = 0; i1 < count; i1++)
|
||||||
|
{
|
||||||
|
// потом читает 12 байт разом (тут видимо какой-то вектор)
|
||||||
|
var unknownFloat1 = fileStream.ReadFloatLittleEndian();
|
||||||
|
var unknownFloat2 = fileStream.ReadFloatLittleEndian();
|
||||||
|
var unknownFloat3 = fileStream.ReadFloatLittleEndian();
|
||||||
|
|
||||||
|
vectors.Add(new Vector3(unknownFloat1, unknownFloat2, unknownFloat3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
infos.Add(new ArealInfo(unknown4Bytes, count, vectors));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArealsFileData(unusedHeader, arealCount, infos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ClansFileData? LoadClans(FileStream fileStream)
|
||||||
|
{
|
||||||
|
var clanFeatureSet = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
if (clanFeatureSet is <= 0 or >= 7) return null;
|
||||||
|
|
||||||
|
var clanCount = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
List<ClanInfo> infos = [];
|
||||||
|
for (var i = 0; i < clanCount; i++)
|
||||||
|
{
|
||||||
|
var clanTreeInfo = new ClanInfo();
|
||||||
|
|
||||||
|
clanTreeInfo.ClanName = fileStream.ReadLengthPrefixedString();
|
||||||
|
clanTreeInfo.UnkInt1 = fileStream.ReadInt32LittleEndian();
|
||||||
|
clanTreeInfo.X = fileStream.ReadFloatLittleEndian();
|
||||||
|
clanTreeInfo.Y = fileStream.ReadFloatLittleEndian();
|
||||||
|
clanTreeInfo.ClanType = (ClanType) fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
if (1 < clanFeatureSet)
|
||||||
|
{
|
||||||
|
// MISSIONS\SCRIPTS\default
|
||||||
|
// MISSIONS\SCRIPTS\tut1_pl
|
||||||
|
// MISSIONS\SCRIPTS\tut1_en
|
||||||
|
clanTreeInfo.UnkString2 = fileStream.ReadLengthPrefixedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (2 < clanFeatureSet)
|
||||||
|
{
|
||||||
|
clanTreeInfo.UnknownClanPartCount = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
// тут игра читает число, затем 12 байт и ещё 2 числа
|
||||||
|
|
||||||
|
List<UnknownClanTreeInfoPart> unknownClanTreeInfoParts = [];
|
||||||
|
for (var i1 = 0; i1 < clanTreeInfo.UnknownClanPartCount; i1++)
|
||||||
|
{
|
||||||
|
unknownClanTreeInfoParts.Add(
|
||||||
|
new UnknownClanTreeInfoPart(
|
||||||
|
fileStream.ReadInt32LittleEndian(),
|
||||||
|
new Vector3(
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian()
|
||||||
|
),
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
clanTreeInfo.UnknownParts = unknownClanTreeInfoParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (3 < clanFeatureSet)
|
||||||
|
{
|
||||||
|
// MISSIONS\SCRIPTS\auto.trf
|
||||||
|
// MISSIONS\SCRIPTS\data.trf
|
||||||
|
// указатель на NRes файл с данными
|
||||||
|
// может быть пустым, например у Ntrl в туториале
|
||||||
|
clanTreeInfo.ResearchNResPath = fileStream.ReadLengthPrefixedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (4 < clanFeatureSet)
|
||||||
|
{
|
||||||
|
clanTreeInfo.UnkInt3 = fileStream.ReadInt32LittleEndian();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (5 < clanFeatureSet)
|
||||||
|
{
|
||||||
|
clanTreeInfo.AlliesMapCount = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
// тут какая-то мапа
|
||||||
|
// в демо миссии тут
|
||||||
|
// player -> 1
|
||||||
|
// player2 -> 0
|
||||||
|
|
||||||
|
// в туториале
|
||||||
|
// Plr -> 1
|
||||||
|
// Trgt -> 1
|
||||||
|
// Enm -> 0
|
||||||
|
// Ntrl -> 1
|
||||||
|
Dictionary<string, int> map = [];
|
||||||
|
for (var i1 = 0; i1 < clanTreeInfo.AlliesMapCount; i1++)
|
||||||
|
{
|
||||||
|
var keyIdString = fileStream.ReadLengthPrefixedString();
|
||||||
|
// это число всегда либо 0 либо 1
|
||||||
|
var unkNumber = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
map[keyIdString] = unkNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
clanTreeInfo.AlliesMap = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
infos.Add(clanTreeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var clanInfo = new ClansFileData(clanFeatureSet, clanCount, infos);
|
||||||
|
|
||||||
|
return clanInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObjectsFileData LoadGameObjects(FileStream fileStream)
|
||||||
|
{
|
||||||
|
var gameObjectsFeatureSet = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
var gameObjectsCount = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
Span<byte> settingVal1 = stackalloc byte[4];
|
||||||
|
Span<byte> settingVal2 = stackalloc byte[4];
|
||||||
|
Span<byte> settingVal3 = stackalloc byte[4];
|
||||||
|
|
||||||
|
List<GameObjectInfo> gameObjectInfos = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < gameObjectsCount; i++)
|
||||||
|
{
|
||||||
|
var gameObjectInfo = new GameObjectInfo();
|
||||||
|
// ReadGameObjectData
|
||||||
|
gameObjectInfo.Type = (GameObjectType) fileStream.ReadInt32LittleEndian();
|
||||||
|
gameObjectInfo.UnknownFlags = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
// UNITS\UNITS\HERO\hero_t.dat
|
||||||
|
gameObjectInfo.DatString = fileStream.ReadLengthPrefixedString();
|
||||||
|
|
||||||
|
if (2 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
gameObjectInfo.OwningClanIndex = fileStream.ReadInt32LittleEndian();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (3 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
gameObjectInfo.UnknownInt3 = fileStream.ReadInt32LittleEndian();
|
||||||
|
}
|
||||||
|
|
||||||
|
// читает 12 байт
|
||||||
|
gameObjectInfo.Position = new Vector3(
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian()
|
||||||
|
);
|
||||||
|
|
||||||
|
// ещё раз читает 12 байт
|
||||||
|
gameObjectInfo.Rotation = new Vector3(
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (gameObjectsFeatureSet < 10)
|
||||||
|
{
|
||||||
|
// если фичесет меньше 10, то игра забивает вектор единицами
|
||||||
|
gameObjectInfo.Scale = new Vector3(1, 1, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// в противном случае читает ещё вектор из файла
|
||||||
|
gameObjectInfo.Scale = new Vector3(
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (6 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
// у HERO пустая строка
|
||||||
|
gameObjectInfo.UnknownString2 = fileStream.ReadLengthPrefixedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (7 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
gameObjectInfo.UnknownInt4 = fileStream.ReadInt32LittleEndian();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (8 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
gameObjectInfo.UnknownInt5 = fileStream.ReadInt32LittleEndian();
|
||||||
|
gameObjectInfo.UnknownInt6 = fileStream.ReadInt32LittleEndian();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (5 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
// тут игра вызывает ещё одну функцию чтения файла - видимо это настройки объекта
|
||||||
|
|
||||||
|
var unused = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
var innerCount = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
List<GameObjectSetting> settings = [];
|
||||||
|
for (var i1 = 0; i1 < innerCount; i1++)
|
||||||
|
{
|
||||||
|
// судя по всему это тип настройки
|
||||||
|
// 0 - float, 1 - int, 2?
|
||||||
|
var settingType = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
settingVal1.Clear();
|
||||||
|
settingVal2.Clear();
|
||||||
|
settingVal3.Clear();
|
||||||
|
fileStream.ReadExactly(settingVal1);
|
||||||
|
fileStream.ReadExactly(settingVal2);
|
||||||
|
fileStream.ReadExactly(settingVal3);
|
||||||
|
|
||||||
|
IntFloatValue val1;
|
||||||
|
IntFloatValue val2;
|
||||||
|
IntFloatValue val3;
|
||||||
|
|
||||||
|
if (settingType == 0)
|
||||||
|
{
|
||||||
|
// float
|
||||||
|
val1 = new IntFloatValue(settingVal1);
|
||||||
|
val2 = new IntFloatValue(settingVal2);
|
||||||
|
val3 = new IntFloatValue(settingVal3);
|
||||||
|
// var innerFloat1 = fileStream.ReadFloatLittleEndian();
|
||||||
|
// var innerFloat2 = fileStream.ReadFloatLittleEndian();
|
||||||
|
// судя по всему это значение настройки
|
||||||
|
// var innerFloat3 = fileStream.ReadFloatLittleEndian();
|
||||||
|
}
|
||||||
|
else if (settingType == 1)
|
||||||
|
{
|
||||||
|
val1 = new IntFloatValue(settingVal1);
|
||||||
|
val2 = new IntFloatValue(settingVal2);
|
||||||
|
val3 = new IntFloatValue(settingVal3);
|
||||||
|
// var innerInt1 = fileStream.ReadInt32LittleEndian();
|
||||||
|
// var innerInt2 = fileStream.ReadInt32LittleEndian();
|
||||||
|
// судя по всему это значение настройки
|
||||||
|
// var innerInt3 = fileStream.ReadInt32LittleEndian();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Settings value type is not float or int");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invulnerability
|
||||||
|
// Life state
|
||||||
|
// LogicalID
|
||||||
|
// ClanID
|
||||||
|
// Type
|
||||||
|
// MaxSpeedPercent
|
||||||
|
// MaximumOre
|
||||||
|
// CurrentOre
|
||||||
|
var name = fileStream.ReadLengthPrefixedString();
|
||||||
|
|
||||||
|
settings.Add(
|
||||||
|
new GameObjectSetting(
|
||||||
|
settingType,
|
||||||
|
val1,
|
||||||
|
val2,
|
||||||
|
val3,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gameObjectInfo.Settings = new GameObjectSettings(unused, innerCount, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
gameObjectInfos.Add(gameObjectInfo);
|
||||||
|
|
||||||
|
// end ReadGameObjectData
|
||||||
|
}
|
||||||
|
|
||||||
|
// DATA\MAPS\KM_2\land
|
||||||
|
// DATA\MAPS\SC_3\land
|
||||||
|
var landString = fileStream.ReadLengthPrefixedString();
|
||||||
|
|
||||||
|
int unkInt7 = 0;
|
||||||
|
string? missionTechDescription = null;
|
||||||
|
if (1 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
unkInt7 = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
// ? - байт cd
|
||||||
|
|
||||||
|
// Mission??????????trm\Is.\Ir
|
||||||
|
// Skirmish 1. Full Base, One opponent?????
|
||||||
|
// New mission?????????????????M
|
||||||
|
missionTechDescription = fileStream.ReadLengthPrefixedString();
|
||||||
|
}
|
||||||
|
|
||||||
|
LodeData? lodeData = null;
|
||||||
|
if (4 < gameObjectsFeatureSet)
|
||||||
|
{
|
||||||
|
var unused = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
var lodeCount = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
List<LodeInfo> lodeInfos = [];
|
||||||
|
for (var i1 = 0; i1 < lodeCount; i1++)
|
||||||
|
{
|
||||||
|
var unkLodeVector = new Vector3(
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian(),
|
||||||
|
fileStream.ReadFloatLittleEndian()
|
||||||
|
);
|
||||||
|
|
||||||
|
var unkLodeInt1 = fileStream.ReadInt32LittleEndian();
|
||||||
|
var unkLodeFlags2 = fileStream.ReadInt32LittleEndian();
|
||||||
|
var unkLodeFloat3 = fileStream.ReadFloatLittleEndian();
|
||||||
|
var unkLodeInt4 = fileStream.ReadInt32LittleEndian();
|
||||||
|
|
||||||
|
lodeInfos.Add(
|
||||||
|
new LodeInfo(
|
||||||
|
unkLodeVector,
|
||||||
|
unkLodeInt1,
|
||||||
|
unkLodeFlags2,
|
||||||
|
unkLodeFloat3,
|
||||||
|
unkLodeInt4
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
lodeData = new LodeData(unused, lodeCount, lodeInfos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GameObjectsFileData(
|
||||||
|
gameObjectsFeatureSet,
|
||||||
|
gameObjectsCount,
|
||||||
|
gameObjectInfos,
|
||||||
|
landString,
|
||||||
|
unkInt7,
|
||||||
|
missionTechDescription,
|
||||||
|
lodeData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MissionTma(ArealsFileData ArealData, ClansFileData ClansData, GameObjectsFileData GameObjectsData);
|
3
MissionTmaLib/UnknownClanTreeInfoPart.cs
Normal file
3
MissionTmaLib/UnknownClanTreeInfoPart.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record UnknownClanTreeInfoPart(int UnkInt1, Vector3 UnkVector, float UnkInt2, float UnkInt3);
|
3
MissionTmaLib/Vector3.cs
Normal file
3
MissionTmaLib/Vector3.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace MissionTmaLib;
|
||||||
|
|
||||||
|
public record Vector3(float X, float Y, float Z);
|
@ -52,6 +52,7 @@ public class App
|
|||||||
|
|
||||||
serviceCollection.AddSingleton(new NResExplorerViewModel());
|
serviceCollection.AddSingleton(new NResExplorerViewModel());
|
||||||
serviceCollection.AddSingleton(new TexmExplorerViewModel());
|
serviceCollection.AddSingleton(new TexmExplorerViewModel());
|
||||||
|
serviceCollection.AddSingleton(new MissionTmaViewModel());
|
||||||
|
|
||||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using MissionTmaLib.Parsing;
|
||||||
using NativeFileDialogSharp;
|
using NativeFileDialogSharp;
|
||||||
using NResLib;
|
using NResLib;
|
||||||
using NResUI.Abstractions;
|
using NResUI.Abstractions;
|
||||||
@ -12,13 +13,15 @@ namespace NResUI.ImGuiUI
|
|||||||
{
|
{
|
||||||
private readonly NResExplorerViewModel _nResExplorerViewModel;
|
private readonly NResExplorerViewModel _nResExplorerViewModel;
|
||||||
private readonly TexmExplorerViewModel _texmExplorerViewModel;
|
private readonly TexmExplorerViewModel _texmExplorerViewModel;
|
||||||
|
private readonly MissionTmaViewModel _missionTmaViewModel;
|
||||||
|
|
||||||
private readonly MessageBoxModalPanel _messageBox;
|
private readonly MessageBoxModalPanel _messageBox;
|
||||||
public MainMenuBar(NResExplorerViewModel nResExplorerViewModel, TexmExplorerViewModel texmExplorerViewModel, MessageBoxModalPanel messageBox)
|
public MainMenuBar(NResExplorerViewModel nResExplorerViewModel, TexmExplorerViewModel texmExplorerViewModel, MessageBoxModalPanel messageBox, MissionTmaViewModel missionTmaViewModel)
|
||||||
{
|
{
|
||||||
_nResExplorerViewModel = nResExplorerViewModel;
|
_nResExplorerViewModel = nResExplorerViewModel;
|
||||||
_texmExplorerViewModel = texmExplorerViewModel;
|
_texmExplorerViewModel = texmExplorerViewModel;
|
||||||
_messageBox = messageBox;
|
_messageBox = messageBox;
|
||||||
|
_missionTmaViewModel = missionTmaViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnImGuiRender()
|
public void OnImGuiRender()
|
||||||
@ -78,6 +81,19 @@ namespace NResUI.ImGuiUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.MenuItem("Open Mission TMA"))
|
||||||
|
{
|
||||||
|
var result = Dialog.FileOpen("tma");
|
||||||
|
|
||||||
|
if (result.IsOk)
|
||||||
|
{
|
||||||
|
var path = result.Path;
|
||||||
|
var parseResult = MissionTmaParser.ReadFile(path);
|
||||||
|
|
||||||
|
_missionTmaViewModel.SetParseResult(parseResult, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_nResExplorerViewModel.HasFile)
|
if (_nResExplorerViewModel.HasFile)
|
||||||
{
|
{
|
||||||
if (ImGui.MenuItem("Экспортировать NRes"))
|
if (ImGui.MenuItem("Экспортировать NRes"))
|
||||||
@ -102,32 +118,5 @@ namespace NResUI.ImGuiUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a direct port of imgui_demo.cpp HelpMarker function
|
|
||||||
|
|
||||||
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L190
|
|
||||||
|
|
||||||
private void ShowHint(string message)
|
|
||||||
{
|
|
||||||
// ImGui.TextDisabled("(?)");
|
|
||||||
if (ImGui.IsItemHovered())
|
|
||||||
{
|
|
||||||
// Change background transparency
|
|
||||||
ImGui.PushStyleColor(
|
|
||||||
ImGuiCol.PopupBg,
|
|
||||||
new Vector4(
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0.8f
|
|
||||||
)
|
|
||||||
);
|
|
||||||
ImGui.BeginTooltip();
|
|
||||||
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f);
|
|
||||||
ImGui.TextUnformatted(message);
|
|
||||||
ImGui.PopTextWrapPos();
|
|
||||||
ImGui.EndTooltip();
|
|
||||||
ImGui.PopStyleColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
218
NResUI/ImGuiUI/MissionTmaExplorer.cs
Normal file
218
NResUI/ImGuiUI/MissionTmaExplorer.cs
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
using ImGuiNET;
|
||||||
|
using MissionTmaLib;
|
||||||
|
using NResUI.Abstractions;
|
||||||
|
using NResUI.Models;
|
||||||
|
|
||||||
|
namespace NResUI.ImGuiUI;
|
||||||
|
|
||||||
|
public class MissionTmaExplorer : IImGuiPanel
|
||||||
|
{
|
||||||
|
private readonly MissionTmaViewModel _viewModel;
|
||||||
|
|
||||||
|
public MissionTmaExplorer(MissionTmaViewModel viewModel)
|
||||||
|
{
|
||||||
|
_viewModel = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnImGuiRender()
|
||||||
|
{
|
||||||
|
if (ImGui.Begin("Mission TMA Explorer"))
|
||||||
|
{
|
||||||
|
var mission = _viewModel.Mission;
|
||||||
|
if (_viewModel.HasFile && mission is not null)
|
||||||
|
{
|
||||||
|
ImGui.Text("Путь к файлу: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(_viewModel.Path);
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("Ареалы"))
|
||||||
|
{
|
||||||
|
var (unusedHeader, arealCount, arealInfos) = mission.ArealData;
|
||||||
|
|
||||||
|
ImGui.Text("Неиспользуемый заголовок: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(unusedHeader.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Количество ареалов: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(arealCount.ToString());
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("Информация об ареалах"))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < arealInfos.Count; i++)
|
||||||
|
{
|
||||||
|
var arealInfo = arealInfos[i];
|
||||||
|
if (ImGui.TreeNodeEx($"Ареал {i}"))
|
||||||
|
{
|
||||||
|
Utils.ShowHint("Кажется, что ареал это просто некая зона на карте");
|
||||||
|
ImGui.Text("Индекс: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(arealInfo.Index.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Количество координат: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(arealInfo.CoordsCount.ToString());
|
||||||
|
|
||||||
|
if (ImGui.BeginTable("Координаты", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("X");
|
||||||
|
ImGui.TableSetupColumn("Y");
|
||||||
|
ImGui.TableSetupColumn("Z");
|
||||||
|
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (int k = 0; k < arealInfo.Coords.Count; k++)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(arealInfo.Coords[k].X.ToString("F2"));
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(arealInfo.Coords[k].Y.ToString("F2"));
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(arealInfo.Coords[k].Z.ToString("F2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("Кланы"))
|
||||||
|
{
|
||||||
|
var (clanFeatureSet, clanCount, clanInfos) = mission.ClansData;
|
||||||
|
|
||||||
|
ImGui.Text("Фиче-сет: ");
|
||||||
|
Utils.ShowHint("Магическое число из файла, на основе которого игра читает разные секции о клане");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanFeatureSet.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Количество кланов: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanCount.ToString());
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("Информация о кланах"))
|
||||||
|
{
|
||||||
|
for (var i = 0; i < clanInfos.Count; i++)
|
||||||
|
{
|
||||||
|
var clanInfo = clanInfos[i];
|
||||||
|
if (ImGui.TreeNodeEx($"Клан {i} - \"{clanInfo.ClanName}\""))
|
||||||
|
{
|
||||||
|
ImGui.Text("Неизвестное число 1: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanInfo.UnkInt1.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("X: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanInfo.X.ToString());
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(" Y: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanInfo.Y.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Тип клана: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanInfo.ClanType.ToReadableString());
|
||||||
|
|
||||||
|
ImGui.Text("Неизвестная строка 1: ");
|
||||||
|
Utils.ShowHint("Кажется это путь к файлу поведения (Behavior), но пока не понятно. Обычно пути соответствуют 2 файла.");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanInfo.UnkString2);
|
||||||
|
|
||||||
|
if (clanInfo.UnknownParts.Count > 0)
|
||||||
|
{
|
||||||
|
ImGui.Text("Неизвестная часть");
|
||||||
|
if (ImGui.BeginTable("Неизвестная часть##unk1", 6, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Число 1");
|
||||||
|
ImGui.TableSetupColumn("X");
|
||||||
|
ImGui.TableSetupColumn("Y");
|
||||||
|
ImGui.TableSetupColumn("Z");
|
||||||
|
ImGui.TableSetupColumn("Число 2");
|
||||||
|
ImGui.TableSetupColumn("Число 3");
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
for (var i1 = 0; i1 < clanInfo.UnknownParts.Count; i1++)
|
||||||
|
{
|
||||||
|
var unkPart = clanInfo.UnknownParts[i1];
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(unkPart.UnkInt1.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(unkPart.UnkVector.X.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(unkPart.UnkVector.Y.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(unkPart.UnkVector.Z.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(unkPart.UnkInt2.ToString());
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(unkPart.UnkInt3.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("Отсутствует неизвестная часть");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Text("Путь к файлу .trf: ");
|
||||||
|
Utils.ShowHint("Не до конца понятно, что означает, вероятно это NRes с деревом исследований");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanInfo.ResearchNResPath);
|
||||||
|
|
||||||
|
ImGui.Text("Неизвестное число 3: ");
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.Text(clanInfo.UnkInt3.ToString());
|
||||||
|
|
||||||
|
ImGui.Text("Матрица союзников");
|
||||||
|
Utils.ShowHint("Если 1, то кланы - союзники, и не нападают друг на друга");
|
||||||
|
if (ImGui.BeginTable("Матрица союзников", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.NoHostExtendX))
|
||||||
|
{
|
||||||
|
ImGui.TableSetupColumn("Клан");
|
||||||
|
ImGui.TableSetupColumn("Союзник?");
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
foreach (var alliesMapKey in clanInfo.AlliesMap.Keys)
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow();
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(alliesMapKey);
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImGui.Text(clanInfo.AlliesMap[alliesMapKey].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.TreeNodeEx("Объекты"))
|
||||||
|
{
|
||||||
|
ImGui.TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("Миссия не открыта");
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -116,6 +116,8 @@ public class TexmExplorer : IImGuiPanel
|
|||||||
|
|
||||||
ImGui.EndTable();
|
ImGui.EndTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.TreePop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
NResUI/Models/MissionTmaViewModel.cs
Normal file
26
NResUI/Models/MissionTmaViewModel.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using MissionTmaLib.Parsing;
|
||||||
|
|
||||||
|
namespace NResUI.Models;
|
||||||
|
|
||||||
|
public class MissionTmaViewModel
|
||||||
|
{
|
||||||
|
public bool HasFile { get; set; }
|
||||||
|
public string? Error { get; set; }
|
||||||
|
|
||||||
|
public MissionTma? Mission { get; set; }
|
||||||
|
|
||||||
|
public string? Path { get; set; }
|
||||||
|
|
||||||
|
public void SetParseResult(MissionTmaParseResult result, string path)
|
||||||
|
{
|
||||||
|
Error = result.Error;
|
||||||
|
|
||||||
|
if (result.Mission != null)
|
||||||
|
{
|
||||||
|
HasFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mission = result.Mission;
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||||
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
<ProjectReference Include="..\TexmLib\TexmLib.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Reflection;
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace NResUI
|
namespace NResUI
|
||||||
{
|
{
|
||||||
@ -60,5 +62,32 @@ namespace NResUI
|
|||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(str);
|
return string.IsNullOrEmpty(str);
|
||||||
}
|
}
|
||||||
|
// This is a direct port of imgui_demo.cpp HelpMarker function
|
||||||
|
|
||||||
|
// https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L190
|
||||||
|
|
||||||
|
public static void ShowHint(string message)
|
||||||
|
{
|
||||||
|
// ImGui.TextDisabled("(?)");
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
// Change background transparency
|
||||||
|
ImGui.PushStyleColor(
|
||||||
|
ImGuiCol.PopupBg,
|
||||||
|
new Vector4(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0.8f
|
||||||
|
)
|
||||||
|
);
|
||||||
|
ImGui.BeginTooltip();
|
||||||
|
ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f);
|
||||||
|
ImGui.TextUnformatted(message);
|
||||||
|
ImGui.PopTextWrapPos();
|
||||||
|
ImGui.EndTooltip();
|
||||||
|
ImGui.PopStyleColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "meta", "meta", "{BAF212FE-A
|
|||||||
README.md = README.md
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -59,5 +61,9 @@ Global
|
|||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.Build.0 = Release|Any CPU
|
{7BF5C860-9194-4AF2-B5DA-216F98B03DBE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
Loading…
x
Reference in New Issue
Block a user