using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; namespace OpenVIII { //Class that provides language extensions made by Maki public static class Extended { public enum Languages { en, fr, de, es, it, jp } public static bool Save_As_PNG(Texture2D texture,string path, int width, int height) { var return_value = false; try { using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite); texture.SaveAsPng(fs, width, height); return_value = true; } catch (Exception e) { Memory.Log.WriteLine(e.Message); } return return_value; } //WORLD MAP COORDINATES HELPER /// /// Indicates vanilla game X axis coordinate for minimum possible X - visually the nearest to left on worldmap /// public const int WORLD_COORDS_MINLEFT = unchecked((int)0xFFFE0000); /// /// Indicates vanilla game X axis coordinate for maximum possible X - visually the furthest to right on worldmap /// public const int WORLD_COORDS_MAXRIGHT = unchecked((int)0x0001FFFF); /// /// zero based range of maximum left-right for worldmap coordinates /// public static readonly int WORLD_COORDS_XRANGE = Math.Abs(WORLD_COORDS_MINLEFT) + WORLD_COORDS_MAXRIGHT; /// /// Indicates vanilla game Y axis coordinate for minimum possible Y - visually at the semi top of worldmap /// public const int WORLD_COORDS_MINTOP = unchecked((int)0xFFFE8000); /// /// Indicates vanilla game Y axis coordinate for maximum possible Y - visually at the very bottom of worldmap /// public const int WORLD_COORDS_MAXBOTTOM = unchecked((int)0x00017FFF); /// /// zero based range of maximum left-right for worldmap coordinates /// public static readonly int WORLD_COORDS_ZRANGE = Math.Abs(WORLD_COORDS_MINTOP) + WORLD_COORDS_MAXBOTTOM; /// /// Indicates the OpenVIII coordinate system which shows maximum X axis. Minimum is always zero (it's the nearest to right side of worldmap) /// public const int WORLD_OPENVIII_MAXRIGHT = -(32 * 512); /// /// Indicates the OpenVIII coordinate system which shows maximum Y axis. Minimum is always zero (it's the furthest to bottom of worldmap) /// public const int WORLD_OPENVIII_MAXBOTTOM = -(24 * 512); /// /// Result of Vanilla.Y * x = openviii.Y equation /// public const float WORLD_COORD_YHELPER = -0.06f; /// /// This method converts vanilla world map coordinates to openVIII equivalent. /// /// /// public static float ConvertVanillaWorldXAxisToOpenVIII(float x) { if(x>WORLD_COORDS_MINLEFT) { var leftSide = x - WORLD_COORDS_MINLEFT; //this is the distance from left to middle of map //0x1FFFF is one part of map var percentUsage = leftSide / 0x1FFFF; //we now know the left side map percentage use return (float)(percentUsage * (WORLD_OPENVIII_MAXRIGHT / 2.0)); } else { var percentUsage = x / WORLD_COORDS_MAXRIGHT; var rightSide = WORLD_OPENVIII_MAXRIGHT / 2.0; return (float)(percentUsage * rightSide + rightSide); } } /// /// This method converts vanilla world map coordinates to openVIII equivalent. /// /// /// public static float ConvertVanillaWorldZAxisToOpenVIII(float z) { if (z > WORLD_COORDS_MINTOP) { var topSide = z - WORLD_COORDS_MINTOP; //this is the distance from left to middle of map //0x1FFFF is one part of map var percentUsage = topSide / 0x17FFF; //we now know the left side map percentage use return (float)(percentUsage * (WORLD_OPENVIII_MAXBOTTOM / 2.0)); } else { var percentUsage = z / WORLD_COORDS_MAXBOTTOM; var rightSide = WORLD_OPENVIII_MAXBOTTOM / 2.0; return (float)(percentUsage * rightSide + rightSide); } } /// /// This method converts vanilla world map coordinates to openVIII equivalent /// /// /// public static float ConvertVanillaWorldYAxisToOpenVIII(float y) => y * WORLD_COORD_YHELPER; //https://stackoverflow.com/a/2887/4509036 public static T ByteArrayToClass(byte[] bytes) where T : class { var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject()); } finally { handle.Free(); } } //https://stackoverflow.com/a/2887/4509036 public static T ByteArrayToStructure(byte[] bytes) where T : struct { var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); try { return (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject()); } finally { handle.Free(); } } #if DEBUG || _WINDOWS public static void DumpBuffer(byte[] buffer, string path) => System.IO.File.WriteAllBytes(path, buffer); public static void DumpBuffer(System.IO.MemoryStream ms, string path) => System.IO.File.WriteAllBytes(path, ms.GetBuffer()); public static void DumpBuffer(System.IO.MemoryStream ms) => System.IO.File.WriteAllBytes(GetUnixFullPath(System.IO.Path.Combine(Memory.FF8Dir, "debugUnpack.debug")), ms.GetBuffer()); public static void DumpTexture(Texture2D tex, string s) { if (Directory.Exists(Path.GetDirectoryName(s))) Extended.Save_As_PNG(tex, s, tex.Width, tex.Height); } #endif ///Detect if an object is a number /// public static bool IsNumber(object value) => value is sbyte || value is byte || value is short || value is ushort || value is int || value is uint || value is long || value is ulong || value is float || value is double || value is decimal; public static double Distance3D(Vector3 xo, Vector3 xa) => Vector3.Distance(xo, xa); /// /// Real-time collision 3D Raycast on plane + barycentric calculation /// /// ray with origin + given direction /// vertex A /// vertex B /// vertex C /// out param for barycentric vector /// 0 - not applicable, 1- intersects; public static int RayIntersection3D(Ray R, Vector3 a, Vector3 b, Vector3 c, out Vector3 barycentric) { barycentric = Vector3.Zero; var p1 = b - a; var p2 = c - a; var lhs = Vector3.Cross(p1, p2); if (lhs == Vector3.Zero) return -1; var direction = R.Direction; var rhs = R.Position - a; var dot00 = -Vector3.Dot(lhs, rhs); var dot01 = Vector3.Dot(lhs, direction); if (Math.Abs(dot01) < 1E-08f) if (dot00 == 0f) return 2; else return 0; else { var dot02 = dot00 / dot01; if (dot02 < 0.0) return 0; barycentric = R.Position + dot02 * direction; var dot10 = Vector3.Dot(p1, p1); var dot11 = Vector3.Dot(p1, p2); var dot12 = Vector3.Dot(p2, p2); var lhs2 = barycentric - a; var dot21 = Vector3.Dot(lhs2, p1); var dot22 = Vector3.Dot(lhs2, p2); var dot30 = dot11 * dot11 - dot10 * dot12; var dot31 = (dot11 * dot22 - dot12 * dot21) / dot30; if (dot31 < 0.0 || dot31 > 1.0) return 0; var dot32 = (dot11 * dot21 - dot10 * dot22) / dot30; if (dot32 < 0.0 || (dot31 + dot32) > 1.0) return 0; return 1; } } public static BoundingBox GetBoundingBox(Vector3 a, Vector3 b, Vector3 c) { var Minx = (new float[] { a.X, b.X, c.X }).Min(); var Maxx = (new float[] { a.X, b.X, c.X }).Max(); var Miny = (new float[] { a.Y, b.Y, c.Y }).Min(); var Maxy = (new float[] { a.Y, b.Y, c.Y }).Max(); var Minz = (new float[] { a.Z, b.Z, c.Z }).Min(); var Maxz = (new float[] { a.Z, b.Z, c.Z }).Max(); var min = new Vector3(Minx, Miny, Minz); var max = new Vector3(Maxx, Maxy, Maxz); return new BoundingBox(min, max); } /// /// Provides VertexPositionTexture[] based on translatePosition and the scale. Result is plane geometry - 4 verts, 2 tris with UV of 0.0-1.0f /// /// /// /// public static VertexPositionTexture[] GetShadowPlane(Vector3 translatePosition, float scale=1f) { /* * THREE----ZERO * | | * | | * | | * | | * TWO------ONE*/ var zero = new Vector3(1f * scale +translatePosition.X , translatePosition.Y, 1f * scale + translatePosition.Z); var one = new Vector3(1f * scale + translatePosition.X, translatePosition.Y, translatePosition.Z); var two = translatePosition; var three = new Vector3(translatePosition.X, translatePosition.Y, 1f * scale + translatePosition.Z); var vpt = new VertexPositionTexture[] { new VertexPositionTexture(zero, new Vector2(1f,1f)), new VertexPositionTexture(one, new Vector2(1f,0f)), new VertexPositionTexture(two, Vector2.Zero), new VertexPositionTexture(two, Vector2.Zero), new VertexPositionTexture(three, new Vector2(0f,1f)), new VertexPositionTexture(zero, new Vector2(1f,1f)), }; return vpt; } /// /// Some debug text is crashing due to brackets not appearing in chartable. This function removes brackets inside string /// /// /// public static string RemoveBrackets(string s) => s.Replace('{', ' ').Replace('}', ' '); public static bool GetBit(byte @object, int positionFromRight) => ((@object >> positionFromRight) & 1) > 0; public static bool GetBit(int @object, int positionFromRight) => ((@object >> positionFromRight) & 1) > 0; /// /// Reads given char[] until null terminator, but returns as byte[]- to be used with FF8String /// /// /// public static byte[] GetBinaryString(BinaryReader br) { var bb = new List(); byte b; while ((b = br.ReadByte()) != 0x00) bb.Add(b); return bb.ToArray(); } public static bool IsLinux { get { var p = (int)Environment.OSVersion.Platform; return (p == 4) || (p == 6) || (p == 128); } } public static string GetUnixFullPath(string pt) { #if _WINDOWS return System.IO.Path.GetFullPath(pt.Replace('/', '\\')); #else return System.IO.Path.GetFullPath(pt.Replace("\\", "/")); #endif } public static bool In(int _in, Vector2 range) => _in >= range.X && _in <= range.Y; public static bool In(float _in, Vector2 range) => _in >= range.X && _in <= range.Y; //: false; public static bool In(int _in, int min, int max) => In(_in, new Vector2(min, max)); public static bool In(float _in, float min, float max) => In(_in, new Vector2(min, max)); public static Matrix GetRotationMatrixX(float angle) => new Matrix( 1, 0, 0, 0, 0, (float)Math.Cos(Radians(angle)), -(float)Math.Sin(Radians(angle)), 0, 0, (float)Math.Sin(Radians(angle)), (float)Math.Cos(Radians(angle)), 0, 0, 0, 0, 0); public static Matrix GetRotationMatrixY(double angle) => new Matrix( (float)Math.Cos(Radians(angle)), 0, (float)Math.Sin(Radians(angle)), 0, 0, 1, 0, 0, -(float)Math.Sin(Radians(angle)), 0, (float)Math.Cos(Radians(angle)), 0, 0, 0, 0, 0); public static Matrix GetRotationMatrixZ(float angle) => new Matrix( (float)Math.Cos(Radians(angle)), -(float)Math.Sin(Radians(angle)), 0, 0, (float)Math.Sin(Radians(angle)), (float)Math.Cos(Radians(angle)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 0); /// /// This Matrix operation performs Matrix multiplication and transposing in-place /// /// /// /// public static Matrix MatrixMultiply_transpose(Matrix a, Matrix b) => new Matrix( 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, 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, 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, 0, 0, 0, 0); //This is the first time I had issue with precision. Cosinus from 270o was different for float and double. MathHelper is broken... public static double Radians(double angle) => angle * Math.PI / 180; public static double Cos(double angle) => Math.Cos(Radians(angle)); public static double Sin(double angle) => Math.Sin(Radians(angle)); /// /// Converts short to float via x/4096f /// /// /// public static float S16ToFloat(short x) => x / 4096f; public static string GetLanguageShort(bool bUseAlternative = false) { var languageIndicator = Memory.Languages.ToString(); return bUseAlternative ? languageIndicator == "en" ? "us" : languageIndicator : languageIndicator; } public static Vector3 ShrinkVector4ToVector3(Vector4 reference, bool bMirrorY = false) { float x, y, z; reference.Deconstruct(out x, out y, out z, out _); if (bMirrorY) y = -y; return new Vector3(x, y, z); } /// /// Converts short to float via x/4096f /// /// mockup of short. I.e. short(50) -> float(50) -> 50.0f / 4096f /// public static float S16ToFloat(float x) => x / 4096f; /// /// Converts Vector3 containing direct short>float to Vector3 that XYZ are treated by S16ToFloat /// /// /// public static Vector3 S16VectorToFloat(Vector3 vec) => new Vector3( S16ToFloat(vec.X), S16ToFloat(vec.Y), S16ToFloat(vec.Z)); public static ushort UshortLittleEndian(ushort ushort_) => (ushort)((ushort_ << 8) | (ushort_ >> 8)); public static short ShortLittleEndian(short ushort_) => (short)((ushort_ << 8) | (ushort_ >> 8)); public static uint UintLittleEndian(uint uint_) => (uint_ << 24) | ((uint_ << 8) & 0x00FF0000) | ((uint_ >> 8) & 0x0000FF00) | (uint_ >> 24); public static int UintLittleEndian(int uint_) => (uint_ << 24) | ((uint_ << 8) & 0x00FF0000) | ((uint_ >> 8) & 0x0000FF00) | (uint_ >> 24); public static int ClampOverload(int a, int min, int max) => a < min ? max - Math.Abs(a) : a > max ? a - max : a; public static float ClampOverload(float a, float min, float max) => a < min ? max - Math.Abs(a) : a > max ? a - max : a; public static bool bRequestedBackBuffer = false; public static bool bBackBufferAvailable = false; private static Texture2D backBufferTexture; public static Texture2D BackBufferTexture { get { if (bBackBufferAvailable) { bBackBufferAvailable = false; return backBufferTexture; } else { return null; } } set { backBufferTexture = value; } } /// /// Makes a request AFTER base.draw() to dump the backBuffer- after that the callback happens to /// postBackBufferDelegate, so make sure to set this parameter to method you want /// /// public static void RequestBackBuffer() { bBackBufferAvailable = false; bRequestedBackBuffer = true; } public delegate void PostBackBufferDelegate(); public static PostBackBufferDelegate postBackBufferDelegate = EmptyPostBackBufferDelegate; public static void EmptyPostBackBufferDelegate() { ; } /// /// This void converts vertexPositionTexture[] so it's texture coordinates /// are changing based on requested sprite (atlas) /// /// /// /// public static void ConvertToSprite(ref VertexPositionTexture[] vpt, int maxSprite, int current) { float spriteLength = vpt.Max(x => x.TextureCoordinate.X) / maxSprite; float startAt = current * spriteLength; for(int i = 0; i