Created
January 6, 2018 05:51
-
-
Save RyuaNerin/4651b2aaf593b74b5f86915d992cc440 to your computer and use it in GitHub Desktop.
맵 좌표 계산 및 역산 클래스
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Threading.Tasks; | |
namespace NPCBot | |
{ | |
class FFXIVMap | |
{ | |
public class MapData | |
{ | |
public int area { get; set; } //teritorytype.exh [index] | |
public int mapFile { get; set; } //teritorytype.exh [mapfile] | |
public string name { get; set; } //teritorytype.exh [placename_index] placename.exh [1]; | |
public int sizeFactor { get; set; } //map.exh | |
public int center_x { get; set; } | |
public int offset_x { get; set; } | |
public int center_y { get; set; } | |
public int offset_y { get; set; } | |
public bool isARR { get; set; } | |
} | |
//지역별 보정 값과 명칭을 CSV로 보관된 상태 | |
public static Dictionary<int, MapData> mapData = null; | |
private static void InitMapData() | |
{ | |
if (mapData != null) return; | |
mapData = new Dictionary<int, MapData>(); | |
var lines = Properties.Resources.FFXIVMapData.Split('\n'); | |
foreach (var line in lines) | |
{ | |
if (string.IsNullOrWhiteSpace(line)) continue; | |
var s = line.Split(','); | |
var area = int.Parse(s[0]); | |
var mapFile = int.Parse(s[1]); | |
var name = s[2]; | |
var sizeFactor = int.Parse(s[3]); | |
var other = s[4]; | |
int cx, ox, cy, oy; | |
var isArr = s[5] == "ffxiv"; | |
if (string.IsNullOrWhiteSpace(other)) | |
{ | |
var t = GetDefaultScaleFactor(sizeFactor); | |
cx = t.Item1; ox = t.Item2; cy = t.Item3; oy = t.Item4; | |
} else | |
{ | |
var sp = other.Split('|'); | |
cx = ParseStringValue(sp[0]); ox = int.Parse(sp[1]); cy = ParseStringValue(sp[2]); oy = int.Parse(sp[3]); | |
} | |
mapData.Add(area, new MapData | |
{ | |
area = area, mapFile = mapFile, | |
name = name, | |
sizeFactor = sizeFactor, | |
center_x = cx, offset_x = ox, center_y = cy, offset_y = oy, | |
isARR = isArr | |
}); | |
} | |
} | |
/*private static float GetCoordReadable(int c, int sizeFactor) | |
{ | |
var d = (float)c / 1000.0f; | |
d *= 0.02f; | |
switch (sizeFactor) | |
{ | |
case 95: | |
return d + 22.5f; | |
case 100: | |
return d + 21.5f; | |
case 300: | |
return d + 7.5f; | |
case 400: | |
return d + 6; | |
case 200: | |
case 0: | |
return d + 11.25f; | |
case 800: | |
return d + 3.5f; | |
default: | |
return 0; | |
} | |
}*/ | |
/*public static int GetCenter(int sizeFactor) | |
{ | |
switch (sizeFactor) | |
{ | |
case 95: return 2250; | |
case 100: return 2150; | |
case 300: return 750; | |
case 400: return 600; | |
case 200: | |
case 0: | |
return 1125; | |
case 800: return 350; | |
default: return 0; | |
} | |
}*/ | |
private static Tuple<int, int, int, int> GetDefaultScaleFactor(int sizeFactor) | |
{ | |
switch (sizeFactor) | |
{ | |
case 95: return Tuple.Create(2250, 2106, 2250, 2106); | |
case 100: return Tuple.Create(2140, 1000, 2140, 1000); | |
case 200: return Tuple.Create(1120, 3000, 1120, 3000); | |
case 300: return Tuple.Create(780, 3667, 780, 3667); | |
case 400: return Tuple.Create(610, 4000, 610, 4000); | |
case 800: return Tuple.Create(350, 2000, 350, 2000); | |
default: return null; | |
} | |
} | |
//Static Methods | |
public static void Initilize() | |
{ | |
InitMapData(); | |
} | |
//네트워크 패킷에서 좌표 데이터 뽑기 | |
public static List<FFXIVMap> Parse(byte[] message, int pos = 85) | |
{ | |
var list = new List<FFXIVMap>(); | |
for (int i = pos; i < message.Length - 3; i++) | |
{ | |
if (message[i] == 0x02 && message[i + 1] == 0x2E) | |
{ | |
int len = message[i + 2]; | |
if ((i + len + 2) < message.Length && message[i + len + 2] == 0x03) | |
{ | |
byte[] mapBuf = new byte[len + 3]; | |
Array.Copy(message, i, mapBuf, 0, len + 3); | |
FFXIVMap map = new FFXIVMap(mapBuf); | |
if (map.isVaild() == false) continue; | |
if (pos < i) | |
{ | |
byte[] sub = new byte[i - pos]; | |
Array.Copy(message, pos, sub, 0, i - pos); | |
list.Add(new FFXIVMap(sub)); | |
} | |
list.Add(map); | |
pos = i + len + 3; | |
i = pos - 1; | |
} | |
} | |
} | |
if (pos < message.Length) | |
{ | |
byte[] sub = new byte[message.Length - pos]; | |
Array.Copy(message, pos, sub, 0, message.Length - pos); | |
list.Add(new FFXIVMap(sub)); | |
} | |
return list; | |
} | |
public static byte[] Generate(int area, int mapFile, int x, int y, int z = -29999) | |
{ | |
using (var memory = new MemoryStream()) | |
{ | |
memory.WriteByte(0x02); | |
memory.WriteByte(0x2E); | |
memory.WriteByte(0x00); | |
memory.WriteByte(0xC9); | |
memory.WriteByte(0x04); | |
var barea = ConvertFromInt(area); | |
memory.Write(barea, 0, barea.Length); | |
var bmf = ConvertFromInt(mapFile); | |
memory.Write(bmf, 0, bmf.Length); | |
var bx = ConvertFromInt(x); | |
memory.Write(bx, 0, bx.Length); | |
var by = ConvertFromInt(y); | |
memory.Write(by, 0, by.Length); | |
var bz = ConvertFromInt(z); | |
memory.Write(bz, 0, bz.Length); | |
memory.WriteByte(0x03); | |
var t = memory.ToArray(); | |
t[2] = (byte)(t.Length - 3); | |
return t; | |
} | |
} | |
// AREA, MAPFILE, X, Y, Z | |
public static Tuple<int, int, int, int, int, int> ParseBytes(byte[] data) | |
{ | |
if (data.Length < 2) return null; | |
if (data[0] == 0x02 && data[1] == 0x2E) | |
{ | |
return ParsePacketBytes(data); | |
} else if (data[0] == 0x02 && data[1] == 0x27) | |
{ | |
return ParseMemoryBytes(data); | |
} | |
return null; | |
} | |
private static Tuple<int, int, int, int, int, int> ParsePacketBytes(byte[] data) | |
{ | |
int offset = 0; | |
if (data.Length < 2) return null; | |
if (data[offset++] != 0x02) return null; | |
if (data[offset++] != 0x2E) return null; | |
int length = data[offset++]; | |
if ((length + 2) >= data.Length || data[2 + length] != 0x03) return null; | |
if (data[offset++] != 0xC9) return null; | |
if (data[offset++] != 0x04) return null; | |
List<int> buf = new List<int>(); | |
while (offset < (2 + length)) | |
{ | |
int value = 0; | |
offset = ReadInt(data, offset, out value); //(int)(ConvertToInt(data, ++offset, size) + neg); | |
if (offset == -1) return null; | |
buf.Add(value); | |
} | |
return buf.Count != 5 ? null : Tuple.Create(length, buf[0], buf[1], buf[2], buf[3], buf[4]); | |
} | |
private static Tuple<int, int, int, int, int, int> ParseMemoryBytes(byte[] data) | |
{ | |
int offset = 0; | |
if (data.Length < 2) return null; | |
if (data[offset++] != 0x02) return null; | |
if (data[offset++] != 0x27) return null; | |
int length = data[offset++]; | |
if ((length + 2) >= data.Length || data[2 + length] != 0x03) return null; | |
if (data[offset++] != 0x04) return null; | |
if (data[offset++] != 0xFE) return null; | |
List<int> buf = new List<int>(); | |
buf.Add(ToInt16(data, offset)); offset += 2; //AREA | |
buf.Add(ToInt16(data, offset)); offset += 2; //MAPFILE | |
while (offset < (2 + length - 2)) | |
{ | |
int value = 0; | |
offset = ReadInt(data, offset, out value); //(int)(ConvertToInt(data, ++offset, size) + neg); | |
if (offset == -1) return null; | |
buf.Add(value); | |
} | |
return buf.Count < 4 ? null : Tuple.Create(length, buf[0], buf[1], buf[2], buf[3], buf.Count == 5 ? buf[4] : -29999); | |
} | |
public static int DebugReadInt(byte[] data, int offset = 0) | |
{ | |
int v = 0; | |
ReadInt(data, offset, out v); | |
return v; | |
} | |
public static short ToInt16(byte[] data, int offset) | |
{ | |
byte[] b = new byte[2]; | |
Array.Copy(data, offset, b, 0, 2); | |
Array.Reverse(b); | |
return BitConverter.ToInt16(b ,0); | |
} | |
// ?? HEAD ?? ?? ?? | |
public static int ReadInt(byte[] data, int offset, out int value) | |
{ | |
int length = 1; | |
long neg = 0; | |
if (data[offset] >= 0xF0) | |
{ | |
List<byte> flags = new List<byte> { 0xF0, 0xF2, 0xF6, 0xFE, 0xFC }; | |
if (flags.Contains(data[offset]) == false) | |
{ | |
value = 0; | |
return -1; | |
} | |
length = flags.IndexOf(data[offset]) + 1; | |
if (length >= 4) neg = -0xFFFFFFFF; | |
} | |
offset++; | |
int len = Math.Min(4, length); | |
byte[] b = new byte[len]; | |
if (length != 5) | |
{ | |
Array.Copy(data, offset, b, 0, len); | |
} | |
else | |
{ | |
b = new byte[] { data[offset], 0xFE, data[offset + 1], data[offset + 2] }; | |
len = 3; | |
} | |
byte[] a = new byte[4]; | |
Array.Reverse(b); | |
Array.Copy(b, a, b.Length); | |
value = (int)(BitConverter.ToInt32(a, 0) + neg); | |
offset += len; | |
return offset; | |
} | |
/* | |
public static byte[] StringToByteArray(string hex) | |
{ | |
hex = hex.Replace(" ", ""); | |
hex = hex.Replace("-", ""); | |
return Enumerable.Range(0, hex.Length) | |
.Where(x => x % 2 == 0) | |
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) | |
.ToArray(); | |
}*/ | |
/*public static int GetStrtoInt(string s) | |
{ | |
var data = s.Split(' '); | |
switch(data[0]) | |
{ | |
case "F0": | |
case "F2": | |
case "F6": //첫 바이트 제거후 변환 | |
return int.Parse(s.Replace(" ", "").Substring(2), System.Globalization.NumberStyles.AllowHexSpecifier); | |
case "FE": | |
return (int)(int.Parse(s.Replace(" ", "").Substring(2), System.Globalization.NumberStyles.AllowHexSpecifier) - 0xFFFFFFFF); | |
default: | |
return int.Parse(s.Replace(" ", ""), System.Globalization.NumberStyles.AllowHexSpecifier); | |
} | |
}*/ | |
public static byte[] ConvertFromInt(int value) | |
{ | |
var buf = new byte[8]; | |
byte[] a = BitConverter.GetBytes(value); | |
var length = TrimmedLength(a); | |
if (length > 3) a = BitConverter.GetBytes(value + 0xFFFFFFFF); | |
var i = 0; | |
for (i = 0; i < length; i++) buf[i] = a[i]; | |
var l = new byte[] { a[i], 0xF0, 0xF2, 0xF6, 0xFE }; | |
buf[i] = l[i]; | |
if (i == 0 && buf[0] == 0x00) buf[1] = l[1]; | |
Array.Reverse(buf); | |
while (buf[0] == 0x00 && buf.Length > 1) buf = buf.Skip(1).ToArray(); | |
return buf; | |
} | |
private static int TrimmedLength(byte[] data) | |
{ | |
var length = data.Length - 1; | |
for (length = data.Length - 1; length > 0 && data[length] == 0x00; length--) { } | |
return length + 1; | |
} | |
public static int ParseStringValue(string readableValue) | |
{ | |
var sp = readableValue.Split('.'); | |
return int.Parse(sp[0]) * 100 + int.Parse(sp[1]) * 10; | |
} | |
const int UNIT = 5000, PER = 10; | |
private static int ConvertToValue(int readableValue, int centerValue, int offset, int sizeFactor) | |
{ | |
return (readableValue - centerValue) * (UNIT * PER / 100) + (offset - UNIT + UNIT / 2); | |
} | |
private static int ConvertToReadable(int value, int centerValue, int offset) //, int sizeFactor) | |
{ | |
//var o = (value - (offset - UNIT + UNIT / 2)) / (UNIT * PER / 100) + centerValue; | |
//var o = (value - (offset - UNIT)) / (UNIT * PER / 100) + centerValue; | |
var o = (int)(((float)(value - (offset - UNIT)) / (UNIT * PER / 1000) + centerValue * 10) / 10); | |
//var v = (int)(GetCoordReadable(value, sizeFactor) * 100.0f); | |
//return (int)(v * 100.0); | |
//Debug.WriteLine("ConvertToReadable " + value + " -> " + o + ", " + v); | |
return o; | |
} | |
public static string GetReadableString(int readableValue) | |
{ | |
int b = (readableValue % 100) / 10; | |
int a = readableValue / 100; | |
return a + "." + b; | |
} | |
//Instance | |
//const string pattern = @"(?:(02 2E .. C9 04) (?<AREA>F2 .{5}|F0 ..|..) (?<MAPFILE>F2 .{5}|F0 ..|..) (?<POSX>FE .{11}|F6 .{8}|F2 .{5}|F0 ..|..) (?<POSY>FE .{11}|F6 .{8}|F2 .{5}|F0 ..|..) (?<POSZ>FE .{11}|F6 .{8}|F2 .{5}|F0 ..|..) 03)"; | |
public readonly byte[] data; | |
public readonly int length; | |
public readonly int area, mapFile; | |
public readonly int x, y, z; | |
public readonly bool no_z = false; | |
//네트워크 패킷 데이터에서 | |
public FFXIVMap(byte[] data) | |
{ | |
InitMapData(); | |
try | |
{ | |
this.data = data; | |
var t = ParseBytes(data); | |
if (t == null) | |
{ | |
this.length = 0; | |
return; | |
} | |
this.length = t.Item1; | |
this.area = t.Item2; | |
this.mapFile = t.Item3; | |
if (mapData.ContainsKey(area) == false) | |
{ | |
this.length = 0; | |
return; | |
} | |
MapData md = mapData[this.area]; | |
var needShiftArea = false; | |
if (md.mapFile != this.mapFile) | |
{ | |
if (mapData[this.area - 1].mapFile == this.mapFile - 1) | |
{ | |
needShiftArea = true; | |
} | |
} | |
else if (md.isARR) | |
{ | |
needShiftArea = true; | |
} | |
if (needShiftArea) | |
{ | |
this.area--; | |
this.mapFile--; | |
md = mapData[this.area]; | |
} | |
x = t.Item4; | |
y = t.Item5; | |
z = t.Item6; | |
if (z == -29999) no_z = true; | |
} | |
catch (Exception) | |
{ | |
this.length = 0; | |
} | |
} | |
//좌표 문자열에서 (텍스트에서) | |
public FFXIVMap(int area, string x, string y, string z = null) | |
{ | |
InitMapData(); | |
MapData md = mapData[area]; | |
this.area = area; | |
this.mapFile = md.mapFile; | |
int rx = ParseStringValue(x); | |
int ry = ParseStringValue(y); | |
int rz = 0; | |
if (z == null) | |
{ | |
no_z = true; | |
} else | |
{ | |
rz = ParseStringValue(z); | |
} | |
this.x = ConvertToValue(rx, md.center_x, md.offset_x, md.sizeFactor); | |
this.y = ConvertToValue(ry, md.center_y, md.offset_y, md.sizeFactor); | |
this.z = (int)(no_z ? -29999 : rz); | |
this.data = Generate(area, mapFile, this.x, this.y, this.z); | |
this.length = this.data[2]; | |
} | |
//메모리상 실제 위치 좌표 (레이더용) | |
public FFXIVMap(int area, int x, int y, int z) | |
{ | |
InitMapData(); | |
MapData md = mapData[area]; | |
this.area = area; | |
this.mapFile = md.mapFile; | |
this.x = x; | |
this.y = y; | |
this.z = z; | |
this.data = Generate(area, mapFile, this.x, this.y, this.z); | |
this.length = this.data[2]; | |
} | |
public bool isVaild() | |
{ | |
return this.length != 0; | |
} | |
public string getX() | |
{ | |
MapData md = mapData[area]; | |
return GetReadableString(ConvertToReadable(this.x, md.center_x, md.offset_x)); | |
} | |
public string getY() | |
{ | |
MapData md = mapData[area]; | |
return GetReadableString(ConvertToReadable(this.y, md.center_y, md.offset_y)); | |
} | |
public string getZ() | |
{ | |
return no_z ? null : GetReadableString(this.z); | |
} | |
override public string ToString() | |
{ | |
if (isVaild() == false) return base.ToString(); | |
MapData md = mapData[area]; | |
var ts = (md == null ? area + ":" + mapFile : md.name); | |
//ts += "(" + area + ", " + mapFile + ")"; | |
//기라바니아 호반지대( 8.5 , 21.4 ) Z: 0.5 | |
var z = getZ(); | |
z = z != null ? " Z: " + z : ""; | |
Debug.WriteLine("ToString: " + md.name + " area=" + area + " mapFile=" + md.mapFile + " sizeFacot=" + md.sizeFactor + " x=" + x + " y=" + y + " z=" + z); | |
return ts + " ( " + getX() + " , " + getY() + " )" + z; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment