diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj index 321f8949..eb57d7d8 100644 --- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj +++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj @@ -289,6 +289,7 @@ + diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index 17c034a8..897a0ab1 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -708,6 +708,12 @@ namespace FFXIVClassic_Map_Server Server.GetWorldConnection().QueuePacket(packet, true, false); } + public void RequestWorldLinkshellChangeActive(Player player, string lsname) + { + SubPacket packet = LinkshellChangePacket.BuildPacket(player.playerSession, lsname); + Server.GetWorldConnection().QueuePacket(packet, true, false); + } + private void RequestWorldServerZoneChange(Player player, uint destinationZoneId, byte spawnType, float spawnX, float spawnY, float spawnZ, float spawnRotation) { ZoneConnection zc = Server.GetWorldConnection(); diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellChangePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellChangePacket.cs new file mode 100644 index 00000000..ec256612 --- /dev/null +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellChangePacket.cs @@ -0,0 +1,30 @@ +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.dataobjects; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group +{ + class LinkshellChangePacket + { + public const ushort OPCODE = 0x1028; + public const uint PACKET_SIZE = 0x48; + + public static SubPacket BuildPacket(Session session, string lsName) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Write(Encoding.ASCII.GetBytes(lsName), 0, Encoding.ASCII.GetByteCount(lsName) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(lsName)); + } + } + return new SubPacket(true, OPCODE, session.id, session.id, data); + } + + } +} diff --git a/FFXIVClassic World Server/DataObjects/Session.cs b/FFXIVClassic World Server/DataObjects/Session.cs index d90a3f1d..966d3248 100644 --- a/FFXIVClassic World Server/DataObjects/Session.cs +++ b/FFXIVClassic World Server/DataObjects/Session.cs @@ -1,4 +1,7 @@ -using FFXIVClassic_World_Server.Packets.Send.Subpackets; +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects.Group; +using FFXIVClassic_World_Server.Packets.Send.Subpackets; +using FFXIVClassic_World_Server.Packets.Send.Subpackets.Groups; using System; using System.Collections.Generic; using System.Linq; @@ -16,7 +19,7 @@ namespace FFXIVClassic_World_Server.DataObjects public string characterName; public uint currentZoneId; - public uint activeLinkshellIndex = 0; + public string activeLinkshellName = ""; public readonly ClientConnection clientConnection; public readonly Channel type; @@ -28,7 +31,7 @@ namespace FFXIVClassic_World_Server.DataObjects this.clientConnection = connection; this.type = type; connection.owner = this; - Database.LoadZoneSessionInfo(this); + Database.LoadZoneSessionInfo(this); } public void SendGameMessage(uint actorId, ushort textId, byte log, params object[] msgParams) @@ -65,5 +68,15 @@ namespace FFXIVClassic_World_Server.DataObjects clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, 0x5FF80001, textId, displayId, log, LuaUtils.CreateLuaParamList(msgParams)), true, false); } + + public bool SetActiveLS(string name) + { + if (Database.SetActiveLS(this, name)) + { + activeLinkshellName = name; + return true; + } + return false; + } } } diff --git a/FFXIVClassic World Server/Database.cs b/FFXIVClassic World Server/Database.cs index e5a3993c..0a63ac4e 100644 --- a/FFXIVClassic World Server/Database.cs +++ b/FFXIVClassic World Server/Database.cs @@ -45,7 +45,7 @@ namespace FFXIVClassic_World_Server public static bool LoadZoneSessionInfo(Session session) { - string characterName; + string characterName, currentLinkshell; uint currentZone = 0; uint destinationZone = 0; bool readIn = false; @@ -55,7 +55,7 @@ namespace FFXIVClassic_World_Server try { conn.Open(); - MySqlCommand cmd = new MySqlCommand("SELECT name, currentZoneId, destinationZoneId FROM characters WHERE id = @charaId", conn); + MySqlCommand cmd = new MySqlCommand("SELECT name, currentZoneId, destinationZoneId, currentActiveLinkshell FROM characters WHERE id = @charaId", conn); cmd.Parameters.AddWithValue("@charaId", session.sessionId); using (MySqlDataReader Reader = cmd.ExecuteReader()) { @@ -64,9 +64,11 @@ namespace FFXIVClassic_World_Server characterName = Reader.GetString("name"); currentZone = Reader.GetUInt32("currentZoneId"); destinationZone = Reader.GetUInt32("destinationZoneId"); + currentLinkshell = Reader.GetString("currentActiveLinkshell"); session.characterName = characterName; session.currentZoneId = currentZone; + session.activeLinkshellName = currentLinkshell; readIn = true; } @@ -501,5 +503,31 @@ namespace FFXIVClassic_World_Server } return success; } + + public static bool SetActiveLS(Session session, string name) + { + bool success = false; + 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))) + { + try + { + conn.Open(); + MySqlCommand cmd = new MySqlCommand("UPDATE characters SET currentActiveLinkshell = @lsName WHERE id = @charaId", conn); + cmd.Parameters.AddWithValue("@charaId", session.sessionId); + cmd.Parameters.AddWithValue("@lsName", name); + cmd.ExecuteNonQuery(); + success = true; + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + return success; + } } } diff --git a/FFXIVClassic World Server/FFXIVClassic World Server.csproj b/FFXIVClassic World Server/FFXIVClassic World Server.csproj index 0d94415c..995f8ba5 100644 --- a/FFXIVClassic World Server/FFXIVClassic World Server.csproj +++ b/FFXIVClassic World Server/FFXIVClassic World Server.csproj @@ -112,6 +112,7 @@ + @@ -128,12 +129,12 @@ + - diff --git a/FFXIVClassic World Server/Packets/Send/Subpackets/Groups/SetActiveLinkshellPacket.cs b/FFXIVClassic World Server/Packets/Send/Subpackets/Groups/SetActiveLinkshellPacket.cs new file mode 100644 index 00000000..8c21a21b --- /dev/null +++ b/FFXIVClassic World Server/Packets/Send/Subpackets/Groups/SetActiveLinkshellPacket.cs @@ -0,0 +1,42 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.DataObjects.Group; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.Packets.Send.Subpackets.Groups +{ + class SetActiveLinkshellPacket + { + + public const ushort OPCODE = 0x018A; + public const uint PACKET_SIZE = 0x98; + + public static SubPacket BuildPacket(uint sessionId, ulong groupId) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + //Write the LS groupId + binWriter.Write((UInt64)groupId); + + //Write the LS group type + binWriter.Seek(0x40, SeekOrigin.Begin); + binWriter.Write((UInt32)Group.CompanyGroup); + + //Seems to be a index but can only set one active so /shrug + binWriter.Seek(0x60, SeekOrigin.Begin); + binWriter.Write((UInt32)(groupId == 0 ? 0 : 1)); + } + } + + return new SubPacket(OPCODE, sessionId, sessionId, data); + } + } +} diff --git a/FFXIVClassic World Server/Packets/WorldPackets/Receive/Group/SetActiveLinkshellPacket.cs b/FFXIVClassic World Server/Packets/WorldPackets/Receive/Group/LinkshellChangePacket.cs similarity index 72% rename from FFXIVClassic World Server/Packets/WorldPackets/Receive/Group/SetActiveLinkshellPacket.cs rename to FFXIVClassic World Server/Packets/WorldPackets/Receive/Group/LinkshellChangePacket.cs index 2f689b2c..99b8bd7f 100644 --- a/FFXIVClassic World Server/Packets/WorldPackets/Receive/Group/SetActiveLinkshellPacket.cs +++ b/FFXIVClassic World Server/Packets/WorldPackets/Receive/Group/LinkshellChangePacket.cs @@ -5,13 +5,13 @@ using System.Text; namespace FFXIVClassic_World_Server.Packets.WorldPackets.Receive.Group { - class SetActiveLinkshellPacket + class LinkshellChangePacket { public bool invalidPacket = false; + + public string lsName; - public string name; - - public SetActiveLinkshellPacket(byte[] data) + public LinkshellChangePacket(byte[] data) { using (MemoryStream mem = new MemoryStream(data)) { @@ -19,7 +19,7 @@ namespace FFXIVClassic_World_Server.Packets.WorldPackets.Receive.Group { try { - name = Encoding.ASCII.GetString(binReader.ReadBytes(0x20)).Trim(new[] { '\0' }); + lsName = Encoding.ASCII.GetString(binReader.ReadBytes(0x20)).Trim(new[] { '\0' }); } catch (Exception) { diff --git a/FFXIVClassic World Server/Server.cs b/FFXIVClassic World Server/Server.cs index 642bc604..557ed80a 100644 --- a/FFXIVClassic World Server/Server.cs +++ b/FFXIVClassic World Server/Server.cs @@ -302,8 +302,8 @@ namespace FFXIVClassic_World_Server break; //Linkshell set active case 0x1028: - SetActiveLinkshellPacket setActiveLinkshellPacket = new SetActiveLinkshellPacket(subpacket.data); - //Linkshell ls = mWorldManager.GetLinkshellManager().GetLinkshell(); + LinkshellChangePacket linkshellChangePacket = new LinkshellChangePacket(subpacket.data); + mWorldManager.ProcessLinkshellSetActive(GetSession(subpacket.header.sourceId), linkshellChangePacket.lsName); break; //Linkshell invite member case 0x1029: diff --git a/FFXIVClassic World Server/WorldMaster.cs b/FFXIVClassic World Server/WorldMaster.cs index 0e588559..01cdddab 100644 --- a/FFXIVClassic World Server/WorldMaster.cs +++ b/FFXIVClassic World Server/WorldMaster.cs @@ -219,7 +219,25 @@ namespace FFXIVClassic_World_Server mRetainerGroupManager.GetRetainerGroup(session.sessionId).SendGroupPackets(session); List linkshells = mLinkshellManager.GetPlayerLinkshellMembership(session.sessionId); foreach (Linkshell ls in linkshells) - ls.SendGroupPackets(session); + ls.SendGroupPackets(session); + + //Reset to blank if in unknown state + ulong activeGroupIndex = 0; + if (!session.activeLinkshellName.Equals("")) + { + Linkshell activeLs = mLinkshellManager.GetLinkshell(session.activeLinkshellName); + if (activeLs != null && activeLs.HasMember(session.sessionId)) + { + activeGroupIndex = activeLs.groupIndex; + } + else + { + session.activeLinkshellName = ""; + Database.SetActiveLS(session, ""); + } + } + SubPacket activeLsPacket = SetActiveLinkshellPacket.BuildPacket(session.sessionId, activeGroupIndex); + session.clientConnection.QueuePacket(activeLsPacket, true, false); } private void SendMotD(Session session) @@ -408,6 +426,34 @@ namespace FFXIVClassic_World_Server relation.SendDeletePackets(inviterSession.sessionId, inviteeSession.sessionId); } + public void ProcessLinkshellSetActive(Session requestSession, string lsName) + { + //Deactivate all + if (lsName.Equals("")) + { + requestSession.SetActiveLS(lsName); + SubPacket activeLsPacket = SetActiveLinkshellPacket.BuildPacket(requestSession.sessionId, 0); + requestSession.clientConnection.QueuePacket(activeLsPacket, true, false); + requestSession.SendGameMessage(25132, 0x20, (object)1); + } + else + { + Linkshell ls = mLinkshellManager.GetLinkshell(lsName); + + if (ls == null || !ls.HasMember(requestSession.sessionId)) + { + requestSession.SendGameMessage(25167, 0x20, (object)1, (object)lsName); + } + else + { + requestSession.SetActiveLS(lsName); + SubPacket activeLsPacket = SetActiveLinkshellPacket.BuildPacket(requestSession.sessionId, ls.groupIndex); + requestSession.clientConnection.QueuePacket(activeLsPacket, true, false); + requestSession.SendGameMessage(25131, 0x20, (object)1, (object)lsName); + } + } + } + public void IncrementGroupIndex() { mRunningGroupIndex++; diff --git a/data/scripts/commands/LinkshellChangeCommand.lua b/data/scripts/commands/LinkshellChangeCommand.lua new file mode 100644 index 00000000..25022775 --- /dev/null +++ b/data/scripts/commands/LinkshellChangeCommand.lua @@ -0,0 +1,15 @@ +--[[ + +LinkshellChangeCommand Script + +--]] + +function onEventStarted(player, actor, triggerName, linkshellName, arg1, arg2) + + if (linkshellName == nil) then + linkshellName = ""; + end + GetWorldManager():RequestWorldLinkshellChangeActive(player, linkshellName); + player:EndEvent(); + +end \ No newline at end of file