/// <summary> /// Returns price and time to level up ability for character. /// </summary> /// <param name="character"></param> /// <param name="abilityData"></param> /// <param name="abilityTreeData"></param> /// <param name="level"></param> /// <param name="price"></param> /// <param name="time"></param> public static void Get(Character character, AbilityData abilityData, AbilityTreeData abilityTreeData, int abilityLevel, out int price, out int time) { price = 999999; time = 999999; // This is all a little temporary. I don't know if we might want // to use Lua for this, or maybe something else entirely. // If we leave it as C#, we might not want to use reflection, // or at least cache the methods, but for now it will work fine. var call = abilityTreeData.PriceTime; var match = CallRegex.Match(call); if (!match.Success) { Log.Warning("AbilityPriceTime.Get: Invalid unlock call '{0}'.", call); return; } var funcName = match.Groups["funcName"].Value; var abilityClassName = abilityData.ClassName; var maxLevel = abilityTreeData.MaxLevel; var method = typeof(AbilityPriceTime).GetMethod(funcName); if (method == null) { Log.Warning("AbilityPriceTime.Get: Unknown function '{0}'.", funcName); return; } var parameters = method.GetParameters(); if ( method.ReturnType != typeof(void) || parameters.Length != 6 || parameters[0].ParameterType != typeof(Character) || parameters[1].ParameterType != typeof(string) || parameters[2].ParameterType != typeof(int) || parameters[3].ParameterType != typeof(int) || parameters[4].ParameterType.GetElementType() != typeof(int) || !parameters[4].IsOut || parameters[5].ParameterType.GetElementType() != typeof(int) || !parameters[5].IsOut ) { Log.Warning("AbilityPriceTime.Get: Function '{0}' has an invalid signature.", funcName); return; } var funcParameters = new object[] { character, abilityClassName, abilityLevel, maxLevel, null, null }; method.Invoke(null, funcParameters); price = (int)funcParameters[4]; time = (int)funcParameters[5]; }
/// <summary> /// Returns true if given ability is unlocked for character. /// </summary> /// <param name="character"></param> /// <param name="abilityTreeData"></param> /// <returns></returns> public static bool IsUnlocked(Character character, AbilityData abilityData, AbilityTreeData abilityTreeData) { // This is all a little temporary. I don't know if we might want // to use Lua for this, or maybe something else entirely. // If we leave it as C#, we might not want to use reflection, // or at least cache the methods, but for now it will work fine. var call = abilityTreeData.Unlock; var match = UnlockCallRegex.Match(call); if (!match.Success) { Log.Warning("AbilityUnlock.IsUnlocked: Invalid unlock call '{0}'.", call); return(false); } var funcName = match.Groups["funcName"].Value; var strParam = match.Groups["strParam"].Value; var numParam = Convert.ToInt32(match.Groups["numParam"].Value); var method = typeof(AbilityUnlock).GetMethod(funcName); if (method == null) { Log.Warning("AbilityUnlock.IsUnlocked: Unknown function '{0}'.", funcName); return(false); } var parameters = method.GetParameters(); if ( method.ReturnType != typeof(bool) || parameters.Length != 4 || parameters[0].ParameterType != typeof(Character) || parameters[1].ParameterType != typeof(string) || parameters[2].ParameterType != typeof(int) || parameters[3].ParameterType != typeof(AbilityData) ) { Log.Warning("AbilityUnlock.IsUnlocked: Function '{0}' has an invalid signature.", funcName); return(false); } var result = (bool)method.Invoke(null, new object[] { character, strParam, numParam, abilityData }); return(result); }
/// <summary> /// Official slash command to learn abilities. /// </summary> /// <param name="conn"></param> /// <param name="sender"></param> /// <param name="target"></param> /// <param name="command"></param> /// <param name="args"></param> /// <returns></returns> private CommandResult HandleLearnPcAbil(ChannelConnection conn, Character sender, Character target, string command, string[] args) { // Since this command is sent via UI interactions, we'll not // use any automated command result messages, but we'll leave // debug messages for now, in case of unexpected values. if (args.Length != 3 || !int.TryParse(args[2], out var levels) || levels < 1) { Log.Debug("HandleLearnPcAbil: Invalid call by user '{0}': {1}", conn.Account.Name, command); return(CommandResult.Okay); } var className = args[1]; var abilityData = ChannelServer.Instance.Data.AbilityDb.Find(className); if (abilityData == null) { Log.Debug("HandleLearnPcAbil: User '{0}' tried to learn non-existent ability '{1}'.", conn.Account.Name, className); return(CommandResult.Okay); } // All we get here is the ability name, but whether it can // be learned or not potentially depends on any of the job's // ability tree's entries. We have to check whether the ability // can be learned by any of the character's jobs. var abilityId = abilityData.Id; var canLearn = false; var jobs = sender.Jobs.GetList(); AbilityTreeData abilityTreeData = null; foreach (var job in jobs) { // An ability can be learned by a job if there's an entry // for it in the tree and an unlock condition is given. var jobAbilityTreeData = ChannelServer.Instance.Data.AbilityTreeDb.Find(job.Id, abilityId); if (jobAbilityTreeData != null && jobAbilityTreeData.HasUnlock) { var unlocked = AbilityUnlock.IsUnlocked(sender, abilityData, jobAbilityTreeData); if (unlocked) { canLearn = true; abilityTreeData = jobAbilityTreeData; break; } } } if (!canLearn) { Log.Debug("HandleLearnPcAbil: User '{0}' tried to learn ability '{1}', which they can't learn (yet).", conn.Account.Name, className); return(CommandResult.Okay); } var ability = sender.Abilities.Get(abilityId); var currentLevel = (ability == null ? 0 : ability.Level); var newLevel = (currentLevel + levels); var maxLevel = abilityTreeData.MaxLevel; if (newLevel > maxLevel) { Log.Debug("HandleLearnPcAbil: User '{0}' tried to increase ability '{1}'s level past the max level of {2}.", conn.Account.Name, className, maxLevel); return(CommandResult.Okay); } // Price and time can come either from the actual values, // or from functions that return both. var price = abilityTreeData.Price; var time = abilityTreeData.Time; if (abilityTreeData.HasPriceTime) { price = 0; for (var i = currentLevel + 1; i <= newLevel; ++i) { AbilityPriceTime.Get(sender, abilityData, abilityTreeData, i, out var addPrice, out time); price += addPrice; } } var points = sender.AbilityPoints; if (points < price) { Log.Debug("HandleLearnPcAbil: User '{0}' didn't have enough points.", conn.Account.Name); return(CommandResult.Okay); } //Log.Debug("Learn: {0}", abilityData.EngName); //Log.Debug("- From: {0}", currentLevel); //Log.Debug("- To: {0}", newLevel); //Log.Debug("- Price: {0}", price); //Log.Debug("- Time: {0}", time); // Add ability if character doesn't have it yet if (ability == null) { ability = new Ability(abilityId, 0); sender.Abilities.Add(ability); } // Update ability ability.Level += levels; Send.ZC_OBJECT_PROPERTY(sender.Connection, ability); sender.ModifyAbilityPoints(-price); Send.ZC_ADDON_MSG(sender, AddonMessage.RESET_ABILITY_UP, "Ability_" + abilityTreeData.Category); Send.ZC_ADDON_MSG(sender, AddonMessage.SUCCESS_LEARN_ABILITY, abilityTreeData.Category); return(CommandResult.Okay); }