Extended.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using System.Threading.Tasks;
  10. namespace OpenVIII
  11. {
  12. //Class that provides language extensions made by Maki
  13. public static class Extended
  14. {
  15. public enum Languages
  16. {
  17. en,
  18. fr,
  19. de,
  20. es,
  21. it,
  22. jp
  23. }
  24. //WORLD MAP COORDINATES HELPER
  25. /// <summary>
  26. /// Indicates vanilla game X axis coordinate for minimum possible X - visually the nearest to left on worldmap
  27. /// </summary>
  28. public const int WORLD_COORDS_MINLEFT = unchecked((int)0xFFFE0000);
  29. /// <summary>
  30. /// Indicates vanilla game X axis coordinate for maximum possible X - visually the furthest to right on worldmap
  31. /// </summary>
  32. public const int WORLD_COORDS_MAXRIGHT = unchecked((int)0x0001FFFF);
  33. /// <summary>
  34. /// zero based range of maximum left-right for worldmap coordinates
  35. /// </summary>
  36. public static readonly int WORLD_COORDS_XRANGE = Math.Abs(WORLD_COORDS_MINLEFT) + WORLD_COORDS_MAXRIGHT;
  37. /// <summary>
  38. /// Indicates vanilla game Y axis coordinate for minimum possible Y - visually at the semi top of worldmap
  39. /// </summary>
  40. public const int WORLD_COORDS_MINTOP = unchecked((int)0xFFFE8000);
  41. /// <summary>
  42. /// Indicates vanilla game Y axis coordinate for maximum possible Y - visually at the very bottom of worldmap
  43. /// </summary>
  44. public const int WORLD_COORDS_MAXBOTTOM = unchecked((int)0x00017FFF);
  45. /// <summary>
  46. /// zero based range of maximum left-right for worldmap coordinates
  47. /// </summary>
  48. public static readonly int WORLD_COORDS_ZRANGE = Math.Abs(WORLD_COORDS_MINTOP) + WORLD_COORDS_MAXBOTTOM;
  49. /// <summary>
  50. /// Indicates the OpenVIII coordinate system which shows maximum X axis. Minimum is always zero (it's the nearest to right side of worldmap)
  51. /// </summary>
  52. public const int WORLD_OPENVIII_MAXRIGHT = -(32 * 512);
  53. /// <summary>
  54. /// Indicates the OpenVIII coordinate system which shows maximum Y axis. Minimum is always zero (it's the furthest to bottom of worldmap)
  55. /// </summary>
  56. public const int WORLD_OPENVIII_MAXBOTTOM = -(24 * 512);
  57. /// <summary>
  58. /// Result of Vanilla.Y * x = openviii.Y equation
  59. /// </summary>
  60. public const float WORLD_COORD_YHELPER = -0.06f;
  61. /// <summary>
  62. /// This method converts vanilla world map coordinates to openVIII equivalent.
  63. /// </summary>
  64. /// <param name="x"></param>
  65. /// <returns></returns>
  66. public static float ConvertVanillaWorldXAxisToOpenVIII(float x)
  67. {
  68. if(x>WORLD_COORDS_MINLEFT)
  69. {
  70. var leftSide = x - WORLD_COORDS_MINLEFT; //this is the distance from left to middle of map
  71. //0x1FFFF is one part of map
  72. var percentUsage = leftSide / 0x1FFFF; //we now know the left side map percentage use
  73. return (float)(percentUsage * (WORLD_OPENVIII_MAXRIGHT / 2.0));
  74. }
  75. else
  76. {
  77. var percentUsage = x / WORLD_COORDS_MAXRIGHT;
  78. var rightSide = WORLD_OPENVIII_MAXRIGHT / 2.0;
  79. return (float)(percentUsage * rightSide + rightSide);
  80. }
  81. }
  82. /// <summary>
  83. /// This method converts vanilla world map coordinates to openVIII equivalent.
  84. /// </summary>
  85. /// <param name="x"></param>
  86. /// <returns></returns>
  87. public static float ConvertVanillaWorldZAxisToOpenVIII(float z)
  88. {
  89. if (z > WORLD_COORDS_MINTOP)
  90. {
  91. var topSide = z - WORLD_COORDS_MINTOP; //this is the distance from left to middle of map
  92. //0x1FFFF is one part of map
  93. var percentUsage = topSide / 0x17FFF; //we now know the left side map percentage use
  94. return (float)(percentUsage * (WORLD_OPENVIII_MAXBOTTOM / 2.0));
  95. }
  96. else
  97. {
  98. var percentUsage = z / WORLD_COORDS_MAXBOTTOM;
  99. var rightSide = WORLD_OPENVIII_MAXBOTTOM / 2.0;
  100. return (float)(percentUsage * rightSide + rightSide);
  101. }
  102. }
  103. /// <summary>
  104. /// This method converts vanilla world map coordinates to openVIII equivalent
  105. /// </summary>
  106. /// <param name="y"></param>
  107. /// <returns></returns>
  108. public static float ConvertVanillaWorldYAxisToOpenVIII(float y)
  109. => y * WORLD_COORD_YHELPER;
  110. //https://stackoverflow.com/a/2887/4509036
  111. public static T ByteArrayToClass<T>(byte[] bytes) where T : class
  112. {
  113. var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
  114. try
  115. {
  116. return (T)Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
  117. }
  118. finally
  119. {
  120. handle.Free();
  121. }
  122. }
  123. //https://stackoverflow.com/a/2887/4509036
  124. public static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
  125. {
  126. var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
  127. try
  128. {
  129. return (T)Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
  130. }
  131. finally
  132. {
  133. handle.Free();
  134. }
  135. }
  136. #if DEBUG || _WINDOWS
  137. public static void DumpBuffer(byte[] buffer, string path)
  138. => System.IO.File.WriteAllBytes(path, buffer);
  139. public static void DumpBuffer(System.IO.MemoryStream ms, string path)
  140. => System.IO.File.WriteAllBytes(path, ms.GetBuffer());
  141. public static void DumpBuffer(System.IO.MemoryStream ms)
  142. => System.IO.File.WriteAllBytes(GetUnixFullPath(System.IO.Path.Combine(Memory.FF8Dir, "debugUnpack.debug")), ms.GetBuffer());
  143. public static void DumpTexture(Texture2D tex, string s)
  144. {
  145. if (Directory.Exists(Path.GetDirectoryName(s)))
  146. using (var fs = new System.IO.FileStream(s, System.IO.FileMode.Create, System.IO.FileAccess.Write))
  147. tex.SaveAsPng(fs, tex.Width, tex.Height);
  148. }
  149. #endif
  150. ///<summary>Detect if an object is a number</summary>
  151. ///<see cref="https://stackoverflow.com/questions/1130698/checking-if-an-object-is-a-number-in-c-sharp"/>
  152. public static bool IsNumber(object value) => value is sbyte
  153. || value is byte
  154. || value is short
  155. || value is ushort
  156. || value is int
  157. || value is uint
  158. || value is long
  159. || value is ulong
  160. || value is float
  161. || value is double
  162. || value is decimal;
  163. public static double Distance3D(Vector3 xo, Vector3 xa) => Vector3.Distance(xo, xa);
  164. /// <summary>
  165. /// Real-time collision 3D Raycast on plane + barycentric calculation
  166. /// </summary>
  167. /// <param name="R">ray with origin + given direction</param>
  168. /// <param name="a">vertex A</param>
  169. /// <param name="b">vertex B</param>
  170. /// <param name="c">vertex C</param>
  171. /// <param name="barycentric">out param for barycentric vector</param>
  172. /// <returns>0 - not applicable, 1- intersects;</returns>
  173. public static int RayIntersection3D(Ray R, Vector3 a, Vector3 b, Vector3 c, out Vector3 barycentric)
  174. {
  175. barycentric = Vector3.Zero;
  176. var p1 = b - a;
  177. var p2 = c - a;
  178. var lhs = Vector3.Cross(p1, p2);
  179. if (lhs == Vector3.Zero)
  180. return -1;
  181. var direction = R.Direction;
  182. var rhs = R.Position - a;
  183. var dot00 = -Vector3.Dot(lhs, rhs);
  184. var dot01 = Vector3.Dot(lhs, direction);
  185. if (Math.Abs(dot01) < 1E-08f)
  186. if (dot00 == 0f)
  187. return 2;
  188. else return 0;
  189. else
  190. {
  191. var dot02 = dot00 / dot01;
  192. if (dot02 < 0.0)
  193. return 0;
  194. barycentric = R.Position + dot02 * direction;
  195. var dot10 = Vector3.Dot(p1, p1);
  196. var dot11 = Vector3.Dot(p1, p2);
  197. var dot12 = Vector3.Dot(p2, p2);
  198. var lhs2 = barycentric - a;
  199. var dot21 = Vector3.Dot(lhs2, p1);
  200. var dot22 = Vector3.Dot(lhs2, p2);
  201. var dot30 = dot11 * dot11 - dot10 * dot12;
  202. var dot31 = (dot11 * dot22 - dot12 * dot21) / dot30;
  203. if (dot31 < 0.0 || dot31 > 1.0)
  204. return 0;
  205. var dot32 = (dot11 * dot21 - dot10 * dot22) / dot30;
  206. if (dot32 < 0.0 || (dot31 + dot32) > 1.0)
  207. return 0;
  208. return 1;
  209. }
  210. }
  211. public static BoundingBox GetBoundingBox(Vector3 a, Vector3 b, Vector3 c)
  212. {
  213. var Minx = (new float[] { a.X, b.X, c.X }).Min();
  214. var Maxx = (new float[] { a.X, b.X, c.X }).Max();
  215. var Miny = (new float[] { a.Y, b.Y, c.Y }).Min();
  216. var Maxy = (new float[] { a.Y, b.Y, c.Y }).Max();
  217. var Minz = (new float[] { a.Z, b.Z, c.Z }).Min();
  218. var Maxz = (new float[] { a.Z, b.Z, c.Z }).Max();
  219. var min = new Vector3(Minx, Miny, Minz);
  220. var max = new Vector3(Maxx, Maxy, Maxz);
  221. return new BoundingBox(min, max);
  222. }
  223. /// <summary>
  224. /// Provides VertexPositionTexture[] based on translatePosition and the scale. Result is plane geometry - 4 verts, 2 tris with UV of 0.0-1.0f
  225. /// </summary>
  226. /// <param name="translatePosition"></param>
  227. /// <param name="scale"></param>
  228. /// <returns></returns>
  229. public static VertexPositionTexture[] GetShadowPlane(Vector3 translatePosition, float scale=1f)
  230. {
  231. /*
  232. * THREE----ZERO
  233. * | |
  234. * | |
  235. * | |
  236. * | |
  237. * TWO------ONE*/
  238. var zero = new Vector3(1f * scale +translatePosition.X , translatePosition.Y, 1f * scale + translatePosition.Z);
  239. var one = new Vector3(1f * scale + translatePosition.X, translatePosition.Y, translatePosition.Z);
  240. var two = translatePosition;
  241. var three = new Vector3(translatePosition.X, translatePosition.Y, 1f * scale + translatePosition.Z);
  242. var vpt = new VertexPositionTexture[]
  243. {
  244. new VertexPositionTexture(zero, new Vector2(1f,1f)),
  245. new VertexPositionTexture(one, new Vector2(1f,0f)),
  246. new VertexPositionTexture(two, Vector2.Zero),
  247. new VertexPositionTexture(two, Vector2.Zero),
  248. new VertexPositionTexture(three, new Vector2(0f,1f)),
  249. new VertexPositionTexture(zero, new Vector2(1f,1f)),
  250. };
  251. return vpt;
  252. }
  253. /// <summary>
  254. /// Some debug text is crashing due to brackets not appearing in chartable. This function removes brackets inside string
  255. /// </summary>
  256. /// <param name="s"></param>
  257. /// <returns></returns>
  258. public static string RemoveBrackets(string s) => s.Replace('{', ' ').Replace('}', ' ');
  259. public static bool GetBit(byte @object, int positionFromRight) => ((@object >> positionFromRight) & 1) > 0;
  260. public static bool GetBit(int @object, int positionFromRight) => ((@object >> positionFromRight) & 1) > 0;
  261. /// <summary>
  262. /// Reads given char[] until null terminator, but returns as byte[]- to be used with FF8String
  263. /// </summary>
  264. /// <param name=""></param>
  265. /// <returns></returns>
  266. public static byte[] GetBinaryString(BinaryReader br)
  267. {
  268. var bb = new List<byte>();
  269. byte b;
  270. while ((b = br.ReadByte()) != 0x00)
  271. bb.Add(b);
  272. return bb.ToArray();
  273. }
  274. public static bool IsLinux
  275. {
  276. get
  277. {
  278. var p = (int)Environment.OSVersion.Platform;
  279. return (p == 4) || (p == 6) || (p == 128);
  280. }
  281. }
  282. public static string GetUnixFullPath(string pt)
  283. {
  284. #if _WINDOWS
  285. return System.IO.Path.GetFullPath(pt.Replace('/', '\\'));
  286. #else
  287. return System.IO.Path.GetFullPath(pt.Replace("\\", "/"));
  288. #endif
  289. }
  290. public static bool In(int _in, Vector2 range) =>
  291. _in >= range.X && _in <= range.Y;
  292. public static bool In(float _in, Vector2 range) => _in >= range.X && _in <= range.Y;
  293. //: false;
  294. public static bool In(int _in, int min, int max) => In(_in, new Vector2(min, max));
  295. public static bool In(float _in, float min, float max) => In(_in, new Vector2(min, max));
  296. public static Matrix GetRotationMatrixX(float angle)
  297. => new Matrix(
  298. 1, 0, 0, 0,
  299. 0, (float)Math.Cos(Radians(angle)), -(float)Math.Sin(Radians(angle)), 0,
  300. 0, (float)Math.Sin(Radians(angle)), (float)Math.Cos(Radians(angle)), 0,
  301. 0, 0, 0, 0);
  302. public static Matrix GetRotationMatrixY(double angle)
  303. => new Matrix(
  304. (float)Math.Cos(Radians(angle)), 0, (float)Math.Sin(Radians(angle)), 0,
  305. 0, 1, 0, 0,
  306. -(float)Math.Sin(Radians(angle)), 0, (float)Math.Cos(Radians(angle)), 0,
  307. 0, 0, 0, 0);
  308. public static Matrix GetRotationMatrixZ(float angle)
  309. => new Matrix(
  310. (float)Math.Cos(Radians(angle)), -(float)Math.Sin(Radians(angle)), 0, 0,
  311. (float)Math.Sin(Radians(angle)), (float)Math.Cos(Radians(angle)), 0, 0,
  312. 0, 0, 1, 0,
  313. 0, 0, 0, 0);
  314. /// <summary>
  315. /// This Matrix operation performs Matrix multiplication and transposing in-place
  316. /// </summary>
  317. /// <param name="a"></param>
  318. /// <param name="b"></param>
  319. /// <returns></returns>
  320. public static Matrix MatrixMultiply_transpose(Matrix a, Matrix b)
  321. => new Matrix(
  322. b.M11 * a.M11 + b.M21 * a.M12 + b.M31 * a.M13, b.M11 * a.M21 + b.M21 * a.M22 + b.M31 * a.M23, b.M11 * a.M31 + b.M21 * a.M32 + b.M31 * a.M33, 0,
  323. b.M12 * a.M11 + b.M22 * a.M12 + b.M32 * a.M13, b.M12 * a.M21 + b.M22 * a.M22 + b.M32 * a.M23, b.M12 * a.M31 + b.M22 * a.M32 + b.M32 * a.M33, 0,
  324. b.M13 * a.M11 + b.M23 * a.M12 + b.M33 * a.M13, b.M13 * a.M21 + b.M23 * a.M22 + b.M33 * a.M23, b.M13 * a.M31 + b.M23 * a.M32 + b.M33 * a.M33, 0,
  325. 0, 0, 0, 0);
  326. //This is the first time I had issue with precision. Cosinus from 270o was different for float and double. MathHelper is broken...
  327. public static double Radians(double angle) => angle * Math.PI / 180;
  328. public static double Cos(double angle) => Math.Cos(Radians(angle));
  329. public static double Sin(double angle) => Math.Sin(Radians(angle));
  330. /// <summary>
  331. /// Converts short to float via x/4096f
  332. /// </summary>
  333. /// <param name="x"></param>
  334. /// <returns></returns>
  335. public static float S16ToFloat(short x) => x / 4096f;
  336. public static string GetLanguageShort(bool bUseAlternative = false)
  337. {
  338. var languageIndicator = Memory.Languages.ToString();
  339. return bUseAlternative ? languageIndicator == "en" ? "us" : languageIndicator : languageIndicator;
  340. }
  341. public static Vector3 ShrinkVector4ToVector3(Vector4 reference, bool bMirrorY = false)
  342. {
  343. float x, y, z;
  344. reference.Deconstruct(out x, out y, out z, out _);
  345. if (bMirrorY)
  346. y = -y;
  347. return new Vector3(x, y, z);
  348. }
  349. /// <summary>
  350. /// Converts short to float via x/4096f
  351. /// </summary>
  352. /// <param name="x">mockup of short. I.e. short(50) -> float(50) -> 50.0f / 4096f</param>
  353. /// <returns></returns>
  354. public static float S16ToFloat(float x) => x / 4096f;
  355. /// <summary>
  356. /// Converts Vector3 containing direct short>float to Vector3 that XYZ are treated by S16ToFloat
  357. /// </summary>
  358. /// <param name="vec"></param>
  359. /// <returns></returns>
  360. public static Vector3 S16VectorToFloat(Vector3 vec) => new Vector3(
  361. S16ToFloat(vec.X),
  362. S16ToFloat(vec.Y),
  363. S16ToFloat(vec.Z));
  364. public static ushort UshortLittleEndian(ushort ushort_)
  365. => (ushort)((ushort_ << 8) | (ushort_ >> 8));
  366. public static short ShortLittleEndian(short ushort_)
  367. => (short)((ushort_ << 8) | (ushort_ >> 8));
  368. public static uint UintLittleEndian(uint uint_)
  369. => (uint_ << 24) | ((uint_ << 8) & 0x00FF0000) |
  370. ((uint_ >> 8) & 0x0000FF00) | (uint_ >> 24);
  371. public static int UintLittleEndian(int uint_)
  372. => (uint_ << 24) | ((uint_ << 8) & 0x00FF0000) |
  373. ((uint_ >> 8) & 0x0000FF00) | (uint_ >> 24);
  374. public static int ClampOverload(int a, int min, int max)
  375. => a < min ? max - Math.Abs(a) : a > max ? a - max : a;
  376. public static float ClampOverload(float a, float min, float max)
  377. => a < min ? max - Math.Abs(a) : a > max ? a - max : a;
  378. public static bool bRequestedBackBuffer = false;
  379. public static bool bBackBufferAvailable = false;
  380. private static Texture2D backBufferTexture;
  381. public static Texture2D BackBufferTexture
  382. {
  383. get
  384. {
  385. if (bBackBufferAvailable)
  386. {
  387. bBackBufferAvailable = false;
  388. return backBufferTexture;
  389. }
  390. else
  391. {
  392. return null;
  393. }
  394. }
  395. set
  396. {
  397. backBufferTexture = value;
  398. }
  399. }
  400. /// <summary>
  401. /// Makes a request AFTER base.draw() to dump the backBuffer- after that the callback happens to
  402. /// postBackBufferDelegate, so make sure to set this parameter to method you want
  403. /// </summary>
  404. /// <param name="callbackMethod"></param>
  405. public static void RequestBackBuffer()
  406. {
  407. bBackBufferAvailable = false;
  408. bRequestedBackBuffer = true;
  409. }
  410. public delegate void PostBackBufferDelegate();
  411. public static PostBackBufferDelegate postBackBufferDelegate = EmptyPostBackBufferDelegate;
  412. public static void EmptyPostBackBufferDelegate()
  413. {
  414. ;
  415. }
  416. /// <summary>
  417. /// This void converts vertexPositionTexture[] so it's texture coordinates
  418. /// are changing based on requested sprite (atlas)
  419. /// </summary>
  420. /// <param name="vpt"></param>
  421. /// <param name="maxSprite"></param>
  422. /// <param name="current"></param>
  423. public static void ConvertToSprite(ref VertexPositionTexture[] vpt, int maxSprite, int current)
  424. {
  425. float spriteLength = vpt.Max(x => x.TextureCoordinate.X) / maxSprite;
  426. float startAt = current * spriteLength;
  427. for(int i = 0; i<vpt.Length; i++)
  428. {
  429. if (vpt[i].TextureCoordinate.X == 0)
  430. vpt[i].TextureCoordinate.X = startAt;
  431. if (vpt[i].TextureCoordinate.X == 1)
  432. vpt[i].TextureCoordinate.X = startAt + spriteLength;
  433. }
  434. }
  435. }
  436. }