--[[ Illarion Server This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ]] local common = require("base.common") local restTime = {} local storedMessage = {} local storedPosition = {} local M = {} -- Called by the server when user:learn(...) is issued by a script --[[Call: Character:learn(skill, actionPoints, learnLimit); skill: Name of the skill as skill key, e.g. Character.mining. actionPoints: The amount of actionPoints or time (1/10s), required by the action, as integer. Do NOT fill in 0, every action relevant for skillgain HAS TO take some time. learnLimit: Maximum skill level the user can reach with the triggering action.]] --Learning speed - Change here if you're unhappy with the learning speed. Skillgain scales in a linear way. local scalingFactor = 500 --Here, you can mod the learning speed. Higher value=faster. --It is the minor skill increase for an action with skill = 0 and all other parameters nominal as per definitions below. --10000 minor skill points make a major skill point. --Constants - Do not change unless you know exactly what you're doing! local amplification = 100 --An 'abritrary' value that governs the 'resolution' of the MC function. local damping = 0.0001 --This value determines how fast the MC system reacts on changed player behaviour. local lowerBorder = 2.5 * amplification / damping --below 2.5 % of time spent online, no additional bonus is granted. local normalMC = 4 * lowerBorder --A 'normal' player invests 4x the time (=10 %) into skill related actions. local normalAP = 50 --How many actionPoints does a 'normal' action take? Default=50. --Constants - end local multiSkilling = {} local function accountForMultiSkills(user, skill, actionPoints) if not multiSkilling[user.id] then multiSkilling[user.id] = {} end -- The purpose of this script is to replace the former division of actionPoints by 3 in fighting that accounted for how you -- might be skilling parry ,armour and weapon at the same time, with a dynamic scaling and to include how you can now also -- be skilling magicResist at the same time local armour = {Character.heavyArmour, Character.mediumArmour, Character.lightArmour} local parry = Character.parry local offensive = {Character.distanceWeapons, Character.slashingWeapons, Character.wrestling, Character.concussionWeapons, Character.punctureWeapons, Character.fireMagic, Character.spiritMagic, Character.windMagic, Character.earthMagic, Character.waterMagic} local magicResist = Character.magicResistance local correctSkill = false local resetTimer = 5 for _, armourSkill in pairs(armour) do if skill == armourSkill then correctSkill = true multiSkilling[user.id]["armour"] = world:getTime("unix") + resetTimer end end if skill == parry then correctSkill = true multiSkilling[user.id]["parry"] = world:getTime("unix") + resetTimer end if skill == magicResist then correctSkill = true multiSkilling[user.id]["magicResist"] = world:getTime("unix") + resetTimer end for _, offensiveSkill in pairs(offensive) do if skill == offensiveSkill then correctSkill = true multiSkilling[user.id]["offensive"] = world:getTime("unix") + resetTimer end end if not correctSkill then return actionPoints end local divideBy = 0 local timesToCheck = {"offensive", "magicResist", "parry", "armour"} for _, timeCheck in pairs(timesToCheck) do local time = multiSkilling[user.id][timeCheck] if not common.IsNilOrEmpty(time) then if time > world:getTime("unix") then --This skill category was learnt within that last 5 seconds divideBy = divideBy + 1 end end end return actionPoints/divideBy end local accumulatedExperience = {} function M.learn(user, skill, actionPoints, learnLimit) local leadAttrib = common.GetLeadAttrib(user, skill) --reading the lead attribute. local skillValue = user:getSkill(skill) --reading the skill points. local minorSkill = user:getMinorSkill(skill) --reading the minor skill points; 10000=1 skill point. local MCvalue = math.max(lowerBorder, user:getMentalCapacity()) --below 2.5% of time spent online, no additional bonus is granted. M.resetRestTime(user) if skillValue < learnLimit and skillValue < 100 then actionPoints = accountForMultiSkills(user, skill, actionPoints) --After the check to ensure level 100 skills dont add to it --you only learn when your skill is lower than the skill of the learnLimit and your skill is <100. local skillFactor = (100 - skillValue)/100 local MCfactor = normalMC / math.max(MCvalue, 1) --10 % of time spent online is considered "normal" -> MCfactor is 1. local attributeBonus = common.GetAttributeBonusHigh(leadAttrib) --+/-0.5 depending on attribute, limited to 1. local intelligenceBonus = common.GetAttributeBonusLow(user:increaseAttrib("intelligence", 0)) --+/-0.1, depending on attribute, limited to 0.2. local actionpointFactor = actionPoints / normalAP --An action with 50 AP is "normal". local minorIncrease = scalingFactor * skillFactor * MCfactor * actionpointFactor * (1 + attributeBonus + intelligenceBonus) if not accumulatedExperience[user.id] then accumulatedExperience[user.id] = {} end if accumulatedExperience[user.id][skill] then minorIncrease = minorIncrease + accumulatedExperience[user.id][skill] end local excess = minorIncrease-math.floor(minorIncrease) -- Skill points only work in whole integers, so we store the decimal for later instead of relying on RNG accumulatedExperience[user.id][skill] = excess minorIncrease = math.floor(minorIncrease) --The whole integer we will increase by --For debugging, use the following line. --[[user:inform("Skill="..user:getSkillName(skill)..", actionPoints="..actionPoints..", skillFactor=" ..skillFactor..", MCfactor="..MCfactor..", attributeBonus="..attributeBonus..", intelligenceBonus=" ..intelligenceBonus..", actionpointFactor="..actionpointFactor..", minorIncrease="..minorIncrease..".") ]] while minorIncrease > 0 do --for the rare case that an action results in two level ups, we have this loop. local realIncrease = math.min(minorIncrease, 10000) --to prevent overflow. if minorSkill + realIncrease < 10000 then user:increaseMinorSkill(skill, realIncrease) else --Level up! user:increaseMinorSkill(skill, realIncrease) --Increase the skill. local skillstring = user:getSkillName(skill) if user:getType() == 0 and user:getQuestProgress(154) == 0 and user:getQuestProgress(325) == 1 then --Only players get an inform once. local callbackSkill = function() end --empty callback. local dialogTitle = common.GetNLS(user,"Levelaufstieg", "Level up") local dialogText = common.GetNLS(user, "Deine Fertigkeit '"..skillstring.."' hat sich soeben erhöht. Drücke 'C' um deine \z Fertigkeiten anzeigen zu lassen. Das Fähigkeitensystem Illarions basiert auf \z 'Learning by doing'. Dein Charakter wird also besser in dem, was er tut. Dein Charakter \z kann mit der Zeit von einem Anfänger im Handwerk zu einem Meister werden, wodurch er dann \z eine größere Vielfalt von Gegenständen besserer Qualität herstellen kann.\n\nJe mehr \z Handlungen du pro Zeit ausführst desto weniger lernst du von jeder Handlung. Unter dem \z Strich lernt aber jeder Charakter gleich viel über die Zeit. Du kannst also so viel Zeit in \z Rollenspiel oder Training stecken wie du möchtest, ohne dass eine Art Illarion zu spielen \z bevorzugt wird.", "Your skill '"..skillstring.."' just increased. Hit 'C' to review your skills. Illarion's \z skill system is based on 'learning by doing'. As your character practices certain actions, \z they begin to improve. Over time your character may develop from an apprentice crafter to a \z master, allowing him/her to create a larger variety of items at a higher quality.\n\nThe more \z actions you do over time, the less you learn from each action. This way, it is on you how \z much time you want to invest on training your skills, the result after a given time will be \z the same. So you can roleplay or train as much as you want without one style of playing being \z favoured by the game.") local dialogSkill = MessageDialog(dialogTitle, dialogText, callbackSkill) user:requestMessageDialog(dialogSkill) user:setQuestProgress(154, 1) --Remember that we already informed the player. elseif user:getType() == 0 then common.TempInformNLS(user, skillstring.." +1", skillstring.." +1") end end minorIncrease = minorIncrease - 10000 end user:increaseMentalCapacity(amplification * actionPoints) end end -- Invoked every 10 s on every user; to be used to reduce MC on a regular basis. -- user:idleTime() can be used to get the number of seconds a user has been idle to check for inactivity. function M.reduceMC(user) M.checkRest(user) if user:idleTime() < 300 and M.getRestTime(user) < 300 then --Has the user done any action within the last five minutes? user:increaseMentalCapacity(-1 * math.floor(user:getMentalCapacity() * damping + 0.5)) --reduce MC-points by 0.01 %, rounded correctly. local currentMC = user:getMentalCapacity() if currentMC < ((0.5/damping)-1) then --Mental Capacity cannot drop below 4999 -> Bugged player or cheater. user:inform("Invalid mental capacity value found: "..currentMC..". Please inform a developer.") user:increaseMentalCapacity(normalMC-currentMC) --Reset to default value. end end --For debugging, use the following line. --[[user:inform("MC="..user:getMentalCapacity()..", idleTime="..user:idleTime()..", MCfactor="..normalMC / math.max(lowerBorder, user:getMentalCapacity())..", Counter="..M.getRestTime(user)..".")]] end function M.getRestTime(user) if not restTime[user.id] then restTime[user.id] = 0 end return restTime[user.id] end function M.increaseRestTime(user) if not restTime[user.id] then restTime[user.id] = 1 else restTime[user.id] = restTime[user.id] + 1 end end function M.resetRestTime(user) restTime[user.id] = 0 local activityTrackerEffect = 84 local foundEffect, myEffect = user.effects:find(activityTrackerEffect) if foundEffect then myEffect:addValue("lastResetRestTime", world:getTime("unix")) end end function M.checkRest(user) if not storedMessage[user.id] then storedMessage[user.id] = user.lastSpokenText end if not storedPosition[user.id] then storedPosition[user.id] = position(user.pos.x,user.pos.y,user.pos.z) end if storedMessage[user.id] == user.lastSpokenText and common.isInRect(user.pos, storedPosition[user.id], 3) and user:idleTime() < 300 then M.increaseRestTime(user) elseif storedMessage[user.id] ~= user.lastSpokenText or not common.isInRect(user.pos, storedPosition[user.id], 3) then M.resetRestTime(user) end storedMessage[user.id] = user.lastSpokenText storedPosition[user.id] = position(user.pos.x,user.pos.y,user.pos.z) end return M