diff --git a/FFXIVClassic Lobby Server/Database.cs b/FFXIVClassic Lobby Server/Database.cs index deb776b4..dce95d6b 100644 --- a/FFXIVClassic Lobby Server/Database.cs +++ b/FFXIVClassic Lobby Server/Database.cs @@ -231,15 +231,51 @@ namespace FFXIVClassic_Lobby_Server catch (MySqlException e) { Program.Log.Error(e.ToString()); - + conn.Dispose(); + return; + } + //Create Hotbar + try + { + MySqlCommand cmd = new MySqlCommand(); + cmd.Connection = conn; + cmd.CommandText = "SELECT id FROM server_battle_commands WHERE classJob = @classjob AND lvl = 1 ORDER BY id DESC"; + cmd.Prepare(); + + cmd.Parameters.AddWithValue("@classJob", charaInfo.currentClass); + List defaultActions = new List(); + using (var reader = cmd.ExecuteReader()) + { + while(reader.Read()) + { + defaultActions.Add(reader.GetUInt32("id")); + } + } + MySqlCommand cmd2 = new MySqlCommand(); + cmd2.Connection = conn; + cmd2.CommandText = "INSERT INTO characters_hotbar (characterId, classId, hotbarSlot, commandId, recastTime) VALUES (@characterId, @classId, @hotbarSlot, @commandId, 0)"; + cmd2.Prepare(); + cmd2.Parameters.AddWithValue("@characterId", cid); + cmd2.Parameters.AddWithValue("@classId", charaInfo.currentClass); + cmd2.Parameters.Add("@hotbarSlot", MySqlDbType.Int16); + cmd2.Parameters.Add("@commandId", MySqlDbType.Int16); + + for(int i = 0; i < defaultActions.Count; i++) + { + cmd2.Parameters["@hotbarSlot"].Value = i; + cmd2.Parameters["@commandId"].Value = defaultActions[i]; + cmd2.ExecuteNonQuery(); + } + } + catch(MySqlException e) + { + Program.Log.Error(e.ToString()); } finally { conn.Dispose(); } - - } Program.Log.Debug("[SQL] CID={0} state updated to active(2).", cid); diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index 643bda61..2aeb81f2 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -785,6 +785,62 @@ namespace FFXIVClassic_Map_Server } } + //Get class experience + query = @" + SELECT + pug, + gla, + mrd, + arc, + lnc, + + thm, + cnj, + + crp, + bsm, + arm, + gsm, + ltw, + wvr, + alc, + cul, + + min, + btn, + fsh + FROM characters_class_exp WHERE characterId = @charId"; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + player.charaWork.battleSave.skillPoint[Player.CLASSID_PUG - 1] = reader.GetInt16("pug"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_GLA - 1] = reader.GetInt16("gla"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_MRD - 1] = reader.GetInt16("mrd"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_ARC - 1] = reader.GetInt16("arc"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_LNC - 1] = reader.GetInt16("lnc"); + + player.charaWork.battleSave.skillPoint[Player.CLASSID_THM - 1] = reader.GetInt16("thm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_CNJ - 1] = reader.GetInt16("cnj"); + + player.charaWork.battleSave.skillPoint[Player.CLASSID_CRP - 1] = reader.GetInt16("crp"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_BSM - 1] = reader.GetInt16("bsm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_ARM - 1] = reader.GetInt16("arm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_GSM - 1] = reader.GetInt16("gsm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_LTW - 1] = reader.GetInt16("ltw"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_WVR - 1] = reader.GetInt16("wvr"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_ALC - 1] = reader.GetInt16("alc"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_CUL - 1] = reader.GetInt16("cul"); + + player.charaWork.battleSave.skillPoint[Player.CLASSID_MIN - 1] = reader.GetInt16("min"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_BTN - 1] = reader.GetInt16("btn"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_FSH - 1] = reader.GetInt16("fsh"); + } + } + //Load Saved Parameters query = @" SELECT @@ -1327,7 +1383,7 @@ namespace FFXIVClassic_Map_Server cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); - cmd.Parameters.AddWithValue("@classId", player.charaWork.parameterSave.state_mainSkill[0]); + cmd.Parameters.AddWithValue("@classId", player.GetCurrentClassOrJob()); player.charaWork.commandBorder = 32; @@ -2197,10 +2253,8 @@ namespace FFXIVClassic_Map_Server } } - public static void LoadGlobalBattleCommandList(Dictionary battleCommandDict, Dictionary, uint> battleCommandIdByLevel) + public static void LoadGlobalBattleCommandList(Dictionary battleCommandDict, Dictionary, List> battleCommandIdByLevel) { - //var battleCommands = new Dictionary(); - 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 @@ -2248,9 +2302,16 @@ namespace FFXIVClassic_Map_Server battleCommandDict.Add(id, battleCommand); - //Handle level 1 abilities separately because of FUCKING ARCHER REEE, just add those to the hotbar when the job is unlocked or on char creation, ez - if(battleCommand.level > 1) - battleCommandIdByLevel.Add(Tuple.Create(battleCommand.job, battleCommand.level), id | 0xA0F00000); + Tuple tuple = Tuple.Create(battleCommand.job, battleCommand.level); + if (battleCommandIdByLevel.ContainsKey(tuple)) + { + battleCommandIdByLevel[tuple].Add(id | 0xA0F00000); + } + else + { + List list = new List() { id | 0xA0F00000 }; + battleCommandIdByLevel.Add(tuple, list); + } } } } @@ -2264,6 +2325,72 @@ namespace FFXIVClassic_Map_Server } } } + + public static void SetExp(Player player, byte classId, int exp) + { + 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(); + + var query = String.Format(@" + UPDATE characters_class_exp + SET + {0} = @exp + WHERE + characterId = @characterId", CharacterUtils.GetClassNameForId(classId)); + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Prepare(); + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@characterId", player.actorId); + cmd.Parameters.AddWithValue("@exp", exp); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void SetLevel(Player player, byte classId, short level) + { + 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(); + + var query = String.Format(@" + UPDATE characters_class_levels + SET + {0} = @lvl + WHERE + characterId = @characterId", CharacterUtils.GetClassNameForId(classId)); + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Prepare(); + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@characterId", player.actorId); + cmd.Parameters.AddWithValue("@lvl", level); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } } } diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index 47a61226..2e88ee53 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -40,7 +40,7 @@ namespace FFXIVClassic_Map_Server private Dictionary currentPlayerParties = new Dictionary(); //GroupId, Party object private Dictionary statusEffectList = new Dictionary(); private Dictionary battleCommandList = new Dictionary(); - private Dictionary, uint> battleCommandIdByLevel = new Dictionary, uint>();//Holds battle command ids keyed by class id and level (in that order) + private Dictionary, List> battleCommandIdByLevel = new Dictionary, List>();//Holds battle command ids keyed by class id and level (in that order) private Dictionary battleNpcGenusMods = new Dictionary(); private Dictionary battleNpcPoolMods = new Dictionary(); private Dictionary battleNpcSpawnMods = new Dictionary(); @@ -1432,10 +1432,10 @@ namespace FFXIVClassic_Map_Server return battleCommandList.TryGetValue((ushort)id, out battleCommand) ? battleCommand.Clone() : null; } - public uint GetBattleCommandIdByLevel(byte classId, short level) + public List GetBattleCommandIdByLevel(byte classId, short level) { - uint id = 0; - return battleCommandIdByLevel.TryGetValue(Tuple.Create(classId, level), out id) ? id : 0; + List ids; + return battleCommandIdByLevel.TryGetValue(Tuple.Create(classId, level), out ids) ? ids : new List(); } } } diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index 8876c08f..4dca7286 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -387,13 +387,30 @@ namespace FFXIVClassic_Map_Server.Actors propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[0]"); propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[1]"); - + //Battle Save Skillpoint - + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_ALC - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_ARC - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_ARM - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_BSM - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_BTN - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_CNJ - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_CRP - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_CUL - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_FSH - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_GLA - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_GSM - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_LNC - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_LTW - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_MIN - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_MRD - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_PUG - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_THM - 1}]"); + propPacketUtil.AddProperty($"charaWork.battleSave.skillPoint[{CLASSID_WVR - 1}]"); + //Commands propPacketUtil.AddProperty("charaWork.commandBorder"); - for (int i = 0; i < charaWork.command.Length; i++) { if (charaWork.command[i] != 0) @@ -407,7 +424,6 @@ namespace FFXIVClassic_Map_Server.Actors } } } - for (int i = 0; i < charaWork.commandCategory.Length; i++) { @@ -421,7 +437,6 @@ namespace FFXIVClassic_Map_Server.Actors if (charaWork.commandAcquired[i] != false) propPacketUtil.AddProperty(String.Format("charaWork.commandAcquired[{0}]", i)); } - for (int i = 0; i < charaWork.additionalCommandAcquired.Length; i++) { @@ -436,13 +451,11 @@ namespace FFXIVClassic_Map_Server.Actors propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_compatibility[{0}]", i)); } - /* - for (int i = 0; i < charaWork.parameterSave.commandSlot_recastTime.Length; i++) - { - if (charaWork.parameterSave.commandSlot_recastTime[i] != 0) - propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_recastTime[{0}]", i)); - } - */ + for (int i = 0; i < charaWork.parameterSave.commandSlot_recastTime.Length; i++) + { + if (charaWork.parameterSave.commandSlot_recastTime[i] != 0) + propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_recastTime[{0}]", i)); + } //System propPacketUtil.AddProperty("charaWork.parameterTemp.forceControl_float_forClientSelf[0]"); @@ -2212,14 +2225,14 @@ namespace FFXIVClassic_Map_Server.Actors //Handles exp being added, does not handle figuring out exp bonus from buffs or skill/link chains or any of that public void AddExp(int exp, byte classId, int bonusPercent = 0) - { + { exp += (int) Math.Ceiling((exp * bonusPercent / 100.0f)); //You earn [exp](+[bonusPercent]%) experience point(s). SendGameMessage(this, Server.GetWorldManager().GetActor(), 33934, 0x44, this, 0, 0, 0, 0, 0, 0, 0, 0, 0, exp, "", bonusPercent); bool leveled = false; int diff = MAXEXP[GetLevel() - 1] - charaWork.battleSave.skillPoint[classId - 1]; - //While there is enough experience to level up, keep leveling up and unlocking skills and removing experience + //While there is enough experience to level up, keep leveling up, unlocking skills and removing experience from exp until we don't have enough to level up while (exp >= diff && GetLevel() < charaWork.battleSave.skillLevelCap[classId]) { //Level up @@ -2243,14 +2256,17 @@ namespace FFXIVClassic_Map_Server.Actors QueuePackets(expPropertyPacket3.Done()); //play levelup animation (do this outside LevelUp so that it only plays once if multiple levels are earned //also i dunno how to do this + + Database.SetLevel(this, classId, GetLevel()); } + //Cap experience for level 50 charaWork.battleSave.skillPoint[classId - 1] = Math.Min(charaWork.battleSave.skillPoint[classId - 1] + exp, MAXEXP[GetLevel() - 1]); ActorPropertyPacketUtil expPropertyPacket = new ActorPropertyPacketUtil("charaWork/battleStateForSelf", this); expPropertyPacket.AddProperty($"charaWork.battleSave.skillPoint[{classId - 1}]"); - //Cap experience for level 50 - QueuePackets(expPropertyPacket.Done()); + QueuePackets(expPropertyPacket.Done()); + Database.SetExp(this, classId, charaWork.battleSave.skillPoint[classId - 1]); } public void LevelUp(byte classId) @@ -2263,12 +2279,12 @@ namespace FFXIVClassic_Map_Server.Actors SendGameMessage(this, Server.GetWorldManager().GetActor(), 33909, 0x44, this, 0, 0, 0, 0, 0, 0, 0, 0, 0, (int) GetLevel()); //If there's an ability that unlocks at this level, equip it. - uint commandId = Server.GetWorldManager().GetBattleCommandIdByLevel(classId, GetLevel()); - if (commandId > 0) + List commandIds = Server.GetWorldManager().GetBattleCommandIdByLevel(classId, GetLevel()); + foreach(uint commandId in commandIds) { EquipAbilityInFirstOpenSlot(classId, commandId, false); byte jobId = ConvertClassIdToJobId(classId); - if (jobId != classId) + if (jobId != classId) EquipAbilityInFirstOpenSlot(jobId, commandId, false); } } @@ -2287,7 +2303,7 @@ namespace FFXIVClassic_Map_Server.Actors break; case CLASSID_ARC: case CLASSID_LNC: - jobId += 10; + jobId += 11; break; case CLASSID_THM: case CLASSID_CNJ: @@ -2297,5 +2313,20 @@ namespace FFXIVClassic_Map_Server.Actors return jobId; } + + public void SetCurrentJob(byte jobId) + { + currentJob = jobId; + BroadcastPacket(SetCurrentJobPacket.BuildPacket(actorId, jobId), true); + Database.LoadHotbar(this); + } + + public byte GetCurrentClassOrJob() + { + if (currentJob != 0) + return (byte) currentJob; + + return charaWork.parameterSave.state_mainSkill[0]; + } } } diff --git a/FFXIVClassic Map Server/utils/CharacterUtils.cs b/FFXIVClassic Map Server/utils/CharacterUtils.cs index 3df2bc6a..b420b5fa 100644 --- a/FFXIVClassic Map Server/utils/CharacterUtils.cs +++ b/FFXIVClassic Map Server/utils/CharacterUtils.cs @@ -96,5 +96,31 @@ namespace FFXIVClassic_Map_Server.utils } } + public static string GetClassNameForId(short id) + { + switch (id) + { + case 2: return "pug"; + case 3: return "gla"; + case 4: return "mrd"; + case 7: return "arc"; + case 8: return "lnc"; + case 22: return "thm"; + case 23: return "cnj"; + case 29: return "crp"; + case 30: return "bsm"; + case 31: return "arm"; + case 32: return "gsm"; + case 33: return "ltw"; + case 34: return "wvr"; + case 35: return "alc"; + case 36: return "cul"; + case 39: return "min"; + case 40: return "btn"; + case 41: return "fsh"; + default: return "undefined"; + } + } + } }