From 62ed9b22f1b4000e802fdc1916a6c3a4288f5009 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Sun, 29 May 2016 15:14:09 -0400 Subject: [PATCH] Implemented actor instancing, as well as automatic name generation for NPCs. --- FFXIVClassic Map Server/Database.cs | 7 +- .../FFXIVClassic Map Server.csproj | 2 + FFXIVClassic Map Server/Server.cs | 4 +- FFXIVClassic Map Server/WorldManager.cs | 120 +++++++++--------- FFXIVClassic Map Server/actors/Actor.cs | 49 ++++++- FFXIVClassic Map Server/actors/area/Area.cs | 29 ++++- .../actors/area/PrivateArea.cs | 22 +++- .../actors/area/SpawnLocation.cs | 36 ++++++ FFXIVClassic Map Server/actors/area/Zone.cs | 42 +++++- .../actors/chara/npc/ActorClass.cs | 24 ++++ .../actors/chara/npc/Npc.cs | 15 ++- FFXIVClassic Map Server/common/Utils.cs | 10 ++ 12 files changed, 278 insertions(+), 82 deletions(-) create mode 100644 FFXIVClassic Map Server/actors/area/SpawnLocation.cs create mode 100644 FFXIVClassic Map Server/actors/chara/npc/ActorClass.cs diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index e9ecd342..2342574f 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -391,7 +391,6 @@ namespace FFXIVClassic_Lobby_Server birthMonth, initialTown, tribe, - currentParty, restBonus, achievementPoints, playTime @@ -421,9 +420,9 @@ namespace FFXIVClassic_Lobby_Server player.playerWork.birthdayMonth = reader.GetByte(14); player.playerWork.initialTown = reader.GetByte(15); player.playerWork.tribe = reader.GetByte(16); - player.playerWork.restBonusExpRate = reader.GetInt32(18); - player.achievementPoints = reader.GetUInt32(19); - player.playTime = reader.GetUInt32(20); + player.playerWork.restBonusExpRate = reader.GetInt32(17); + player.achievementPoints = reader.GetUInt32(18); + player.playTime = reader.GetUInt32(19); } } diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj index d409ff82..0746e1ca 100644 --- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj +++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj @@ -63,7 +63,9 @@ + + diff --git a/FFXIVClassic Map Server/Server.cs b/FFXIVClassic Map Server/Server.cs index f0057765..08f5e9fb 100644 --- a/FFXIVClassic Map Server/Server.cs +++ b/FFXIVClassic Map Server/Server.cs @@ -91,7 +91,9 @@ namespace FFXIVClassic_Lobby_Server mWorldManager = new WorldManager(this); mWorldManager.LoadZoneList(); mWorldManager.LoadZoneEntranceList(); - mWorldManager.LoadNPCs(); + mWorldManager.LoadActorClasses(); + mWorldManager.LoadSpawnLocations(); + mWorldManager.spawnAllActors(); IPEndPoint serverEndPoint = new System.Net.IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), FFXIV_MAP_PORT); diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index 7484ac22..0a729f99 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -1,6 +1,7 @@ using FFXIVClassic_Lobby_Server; using FFXIVClassic_Lobby_Server.common; using FFXIVClassic_Map_Server.actors.area; +using FFXIVClassic_Map_Server.actors.chara.npc; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.common.EfficientHashTables; using FFXIVClassic_Map_Server.dataobjects; @@ -24,6 +25,7 @@ namespace FFXIVClassic_Map_Server private WorldMaster worldMaster = new WorldMaster(); private Dictionary zoneList; private Dictionary zoneEntranceList; + private Dictionary actorClasses = new Dictionary(); private Server mServer; @@ -110,7 +112,7 @@ namespace FFXIVClassic_Map_Server if (zoneList.ContainsKey(parentZoneId)) { Zone parent = zoneList[parentZoneId]; - PrivateArea privArea = new PrivateArea(parent, reader.GetUInt32("id"), reader.GetString("className"), reader.GetString("privateAreaName"), reader.GetUInt16("dayMusic"), reader.GetUInt16("nightMusic"), reader.GetUInt16("battleMusic")); + PrivateArea privArea = new PrivateArea(parent, reader.GetUInt32("id"), reader.GetString("className"), reader.GetString("privateAreaName"), 1, reader.GetUInt16("dayMusic"), reader.GetUInt16("nightMusic"), reader.GetUInt16("battleMusic")); parent.addPrivateArea(privArea); } else @@ -182,7 +184,7 @@ namespace FFXIVClassic_Map_Server Log.info(String.Format("Loaded {0} zone spawn locations.", count)); } - public void LoadNPCs() + public void LoadActorClasses() { int count = 0; using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) @@ -194,20 +196,11 @@ namespace FFXIVClassic_Map_Server string query = @" SELECT id, - name, - zoneId, - positionX, - positionY, - positionZ, - rotation, - actorState, - animationId, + classPath, displayNameId, - customDisplayName, - actorClassName, eventConditions FROM gamedata_actor_class - WHERE name is not NULL AND zoneId > 0 + WHERE classPath <> '' AND eventConditions is not NULL "; MySqlCommand cmd = new MySqlCommand(query, conn); @@ -216,27 +209,14 @@ namespace FFXIVClassic_Map_Server { while (reader.Read()) { - string customName = null; - if (!reader.IsDBNull(10)) - customName = reader.GetString(10); + uint id = reader.GetUInt32("id"); + string classPath = reader.GetString("classPath"); + uint nameId = reader.GetUInt32("displayNameId"); + string eventConditions = reader.GetString("eventConditions"); - Npc npc = new Npc(reader.GetUInt32(0), reader.GetString(1), reader.GetUInt32(2), reader.GetFloat(3), reader.GetFloat(4), reader.GetFloat(5), reader.GetFloat(6), reader.GetUInt16(7), reader.GetUInt32(8), reader.GetUInt32(9), customName, reader.GetString(11)); - - if (!reader.IsDBNull(12)) - { - string eventConditions = reader.GetString(12); - npc.loadEventConditions(eventConditions); - } - - if (!zoneList.ContainsKey(npc.zoneId)) - continue; - Zone zone = zoneList[npc.zoneId]; - if (zone == null) - continue; - npc.zone = zone; - zone.addActorToZone(npc); + ActorClass actorClass = new ActorClass(id, classPath, nameId, eventConditions); + actorClasses.Add(id, actorClass); count++; - } } @@ -249,10 +229,10 @@ namespace FFXIVClassic_Map_Server } } - Log.info(String.Format("Loaded {0} npc(s).", count)); + Log.info(String.Format("Loaded {0} actor classes.", count)); } - public void LoadNPCs(uint zoneId) + public void LoadSpawnLocations() { int count = 0; using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) @@ -263,25 +243,21 @@ namespace FFXIVClassic_Map_Server string query = @" SELECT - id, - name, - zoneId, + actorClassId, + zoneId, + privateAreaName, + privateAreaLevel, positionX, positionY, positionZ, rotation, actorState, animationId, - displayNameId, - customDisplayName, - actorClassName, - eventConditions - FROM gamedata_actor_class - WHERE name is not NULL AND zoneId = @zoneId + customDisplayName + FROM server_spawn_locations "; MySqlCommand cmd = new MySqlCommand(query, conn); - cmd.Parameters.AddWithValue("@zoneId", zoneId); using (MySqlDataReader reader = cmd.ExecuteReader()) { @@ -289,28 +265,36 @@ namespace FFXIVClassic_Map_Server { string customName = null; if (!reader.IsDBNull(10)) - customName = reader.GetString(10); + customName = reader.GetString("customDisplayName"); - Npc npc = new Npc(reader.GetUInt32(0), reader.GetString(1), reader.GetUInt32(2), reader.GetFloat(3), reader.GetFloat(4), reader.GetFloat(5), reader.GetFloat(6), reader.GetUInt16(7), reader.GetUInt32(8), reader.GetUInt32(9), customName, reader.GetString(11)); + uint classId = reader.GetUInt32("actorClassId"); + uint zoneId = reader.GetUInt32("zoneId"); + string privAreaName = reader.GetString("privateAreaName"); + uint privAreaLevel = reader.GetUInt32("privateAreaLevel"); + float x = reader.GetFloat("positionX"); + float y = reader.GetFloat("positionY"); + float z = reader.GetFloat("positionZ"); + float rot = reader.GetFloat("rotation"); + ushort state = reader.GetUInt16("actorState"); + uint animId = reader.GetUInt32("animationId"); - if (!reader.IsDBNull(12)) - { - string eventConditions = reader.GetString(12); - npc.loadEventConditions(eventConditions); - } - - if (!zoneList.ContainsKey(npc.zoneId)) + if (!actorClasses.ContainsKey(classId)) continue; - Zone zone = zoneList[npc.zoneId]; + if (!zoneList.ContainsKey(zoneId)) + continue; + + Zone zone = zoneList[zoneId]; if (zone == null) continue; - npc.zone = zone; - zone.addActorToZone(npc); - count++; + SpawnLocation spawn = new SpawnLocation(classId, zoneId, privAreaName, privAreaLevel, x, y, z, rot, state, animId); + + zone.addSpawnLocation(spawn); + + count++; } } - + } catch (MySqlException e) { Console.WriteLine(e); } @@ -320,7 +304,14 @@ namespace FFXIVClassic_Map_Server } } - Log.info(String.Format("Loaded {0} npc(s).", count)); + Log.info(String.Format("Loaded {0} spawn(s).", count)); + } + + public void spawnAllActors() + { + Log.info("Spawning actors..."); + foreach (Zone z in zoneList.Values) + z.spawnAllActors(true); } //Moves the actor to the new zone if exists. No packets are sent nor position changed. @@ -473,7 +464,7 @@ namespace FFXIVClassic_Map_Server Zone zone = zoneList[zoneId]; //zone.clear(); - LoadNPCs(zone.actorId); + //LoadNPCs(zone.actorId); } @@ -556,6 +547,15 @@ namespace FFXIVClassic_Map_Server else return null; } + + public ActorClass GetActorClass(uint id) + { + if (actorClasses.ContainsKey(id)) + return actorClasses[id]; + else + return null; + } + } } diff --git a/FFXIVClassic Map Server/actors/Actor.cs b/FFXIVClassic Map Server/actors/Actor.cs index eab0e1fa..3f89c37e 100644 --- a/FFXIVClassic Map Server/actors/Actor.cs +++ b/FFXIVClassic Map Server/actors/Actor.cs @@ -18,7 +18,7 @@ using System.Text; namespace FFXIVClassic_Map_Server.Actors { class Actor - { + { public uint actorId; public string actorName; @@ -307,6 +307,53 @@ namespace FFXIVClassic_Map_Server.Actors SubPacket changeSpeedPacket = SetActorSpeedPacket.buildPacket(actorId, actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2]); zone.broadcastPacketAroundActor(this, changeSpeedPacket); } + + public void generateActorName(int actorNumber) + { + //Format Class Name + string className = this.className.Replace("Populace", "Ppl") + .Replace("Monster", "Mon") + .Replace("Crowd", "Crd") + .Replace("MapObj", "Map") + .Replace("Object", "Obj") + .Replace("Retainer", "Rtn") + .Replace("Standard", "Std"); + className = Char.ToLowerInvariant(className[0]) + className.Substring(1); + + //Format Zone Name + string zoneName = zone.zoneName.Replace("Field", "Fld") + .Replace("Dungeon", "Dgn") + .Replace("Town", "Twn") + .Replace("Battle", "Btl") + .Replace("Test", "Tes") + .Replace("Event", "Evt") + .Replace("Ship", "Shp") + .Replace("Office", "Ofc"); + if (zone is PrivateArea) + { + //Check if "normal" + zoneName = zoneName.Remove(zoneName.Length - 1, 1) + "P"; + } + zoneName = Char.ToLowerInvariant(zoneName[0]) + zoneName.Substring(1); + + try + { + className = className.Substring(0, 20 - zoneName.Length); + } + catch (ArgumentOutOfRangeException e) + {} + + //Convert actor number to base 63 + string classNumber = Utils.ToStringBase63(actorNumber); + + //Get stuff after @ + uint zoneId = zone.actorId; + uint privLevel = 0; + if (zone is PrivateArea) + privLevel = ((PrivateArea)zone).getPrivateAreaLevel(); + + actorName = String.Format("{0}_{1}_{2}@{3:X3}{4:X2}", className, zoneName, classNumber, zoneId, privLevel); + } } } diff --git a/FFXIVClassic Map Server/actors/area/Area.cs b/FFXIVClassic Map Server/actors/area/Area.cs index f43ff170..fb33b770 100644 --- a/FFXIVClassic Map Server/actors/area/Area.cs +++ b/FFXIVClassic Map Server/actors/area/Area.cs @@ -1,5 +1,8 @@ -using FFXIVClassic_Lobby_Server.common; +using FFXIVClassic_Lobby_Server; +using FFXIVClassic_Lobby_Server.common; using FFXIVClassic_Lobby_Server.packets; +using FFXIVClassic_Map_Server.actors.area; +using FFXIVClassic_Map_Server.actors.chara.npc; using FFXIVClassic_Map_Server.dataobjects; using FFXIVClassic_Map_Server.dataobjects.chara; using FFXIVClassic_Map_Server.lua; @@ -21,15 +24,16 @@ namespace FFXIVClassic_Map_Server.Actors public ushort weatherNormal, weatherCommon, weatherRare; public ushort bgmDay, bgmNight, bgmBattle; - private string classPath; + protected string classPath; public int boundingGridSize = 50; public int minX = -1000, minY = -1000, maxX = 1000, maxY = 1000; - private int numXBlocks, numYBlocks; - private int halfWidth, halfHeight; + protected int numXBlocks, numYBlocks; + protected int halfWidth, halfHeight; - private Dictionary mActorList = new Dictionary(); - private List[,] mActorBlock; + protected List mSpawnLocations = new List(); + protected Dictionary mActorList = new Dictionary(); + protected List[,] mActorBlock; Script areaScript; @@ -335,5 +339,18 @@ namespace FFXIVClassic_Map_Server.Actors } } + public void spawnActor(SpawnLocation location) + { + ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId); + + if (actorClass == null) + return; + + Npc npc = new Npc(mActorList.Count + 1, actorClass.actorClassId, actorId, location.x, location.y, location.z, location.rot, location.state, location.animId, actorClass.displayNameId, null, actorClass.classPath); + npc.loadEventConditions(actorClass.eventConditions); + + addActorToZone(npc); + } + } } diff --git a/FFXIVClassic Map Server/actors/area/PrivateArea.cs b/FFXIVClassic Map Server/actors/area/PrivateArea.cs index 3c6f6726..217786c7 100644 --- a/FFXIVClassic Map Server/actors/area/PrivateArea.cs +++ b/FFXIVClassic Map Server/actors/area/PrivateArea.cs @@ -14,12 +14,14 @@ namespace FFXIVClassic_Map_Server.actors.area { private Zone parentZone; private string privateAreaName; + private uint privateAreaLevel; - public PrivateArea(Zone parent, uint id, string className, string privateAreaName,ushort bgmDay, ushort bgmNight, ushort bgmBattle) + public PrivateArea(Zone parent, uint id, string className, string privateAreaName, uint privateAreaLevel, ushort bgmDay, ushort bgmNight, ushort bgmBattle) : base(id, parent.zoneName, parent.regionId, className, bgmDay, bgmNight, bgmBattle, parent.isIsolated, parent.isInn, parent.canRideChocobo, parent.canStealth, true) { this.parentZone = parent; this.privateAreaName = privateAreaName; + this.privateAreaLevel = privateAreaLevel; } public string getPrivateAreaName() @@ -27,6 +29,11 @@ namespace FFXIVClassic_Map_Server.actors.area return privateAreaName; } + public uint getPrivateAreaLevel() + { + return privateAreaLevel; + } + public Zone getParentZone() { return parentZone; @@ -45,6 +52,17 @@ namespace FFXIVClassic_Map_Server.actors.area ActorInstantiatePacket.buildPacket(actorId, playerActorId, actorName, className, lParams).debugPrintSubPacket(); return ActorInstantiatePacket.buildPacket(actorId, playerActorId, actorName, className, lParams); } - + + + public void addSpawnLocation(SpawnLocation spawn) + { + mSpawnLocations.Add(spawn); + } + + public void spawnAllActors() + { + foreach (SpawnLocation spawn in mSpawnLocations) + spawnActor(spawn); + } } } diff --git a/FFXIVClassic Map Server/actors/area/SpawnLocation.cs b/FFXIVClassic Map Server/actors/area/SpawnLocation.cs new file mode 100644 index 00000000..9e1fe67b --- /dev/null +++ b/FFXIVClassic Map Server/actors/area/SpawnLocation.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.area +{ + class SpawnLocation + { + public uint classId; + public uint zoneId; + public string privAreaName; + public uint privAreaLevel; + public float x; + public float y; + public float z; + public float rot; + public ushort state; + public uint animId; + + public SpawnLocation(uint classId, uint zoneId, string privAreaName, uint privAreaLevel, float x, float y, float z, float rot, ushort state, uint animId) + { + this.classId = classId; + this.zoneId = zoneId; + this.privAreaName = privAreaName; + this.privAreaLevel = privAreaLevel; + this.x = x; + this.y = y; + this.z = z; + this.rot = rot; + this.state = state; + this.animId = animId; + } + } +} diff --git a/FFXIVClassic Map Server/actors/area/Zone.cs b/FFXIVClassic Map Server/actors/area/Zone.cs index 90e716d2..fd3dcbd1 100644 --- a/FFXIVClassic Map Server/actors/area/Zone.cs +++ b/FFXIVClassic Map Server/actors/area/Zone.cs @@ -1,4 +1,7 @@ -using FFXIVClassic_Lobby_Server.packets; +using FFXIVClassic_Lobby_Server; +using FFXIVClassic_Lobby_Server.common; +using FFXIVClassic_Lobby_Server.packets; +using FFXIVClassic_Map_Server.actors.chara.npc; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.lua; using FFXIVClassic_Map_Server.packets.send.actor; @@ -11,7 +14,7 @@ using System.Threading.Tasks; namespace FFXIVClassic_Map_Server.actors.area { class Zone : Area - { + { Dictionary> privateAreas = new Dictionary>(); public Zone(uint id, string zoneName, ushort regionId, string className, ushort bgmDay, ushort bgmNight, ushort bgmBattle, bool isIsolated, bool isInn, bool canRideChocobo, bool canStealth, bool isInstanceRaid) @@ -54,5 +57,40 @@ namespace FFXIVClassic_Map_Server.actors.area return ActorInstantiatePacket.buildPacket(actorId, playerActorId, actorName, className, lParams); } + public void addSpawnLocation(SpawnLocation spawn) + { + //Is it in a private area? + if (!spawn.privAreaName.Equals("")) + { + if (privateAreas.ContainsKey(spawn.privAreaName)) + { + Dictionary levels = privateAreas[spawn.privAreaName]; + if (levels.ContainsKey(spawn.privAreaLevel)) + levels[spawn.privAreaLevel].addSpawnLocation(spawn); + else + Log.error(String.Format("Tried to add a spawn location to non-existing private area level \"{0}\" in area {1} in zone {2}", spawn.privAreaName, spawn.privAreaLevel, zoneName)); + } + else + Log.error(String.Format("Tried to add a spawn location to non-existing private area \"{0}\" in zone {1}", spawn.privAreaName, zoneName)); + } + else + mSpawnLocations.Add(spawn); + } + + public void spawnAllActors(bool doPrivAreas) + { + foreach (SpawnLocation spawn in mSpawnLocations) + spawnActor(spawn); + + if (doPrivAreas) + { + foreach (Dictionary areas in privateAreas.Values) + { + foreach (PrivateArea pa in areas.Values) + pa.spawnAllActors(); + } + } + } + } } diff --git a/FFXIVClassic Map Server/actors/chara/npc/ActorClass.cs b/FFXIVClassic Map Server/actors/chara/npc/ActorClass.cs new file mode 100644 index 00000000..6d3da091 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/npc/ActorClass.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.chara.npc +{ + class ActorClass + { + public readonly uint actorClassId; + public readonly string classPath; + public readonly uint displayNameId; + public readonly string eventConditions; + + public ActorClass(uint id, string classPath, uint nameId, string eventConditions) + { + this.actorClassId = id; + this.classPath = classPath; + this.displayNameId = nameId; + this.eventConditions = eventConditions; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs index 4ea2ed8a..69c3b02f 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs @@ -23,11 +23,9 @@ namespace FFXIVClassic_Map_Server.Actors public NpcWork npcWork = new NpcWork(); - public Npc(uint id, string actorName, uint zoneId, float posX, float posY, float posZ, float rot, ushort actorState, uint animationId, uint displayNameId, string customDisplayName, string className) - : base(id) + public Npc(int actorNumber, uint classId, uint zoneId, float posX, float posY, float posZ, float rot, ushort actorState, uint animationId, uint displayNameId, string customDisplayName, string classPath) + : base((4 << 28 | zoneId << 19 | (uint)actorNumber)) { - this.actorName = actorName; - this.actorClassId = id; this.positionX = posX; this.positionY = posY; this.positionZ = posZ; @@ -39,8 +37,11 @@ namespace FFXIVClassic_Map_Server.Actors this.customDisplayName = customDisplayName; this.zoneId = zoneId; + this.zone = Server.GetWorldManager().GetZone(zoneId); - loadNpcTemplate(id); + loadNpcAppearance(classId); + + className = classPath.Substring(classPath.LastIndexOf("/")+1); charaWork.battleSave.potencial = 1.0f; @@ -61,6 +62,8 @@ namespace FFXIVClassic_Map_Server.Actors charaWork.property[3] = 1; charaWork.property[4] = 1; + + generateActorName((int)actorNumber); } public SubPacket createAddActorPacket(uint playerActorId) @@ -156,7 +159,7 @@ namespace FFXIVClassic_Map_Server.Actors return actorClassId; } - public void loadNpcTemplate(uint id) + public void loadNpcAppearance(uint id) { using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) { diff --git a/FFXIVClassic Map Server/common/Utils.cs b/FFXIVClassic Map Server/common/Utils.cs index d66c188a..0fb85fa0 100644 --- a/FFXIVClassic Map Server/common/Utils.cs +++ b/FFXIVClassic Map Server/common/Utils.cs @@ -302,5 +302,15 @@ namespace FFXIVClassic_Lobby_Server.common return (value >> bits) | (value << (16 - bits)); } + + public static string ToStringBase63(int number) + { + string lookup = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + string secondDigit = lookup.Substring((int)Math.Floor((double)number / (double)lookup.Length), 1); + string firstDigit = lookup.Substring(number % lookup.Length, 1); + + return secondDigit + firstDigit; + } } }