From 33f8709d765e3aa97a19cdd72f63a96ee99520ea Mon Sep 17 00:00:00 2001 From: yogurt Date: Wed, 27 Sep 2017 18:10:22 -0500 Subject: [PATCH] Added exp and leveling functions, redid equip ability functions to allow adding abilities that aren't for the player's current class and made functions more clear, added dictionary of BattleCommand ids indexed by a tuple containing classId and level. --- FFXIVClassic Map Server/Database.cs | 66 +++++- FFXIVClassic Map Server/WorldManager.cs | 9 +- .../actors/chara/BattleSave.cs | 2 +- .../actors/chara/CharaWork.cs | 2 +- .../actors/chara/player/Player.cs | 191 +++++++++++++++++- data/scripts/commands/EquipAbilityCommand.lua | 59 +++++- data/scripts/commands/gm/giveexp.lua | 34 ++++ 7 files changed, 333 insertions(+), 30 deletions(-) create mode 100644 data/scripts/commands/gm/giveexp.lua diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index 995cdf20..643bda61 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -1218,7 +1218,7 @@ namespace FFXIVClassic_Map_Server } } - public static void EquipAbility(Player player, ushort hotbarSlot, uint commandId, uint recastTime) + public static void EquipAbility(Player player, byte classId, ushort hotbarSlot, uint commandId, uint recastTime) { //2700083201 is where abilities start. 2700083200 is for unequipping abilities. Trying to put this in the hotbar will crash the game, need to put 0 instead if (commandId > 2700083200) @@ -1245,7 +1245,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", classId); cmd.Parameters.AddWithValue("@commandId", commandId); cmd.Parameters.AddWithValue("@hotbarSlot", hotbarSlot); cmd.Parameters.AddWithValue("@recastTime", recastTime); @@ -1358,6 +1358,57 @@ namespace FFXIVClassic_Map_Server } } + public static ushort FindFirstCommandSlot(Player player, byte classId) + { + ushort slot = 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))) + { + try + { + conn.Open(); + MySqlCommand cmd; + string query = ""; + + //Drop + List> hotbarList = new List>(); + query = @" + SELECT hotbarSlot + FROM characters_hotbar + WHERE characterId = @charId AND classId = @classId + ORDER BY hotbarSlot + "; + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@classId", classId); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + if (slot != reader.GetUInt16("hotbarSlot")) + return slot; + + slot++; + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + return slot; + } public static List GetInventory(Player player, uint slotOffset, uint type) { List items = new List(); @@ -2146,9 +2197,9 @@ namespace FFXIVClassic_Map_Server } } - public static Dictionary LoadGlobalBattleCommandList() + public static void LoadGlobalBattleCommandList(Dictionary battleCommandDict, Dictionary, uint> battleCommandIdByLevel) { - var battleCommands = new Dictionary(); + //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))) { @@ -2195,7 +2246,11 @@ namespace FFXIVClassic_Map_Server battleCommand.battleAnimation = reader.GetUInt32("battleAnimation"); battleCommand.validUser = (BattleCommandValidUser)reader.GetByte("validUser"); - battleCommands.Add(id, battleCommand); + 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); } } } @@ -2208,7 +2263,6 @@ namespace FFXIVClassic_Map_Server conn.Dispose(); } } - return battleCommands; } } diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index 065955f4..47a61226 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -40,6 +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 battleNpcGenusMods = new Dictionary(); private Dictionary battleNpcPoolMods = new Dictionary(); private Dictionary battleNpcSpawnMods = new Dictionary(); @@ -1422,7 +1423,7 @@ namespace FFXIVClassic_Map_Server public void LoadBattleCommands() { - battleCommandList = Database.LoadGlobalBattleCommandList(); + Database.LoadGlobalBattleCommandList(battleCommandList, battleCommandIdByLevel); } public BattleCommand GetBattleCommand(uint id) @@ -1430,5 +1431,11 @@ namespace FFXIVClassic_Map_Server BattleCommand battleCommand; return battleCommandList.TryGetValue((ushort)id, out battleCommand) ? battleCommand.Clone() : null; } + + public uint GetBattleCommandIdByLevel(byte classId, short level) + { + uint id = 0; + return battleCommandIdByLevel.TryGetValue(Tuple.Create(classId, level), out id) ? id : 0; + } } } diff --git a/FFXIVClassic Map Server/actors/chara/BattleSave.cs b/FFXIVClassic Map Server/actors/chara/BattleSave.cs index bf9a6000..8d2928e4 100644 --- a/FFXIVClassic Map Server/actors/chara/BattleSave.cs +++ b/FFXIVClassic Map Server/actors/chara/BattleSave.cs @@ -5,7 +5,7 @@ public float potencial = 6.6f; public short[] skillLevel = new short[52]; public short[] skillLevelCap = new short[52]; - public short[] skillPoint = new short[52]; + public int[] skillPoint = new int[52]; public short physicalLevel; public int physicalExp; diff --git a/FFXIVClassic Map Server/actors/chara/CharaWork.cs b/FFXIVClassic Map Server/actors/chara/CharaWork.cs index 652178d6..45afc964 100644 --- a/FFXIVClassic Map Server/actors/chara/CharaWork.cs +++ b/FFXIVClassic Map Server/actors/chara/CharaWork.cs @@ -23,7 +23,7 @@ public uint[] command = new uint[64]; //ACTORS public byte[] commandCategory = new byte[64]; public byte commandBorder = 0x20; - public bool[] commandAcquired = new bool[4096]; + public bool[] commandAcquired = new bool[4096]; public bool[] additionalCommandAcquired = new bool[36]; public uint currentContentGroup; diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index fab60a05..2c139abd 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -956,11 +956,11 @@ namespace FFXIVClassic_Map_Server.Actors //Calculate hp/mp //Get Potenciel ?????? - + //Set HP/MP/TP PARAMS //Set mainskill and level - + //Set Parameters //Set current EXP @@ -972,7 +972,6 @@ namespace FFXIVClassic_Map_Server.Actors //Check if bonus point available... set //Set rested EXP - charaWork.parameterSave.state_mainSkill[0] = classId; charaWork.parameterSave.state_mainSkillLevel = charaWork.battleSave.skillLevel[classId-1]; playerWork.restBonusExpRate = 0.0f; @@ -1846,7 +1845,89 @@ namespace FFXIVClassic_Map_Server.Actors QueuePackets(recastPacketUtil.Done()); } - public void EquipAbility(ushort hotbarSlot, ushort commandId) + //Find the first open slot in classId's hotbar and equip an ability there. + public void EquipAbilityInFirstOpenSlot(byte classId, uint commandId, bool printMessage = true) + { + //Find first open slot on class's hotbar slot, then call EquipAbility with that slot. + ushort hotbarSlot = 0; + + //If the class we're equipping for is the current class, we can just look at charawork.command + if(classId == charaWork.parameterSave.state_mainSkill[0]) + { + hotbarSlot = FindFirstCommandSlotById(0); + } + //Otherwise, we need to check the database. + else + { + hotbarSlot = (ushort) (Database.FindFirstCommandSlot(this, classId) + charaWork.commandBorder); + } + + EquipAbility(classId, commandId, hotbarSlot, printMessage); + } + + //Add commandId to classId's hotbar at hotbarSlot. + //If classId is not the current class, do it in the database + //hotbarSlot is 32-indexed + public void EquipAbility(byte classId, uint commandId, ushort hotbarSlot, bool printMessage = true) + { + var ability = Server.GetWorldManager().GetBattleCommand(commandId); + uint trueCommandId = commandId | 0xA0F00000; + ushort lowHotbarSlot = (ushort)(hotbarSlot - charaWork.commandBorder); + ushort maxRecastTime = (ushort)ability.recastTimeSeconds; + uint recastEnd = Utils.UnixTimeStampUTC() + maxRecastTime; + List slotsToUpdate = new List(); + + //If the class we're equipping for is the current class (need to find out if state_mainSkill is supposed to change when you're a job) + //then equip the ability in charawork.commands and save in databse, otherwise just save in database + if (classId == charaWork.parameterSave.state_mainSkill[0]) + { + charaWork.command[hotbarSlot] = trueCommandId; + charaWork.commandCategory[hotbarSlot] = 1; + charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot] = maxRecastTime; + charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot] = recastEnd; + + slotsToUpdate.Add(hotbarSlot); + UpdateHotbar(slotsToUpdate); + } + + Database.EquipAbility(this, classId, (ushort) (hotbarSlot - charaWork.commandBorder), commandId, recastEnd); + + if(printMessage) + SendGameMessage(Server.GetWorldManager().GetActor(), 30603, 0x20, 0, commandId); + } + + //Doesn't take a classId because the only way to swap abilities is through the ability equip widget oe /eaction, which only apply to current class + //hotbarSlot 1 and 2 are 32-indexed. + public void SwapAbilities(ushort hotbarSlot1, ushort hotbarSlot2) + { + uint lowHotbarSlot1 = (ushort)(hotbarSlot1 - charaWork.commandBorder); + uint lowHotbarSlot2 = (ushort)(hotbarSlot2 - charaWork.commandBorder); + + //Store information about first command + uint commandId = charaWork.command[hotbarSlot1]; + uint recastEnd = charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1]; + ushort recastMax = charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot1]; + + //Move second command's info to first hotbar slot + charaWork.command[hotbarSlot1] = charaWork.command[hotbarSlot2]; + charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot1] = charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot2]; + charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1] = charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2]; + + //Move first command's info to second slot + charaWork.command[hotbarSlot2] = commandId; + charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot2] = recastMax; + charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2] = recastEnd; + + //Save changes + Database.EquipAbility(this, charaWork.parameterSave.state_mainSkill[0], (ushort)(lowHotbarSlot1), charaWork.command[hotbarSlot1], charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1]); + + List slotsToUpdate = new List(); + slotsToUpdate.Add(hotbarSlot1); + slotsToUpdate.Add(hotbarSlot2); + UpdateHotbar(slotsToUpdate); + } + + public void EquipAbility(ushort hotbarSlot, uint commandId) { var ability = Server.GetWorldManager().GetBattleCommand(commandId); uint trueCommandId = commandId | 0xA0F00000; @@ -1881,7 +1962,7 @@ namespace FFXIVClassic_Map_Server.Actors charaWork.parameterTemp.maxCommandRecastTime[oldSlot - charaWork.commandBorder] = charaWork.parameterTemp.maxCommandRecastTime[trueHotbarSlot - charaWork.commandBorder]; charaWork.parameterSave.commandSlot_recastTime[oldSlot - charaWork.commandBorder] = charaWork.parameterSave.commandSlot_recastTime[trueHotbarSlot - charaWork.commandBorder]; //Save changes - Database.EquipAbility(this, (ushort)(oldSlot - charaWork.commandBorder), charaWork.command[oldSlot], charaWork.parameterSave.commandSlot_recastTime[oldSlot - charaWork.commandBorder]); + Database.EquipAbility(this, charaWork.parameterSave.state_mainSkill[0], (ushort)(oldSlot - charaWork.commandBorder), charaWork.command[oldSlot], charaWork.parameterSave.commandSlot_recastTime[oldSlot - charaWork.commandBorder]); slotsToUpdate.Add(oldSlot); } @@ -1897,11 +1978,11 @@ namespace FFXIVClassic_Map_Server.Actors charaWork.parameterSave.commandSlot_recastTime[trueHotbarSlot - charaWork.commandBorder] = recastEnd; slotsToUpdate.Add(trueHotbarSlot); - Database.EquipAbility(this, (ushort) (trueHotbarSlot - charaWork.commandBorder), trueCommandId, recastEnd); + Database.EquipAbility(this, charaWork.parameterSave.state_mainSkill[0], (ushort) (trueHotbarSlot - charaWork.commandBorder), trueCommandId, recastEnd); //"[Command] set." if (!isAlreadyEquipped) - SendGameMessage(Server.GetWorldManager().GetActor(), 30603, 0x20, 0, commandId); + SendGameMessage(Server.GetWorldManager().GetActor(), 30603, 0x20, 0, commandId ^ 0xA0F00000); } //Ability is already equipped else if (isAlreadyEquipped) @@ -1920,7 +2001,7 @@ namespace FFXIVClassic_Map_Server.Actors } - public void UnequipAbility(ushort hotbarSlot) + public void UnequipAbility(ushort hotbarSlot, bool printMessage = true) { List slotsToUpdate = new List(); ushort trueHotbarSlot = (ushort)(hotbarSlot + charaWork.commandBorder - 1); @@ -1928,14 +2009,16 @@ namespace FFXIVClassic_Map_Server.Actors Database.UnequipAbility(this, (ushort)(trueHotbarSlot - charaWork.commandBorder)); charaWork.command[trueHotbarSlot] = 0; slotsToUpdate.Add(trueHotbarSlot); - SendGameMessage(Server.GetWorldManager().GetActor(), 30604, 0x20, 0, commandId ^ 0xA0F00000); + + if(printMessage) + SendGameMessage(Server.GetWorldManager().GetActor(), 30604, 0x20, 0, commandId ^ 0xA0F00000); UpdateHotbar(slotsToUpdate); } //Finds the first hotbar slot with a given commandId. //If the returned value is outside the hotbar, it indicates it wasn't found. - private ushort FindFirstCommandSlotById(uint commandId) + public ushort FindFirstCommandSlotById(uint commandId) { if(commandId != 0) commandId |= 0xA0F00000; @@ -2200,5 +2283,93 @@ namespace FFXIVClassic_Map_Server.Actors // todo: this really shouldnt be called on each ws? lua.LuaEngine.CallLuaBattleFunction(this, "onWeaponSkill", this, state.GetTarget(), skill); } + + //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 (exp >= diff && GetLevel() < charaWork.battleSave.skillLevelCap[classId]) + { + //Level up + LevelUp(classId); + leveled = true; + //Reduce exp based on how much exp is needed to level + exp -= diff; + diff = MAXEXP[GetLevel() - 1]; + } + + if(leveled) + { + //Set exp to current class to 0 so that exp is added correctly + charaWork.battleSave.skillPoint[classId - 1] = 0; + //send new level + ActorPropertyPacketUtil expPropertyPacket2 = new ActorPropertyPacketUtil("charaWork/exp", this); + ActorPropertyPacketUtil expPropertyPacket3 = new ActorPropertyPacketUtil("charaWork/stateForAll", this); + expPropertyPacket2.AddProperty($"charaWork.battleSave.skillLevel[{classId - 1}]"); + expPropertyPacket2.AddProperty($"charaWork.parameterSave.state_mainSkillLevel"); + QueuePackets(expPropertyPacket2.Done()); + 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 + } + 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()); + } + + public void LevelUp(byte classId) + { + if (charaWork.battleSave.skillLevel[classId - 1] < charaWork.battleSave.skillLevelCap[classId]) + { + //Increase level + charaWork.battleSave.skillLevel[classId - 1]++; + charaWork.parameterSave.state_mainSkillLevel++; + + 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) + { + EquipAbilityInFirstOpenSlot(classId, commandId, false); + byte jobId = ConvertClassIdToJobId(classId); + if (jobId != classId) + EquipAbilityInFirstOpenSlot(jobId, commandId, false); + } + } + } + + public static byte ConvertClassIdToJobId(byte classId) + { + byte jobId = classId; + + switch(classId) + { + case CLASSID_PUG: + case CLASSID_GLA: + case CLASSID_MRD: + jobId += 13; + break; + case CLASSID_ARC: + case CLASSID_LNC: + jobId += 10; + break; + case CLASSID_THM: + case CLASSID_CNJ: + jobId += 4; + break; + } + + return jobId; + } } } diff --git a/data/scripts/commands/EquipAbilityCommand.lua b/data/scripts/commands/EquipAbilityCommand.lua index e11e5d27..6b2251d8 100644 --- a/data/scripts/commands/EquipAbilityCommand.lua +++ b/data/scripts/commands/EquipAbilityCommand.lua @@ -10,8 +10,10 @@ function onEventStarted(player, equipAbilityWidget, triggername, slot, commandid local worldManager = GetWorldManager(); local ability = worldManager:GetBattleCommand(commandid); + --Equip if (commandid > 0) then + --[[]] --Can the player equip any more cross class actions if (player.charaWork.parameterTemp.otherClassAbilityCount[0] >= player.charaWork.parameterTemp.otherClassAbilityCount[1]) then --"You cannot set any more actions." @@ -19,30 +21,65 @@ function onEventStarted(player, equipAbilityWidget, triggername, slot, commandid player:endEvent(); return; end - + --Is the player high enough level in that class to equip the ability - if (player.charaWork.battleSave.skillLevel[ability.job] < ability.level) then - - --"You have not yet acquired that action" + if (player.charaWork.battleSave.skillLevel[ability.job - 1] < ability.level) then + --"You have not yet acquired that action." player:SendGameMessage(GetWorldMaster(), 30742, 0x20, 0, 0); player:endEvent(); return; end - --Equip the ability - player:EquipAbility(slot, commandid); + + local oldSlot = player:FindFirstCommandSlotById(commandid); + local isEquipped = oldSlot < player.charaWork.commandBorder + 30; + --If slot is 0, find the first open slot + if (slot == 0) then + --If the ability is already equipped and slot is 0, then it can't be equipped again + --If the slot isn't 0, it's a move or a swap command + if (isEquipped == true) then + --"That action is already set to an action slot." + player:SendGameMessage(GetWorldMaster(), 30719, 0x20, 0); + player:endEvent(); + return; + end + + slot = player:FindFirstCommandSlotById(0) - player.charaWork.commandBorder; + + --If the first open slot is outside the hotbar, then the hotbar is full + if(slot >= 30) then + --"You cannot set any more actions." + player:SendGameMessage(Server.GetWorldManager().GetActor(), 30720, 0x20, 0); + player:endEvent(); + return; + end + else + slot = slot - 1; + end + + if(isEquipped == true) then + player:SwapAbilities(oldSlot, slot + player.charaWork.commandBorder); + else + local tslot = slot + player.charaWork.commandBorder; + player:EquipAbility(player.GetJob(), commandid, tslot, true); + end + --Unequip elseif (commandid == 0) then - commandid = player.charaWork.command[slot + player.charaWork.commandBorder]; - + commandid = player.charaWork.command[slot + player.charaWork.commandBorder - 1]; + ability = worldManager.GetBattleCommand(commandid); --Is the ability a part of the player's current class? --This check isn't correct because of jobs having different ids - if(worldManager:GetBattleCommand(commandid).job == player.charaWork.parameterSave.state_mainSkill[0]) then - --"Actions of your current class or job cannot be removed." - player:SendGameMessage(GetWorldMaster(), 30745, 0x20, 0, 0); + local classId = player:GetJob(); + local jobId = player:ConvertClassIdToJobId(classId); + + if(ability.job == classId or ability.job == jobId) then + --"Actions of your current class or job cannot be removed." + player:SendGameMessage(GetWorldMaster(), 30745, 0x20, 0, 0); elseif (commandid != 0) then player:UnequipAbility(slot); end end + player:endEvent(); end \ No newline at end of file diff --git a/data/scripts/commands/gm/giveexp.lua b/data/scripts/commands/gm/giveexp.lua new file mode 100644 index 00000000..d27366ca --- /dev/null +++ b/data/scripts/commands/gm/giveexp.lua @@ -0,0 +1,34 @@ +require("global"); + +properties = { + permissions = 0, + parameters = "sss", + description = +[[ +Adds experience to player or . +!giveexp | +!giveexp | +]], +} + +function onTrigger(player, argc, qty, name, lastName) + local sender = "[giveexp] "; + + if name then + if lastName then + player = GetWorldManager():GetPCInWorld(name.." "..lastName) or nil; + else + player = GetWorldManager():GetPCInWorld(name) or nil; + end; + end; + + if player then + currency = 1000001; + qty = tonumber(qty) or 1; + location = INVENTORY_CURRENCY; + + player:AddExp(qty, player.charaWork.parameterSave.state_mainSkill[0], 5); + else + print(sender.."unable to add experience, ensure player name is valid."); + end; +end; \ No newline at end of file