Skip to content

Instantly share code, notes, and snippets.

@RyuaNerin
Created January 6, 2018 05:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RyuaNerin/4651b2aaf593b74b5f86915d992cc440 to your computer and use it in GitHub Desktop.
Save RyuaNerin/4651b2aaf593b74b5f86915d992cc440 to your computer and use it in GitHub Desktop.
맵 좌표 계산 및 역산 클래스
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