294 lines
13 KiB
Lua
294 lines
13 KiB
Lua
--[[
|
||
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 <http://www.gnu.org/licenses/>.
|
||
]]
|
||
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<72>ht. Dr<44>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<6E>nger im Handwerk zu einem Meister werden, wodurch er dann \z
|
||
eine gr<67><72>ere Vielfalt von Gegenst<73>nden besserer Qualit<69>t herstellen kann.\n\nJe mehr \z
|
||
Handlungen du pro Zeit ausf<73>hrst desto weniger lernst du von jeder Handlung. Unter dem \z
|
||
Strich lernt aber jeder Charakter gleich viel <20>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
|