mirror of
				https://github.com/sampletext32/ParkanPlayground.git
				synced 2025-10-31 05:29:43 +03:00 
			
		
		
		
	добавил tma в просмотр и отрефакторил код
This commit is contained in:
		| @@ -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\\CAMPAIGN\\CAMPAIGN.01\\Mission.02\\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 TexmExplorerViewModel()); | ||||
|             serviceCollection.AddSingleton(new MissionTmaViewModel()); | ||||
|  | ||||
|             var serviceProvider = serviceCollection.BuildServiceProvider(); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System.Numerics; | ||||
| using ImGuiNET; | ||||
| using MissionTmaLib.Parsing; | ||||
| using NativeFileDialogSharp; | ||||
| using NResLib; | ||||
| using NResUI.Abstractions; | ||||
| @@ -12,13 +13,15 @@ namespace NResUI.ImGuiUI | ||||
|     { | ||||
|         private readonly NResExplorerViewModel _nResExplorerViewModel; | ||||
|         private readonly TexmExplorerViewModel _texmExplorerViewModel; | ||||
|         private readonly MissionTmaViewModel _missionTmaViewModel; | ||||
|  | ||||
|         private readonly MessageBoxModalPanel _messageBox; | ||||
|         public MainMenuBar(NResExplorerViewModel nResExplorerViewModel, TexmExplorerViewModel texmExplorerViewModel, MessageBoxModalPanel messageBox) | ||||
|         public MainMenuBar(NResExplorerViewModel nResExplorerViewModel, TexmExplorerViewModel texmExplorerViewModel, MessageBoxModalPanel messageBox, MissionTmaViewModel missionTmaViewModel) | ||||
|         { | ||||
|             _nResExplorerViewModel = nResExplorerViewModel; | ||||
|             _texmExplorerViewModel = texmExplorerViewModel; | ||||
|             _messageBox = messageBox; | ||||
|             _missionTmaViewModel = missionTmaViewModel; | ||||
|         } | ||||
|  | ||||
|         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 (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.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> | ||||
|       <ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" /> | ||||
|       <ProjectReference Include="..\NResLib\NResLib.csproj" /> | ||||
|       <ProjectReference Include="..\TexmLib\TexmLib.csproj" /> | ||||
|     </ItemGroup> | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| using System.Reflection; | ||||
| using System.Numerics; | ||||
| using System.Reflection; | ||||
| using ImGuiNET; | ||||
|  | ||||
| namespace NResUI | ||||
| { | ||||
| @@ -60,5 +62,32 @@ namespace NResUI | ||||
|         { | ||||
|             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 | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MissionTmaLib", "MissionTmaLib\MissionTmaLib.csproj", "{773D8EEA-6005-4127-9CB4-5F9F1A028B5D}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		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}.Release|Any CPU.ActiveCfg = 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 | ||||
| EndGlobal | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 bird_egop
					bird_egop