illarion-content/base/magic.lua
2026-02-10 15:00:24 +01:00

390 lines
12 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 gems = require("base.gems")
local globalvar = require("base.globalvar")
local MAGE_MIN_ATTRIBUTE_SUM = 30
local M = {}
M.mageSkills = {
Character.enchanting,
Character.spatialMagic,
Character.earthMagic,
Character.windMagic,
Character.fireMagic,
Character.spiritMagic,
Character.waterMagic
}
M.druidSkills = {
Character.alchemy,
Character.transmutation,
Character.deepweave,
Character.bloomweave,
Character.wildweave,
Character.stoneweave,
Character.witherweave
}
M.priestSkills = {
Character.blessing,
Character.praying,
Character.vowing,
Character.confessing,
Character.ceremony,
Character.consecrateWeapons,
Character.consecrateArmours
}
M.wandIds = {323,2782,2783,2784,2785,3608,4820}
local wandList = {
{id = 323, element = "Neutral"},
{id = 2782, element = "Earth"},
{id = 2783, element = "Fire"},
{id = 2784, element = "Water"},
{id = 2785, element = "Air"},
{id = 3608, element = "Spirit"},
{id = 4820, element = "Novice"}
}
function M.getWand(user)
local left = user:getItemAt(5)
local right = user:getItemAt(6)
for _, wand in pairs(wandList) do
if left.id == wand.id then
return left
elseif right.id == wand.id then
return right
end
end
return false
end
function M.getWandElement(user)
local left = user:getItemAt(5)
local right = user:getItemAt(6)
for _, wand in pairs(wandList) do
if left.id == wand.id then
return wand.element
elseif right.id == wand.id then
return wand.element
end
end
return false
end
function M.checkElementBonus(user, element) --Return a number between 0.025 and 0.1 if there is a wand
local wandElement = M.getWandElement(user)
if not wandElement then
return 0
end
if wandElement == element then
return 0.1
elseif wandElement == "Neutral" then
return 0.05
elseif wandElement == "Novice" then
return 0
else
return 0.025
end
end
-- Best skill values
local function bestSkillFromSkillList(user, skillList)
local bestValue = 0
local thisValue
for _, skill in pairs (skillList) do
thisValue = user:getSkill(skill)
if thisValue > bestValue then
bestValue = thisValue
end
end
return tonumber(bestValue)
end
function M.getBestMageSkill(user)
return bestSkillFromSkillList(user, M.mageSkills)
end
function M.getBestDruidSkill(user)
return bestSkillFromSkillList(user, M.druidSkills)
end
function M.getBestPriestSkill(user)
return bestSkillFromSkillList(user, M.priestSkills)
end
function M.getBestSkill(user)
local bestValue
bestValue = math.max(M.getBestMageSkill(user), M.getBestDruidSkill(user), M.getBestPriestSkill(user))
return bestValue
end
M.mageAttributes = {
"intelligence",
"essence",
"willpower"
}
-- Best magic attribute
function M.getBestMageAttribute(user)
local bestValue = 0
local thisValue
for _, attribute in pairs (M.mageAttributes) do
thisValue = user:increaseAttrib(attribute, 0)
if thisValue > bestValue then
bestValue = thisValue
end
end
return bestValue
end
-- play a random magic related GFX at location, with a preference for sprinkle
function M.pickAndPlayRandomGfx(location)
local gfxList = {globalvar.gfxRAIN, globalvar.gfxSCOTTY, globalvar.gfxSPRINKLE, globalvar.gfxSPRINKLE, globalvar.gfxSPRINKLE}
local selectedGfx = gfxList[math.random(1,#gfxList)]
world:gfx(selectedGfx, location)
end
--[[canBeMage
mage attributes must be >= 30
returns true or false and attribute sum ]]--
function M.hasMageAttributes(user)
local attributeSum = 0
for _, attribute in pairs (M.mageAttributes) do
attributeSum = attributeSum + user:increaseAttrib(attribute, 0)
end
if attributeSum < MAGE_MIN_ATTRIBUTE_SUM then
return false, attributeSum
else
return true, attributeSum
end
end
function M.characterIsAMage(user)
if M.hasMageAttributes(user) and user:getMagicType() == 0 and user:getQuestProgress(37) ~= 0 then
return true
end
return false
end
--[[neededFood
1st try 40 foodpoints / s magic
return foodpoints]]--
function M.neededFood(user, spellTime)
return spellTime * 40
end
--[[mana check
return: true if sufficient mana]]--
function M.hasSufficientMana(user,mana)
if user:increaseAttrib("mana", 0) > mana then
return true
end
return false
end
--[[gemBonus for user
get the gem bonus of the currently equipped wand
return gemBonus]]--
function M.getGemBonusWand(user)
local leftTool = user:getItemAt(Character.left_tool)
local rightTool = user:getItemAt(Character.right_tool)
if common.isInList(leftTool.id, M.wandIds) then
return gems.getGemBonus(leftTool)/2
elseif common.isInList(rightTool.id, M.wandIds) then
return gems.getGemBonus(rightTool)/2
end
return 0
end
function M.getGemBonusCloak(user) -- Used for magic defense purposes
if not isValidChar(user) then
return 0
end
local cloak = user:getItemAt(Character.coat)
if cloak and cloak.id ~= 0 then
return gems.getGemBonus(cloak)/2
end
return 0
end
function M.getQualityBonusWand(user)
local leftTool = user:getItemAt(Character.left_tool)
local rightTool = user:getItemAt(Character.right_tool)
if common.isInList(leftTool.id, M.wandIds) then
return common.GetQualityBonusStandard(leftTool)
elseif common.isInList(rightTool.id, M.wandIds) then
return common.GetQualityBonusStandard(rightTool)
end
return 0
end
function M.getQualityBonusCloak(user)
local cloak = user:getItemAt(Character.coat)
if cloak and cloak.id ~= 0 then
return gems.GetQualityBonusStandard(cloak)
end
return 0
end
--[[getValueWithGemBonus
calculates a mana consumption, damage or time taking the gem bonus into consideration
level 1 = 94 %
level 3 = 82 %
level 5 = 70 %
return: mana consumption]]--
function M.getValueWithGemBonus(user, inputValue)
local newValue = inputValue * (1 - 0.005 * M.getGemBonusWand(user))
return math.floor(newValue)
end
--[[wandDegrade
reduced the wand durability by 1 with a given chance
1 out of chance
wand default the wand in hand beginning left hand
chance default = 20
return: nothing]]--
function M.wandDegrade(user, wand, chance)
local degradeChance = chance or 20
if user:isNewPlayer() then
degradeChance = degradeChance * 2
end
degradeChance = math.max(1,math.floor(degradeChance))
if wand == nil then
local leftTool = user:getItemAt(Character.left_tool)
local rightTool = user:getItemAt(Character.right_tool)
if common.isInList(leftTool.id, M.wandIds) then
wand = leftTool
elseif common.isInList(rightTool.id, M.wandIds) then
wand = rightTool
else
return
end
end
if (common.Chance(1, degradeChance)) then
local durability = common.getItemDurability(wand)
local nameText = common.getItemName(wand, user:getPlayerLanguage())
durability = durability - 1
if (durability == 0) then
common.InformNLS(user,
"Deine Waffe '"..nameText.."' zerbricht.",
"Your weapon '"..nameText.."' shatters.")
end
common.setItemDurability(wand, durability)
--[[if (durability < 10) then
common.InformNLS(user,
"Deine Waffe '"..nameText.."' hat schon bessere Zeiten gesehen. Vielleicht solltest du sie reparieren lassen.",
"Your weapon '"..nameText.."' has seen better days. You may want to get it repaired.")
end]]
end
end
local function getBonus(character, max, bodyPositions)
local itemsWithMagicBonus = {}
local magicBonus = 0
local quality = 0
for _, bodyPosition in pairs(bodyPositions) do
local checkItem = character:getItemAt(bodyPosition)
local itemExists = checkItem ~= nil and checkItem.id > 0
if itemExists then
local _, armorStruct = world:getArmorStruct(checkItem.id)
local itemBonus = armorStruct.MagicDisturbance
if itemBonus > 0 then
magicBonus = magicBonus + itemBonus
quality = quality + math.floor(checkItem.quality/100)
table.insert(itemsWithMagicBonus, checkItem)
end
end
end
local qualityBonus = 1
if #itemsWithMagicBonus >= 1 then
qualityBonus = qualityBonus+(quality/#itemsWithMagicBonus - 5)*2.5/100 -- quality 5 has no influence; above 5, each point grants 2.5%. under 5, each point takes 2.5%
end
return math.min(max, magicBonus*qualityBonus/500)
end
--Returns the total magic bonus and a list containing the items which add to the magic bonus
function M.getMagicBonus(character)
-- Best jewellery of perfect archmage rings + icebird amulet provide a bonus of 0.1386
local jewelleryBonus = getBonus(character, 0.1386, {Character.neck, Character.finger_left_hand, Character.finger_right_hand})
-- Best clothing combination is a bonus of 0.121
local clothingBonus = getBonus(character, 0.0614, {Character.head, Character.breast, Character.hands, Character.legs, Character.feet, Character.coat})
-- We set it to 0.0614. This way clothing is more about wearing clothes instead of armour, and you are able to more freely mix how your paperdolling looks
-- There is still a small impact, wearing low quality clothes or wearing only low level clothing will not reach the max sum
-- Technically caps out at 0.2596, but lowering that to 0.2 allows more flexibility in fashion choices
-- As jewellery and clothing are calculated separately, we avoid a scenario where someone minmaxes to get the best glyphed jewellery and wears the same brown cloak blue clothes "best" gear all the time as a mage
-- As the purpose of this leeway is to allow more expression in paperdolling without feeling punished, NOT to allow you to ignore the choice between best jewellery (archmage, icebird) and glyph bonuses
return math.min(0.2, (jewelleryBonus+clothingBonus))
-- All in all, jewellery has a 13.86% max magic resistance/penetration impact and clothing 6.14%.
-- not counting how gems in your cloak/wand increase magic damage reduced/dealt
-- You have 30% by default, which was needed to balance out numbers and not be too top heavy
-- Meanwhile the remaining 50% is all willpower as the biggest impact
end
return M