I decided to blog my thought process during this support challenge.
getPlayerInstantSpellCount
returns the amount of spells a player has, (etc 7 spells) which you add to the variable
count
.
It then loops from 0 to that count-1, so etc 0,1,2,3,4,5 and finally 6. This is what will be stored in the variable
i
for each iteration of the for loop.
But then you proceed to insert this decreasing (remaining spells count to loop over) into the function
getPlayerInstantSpellInfo
. However, this function is interested in spellid, not some random recursive iterator in the code that just logically tells how many spells remains to be looped over.
To figure out how this works, I open the project folder in notepad++ or sublime text or any editor that lets you recursively search all files inside a folder (TFS server) for a text. I searched for
getPlayerInstantSpellInfo
to figure out how it works, and found it located in the data/lib/compat/compat.lua:
Lua:
function getPlayerInstantSpellCount(cid) local p = Player(cid) return p and #p:getInstantSpells() end
function getPlayerInstantSpellInfo(cid, spellId) -- second param here is spellid
local player = Player(cid)
if not player then
return false
end
local spell = Spell(spellId) -- <-- spell metatable? Perhaps we have something in our wiki
if not spell or not player:canCast(spell) then
return false -- <-- hmm, if it fails to find the spell, it returns false.
end
return spell -- the object it returns upon success, appears to be the spell metatable itself
end
You see the second parameter is spellid. Your third spell might be under your
i=3
iteration value, but the spellid might be 53. If it fails to find a spell, it will return false instead of the spell metatable. In line 6 you load this variable, and in line 7 you compare it:
Lua:
local spell = getPlayerInstantSpellInfo(player, i)
if spell.maglv >= 0 then
But in this instance, spell could be false, so your trying to compare false.maglv >= 0. false is a boolean value, and has no attributes, so when you try to grab "false" .maglv property, it returns null, which then is compared to see if its greater or equal to 0. This is probably why you get the error message
spellbook.lua:7: attempt to compare number with nil
.
But do we know that you need to call
spell.maglv
in order to retrieve the magic level requirement for the spell? Could it be another name, like maglvl or magicLevel? I don't remember, so figured I wanted to find out.
I also figured looking at the default code for spellbook in the TFS repo is a good starting point to see how its made:
Lua:
local spellbook = Action()
function spellbook.onUse(player, item, fromPosition, target, toPosition, isHotkey)
local text = {}
local spells = {}
for _, spell in ipairs(player:getInstantSpells()) do -- interesting way to loop through spells, declares spell variable for us
if spell.level ~= 0 then
if spell.manapercent > 0 then
spell.mana = spell.manapercent .. "%"
end
spells[#spells + 1] = spell
end
end
table.sort(spells, function(a, b) return a.level < b.level end)
local prevLevel = -1
for i, spell in ipairs(spells) do
if prevLevel ~= spell.level then
if i == 1 then
text[#text == nil and 1 or #text+1] = "Spells for Level "
else
text[#text+1] = "\nSpells for Level "
end
text[#text+1] = spell.level .. "\n"
prevLevel = spell.level
end
text[#text+1] = spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n"
end
player:showTextDialog(item:getId(), table.concat(text))
return true
end
spellbook:id(2175, 6120, 8900, 8901, 8902, 8903, 8904, 8918, 16112, 18401, 22422, 22423, 22424, 23771)
spellbook:register()
Looking at this code, I see they used a different way to loop through spells, instead of
Lua:
local count = getPlayerInstantSpellCount(player)
for i = 0, count - 1 do
local spell = getPlayerInstantSpellInfo(player, i)
-- ...
They iterate over the spells using ipairs:
Lua:
for _, spell in ipairs(player:getInstantSpells()) do
Which seems a bit easier to work with than using compat functions.
Another benefit of already getting spell objects, is that we probably don't need to figure out the spellid for each spell, and if we do, we can probably find it using spell.id.
I was hoping to see a sample on how magic level is used here. But found none.
But since spell metatable is used (I think?), so lets look into the docs:
A free and open-source MMORPG server emulator written in C++ - otland/forgottenserver
github.com
But its unfortunately a bit lacking, I tried to patch it up a year ago but turns out I didnt finish it. But you might be able to do spell.magicLevel, but perhaps try spell.maglvl as well.
To see everything that is available in the spell metatable, you can try to dump it to console:
Lua:
tdump("Iterated spell object", spell)
To have a look at the data inside the spell variable. It should print it out in console when you execute the code, nice for debugging. There you can see what you need to call to retrieve etc magiclevel, name and other spell things.
So lets use these findings, and try to build some code, I start with the presumption that I can get magic level from spell.maglv (and hope the spell object was just initialized wrong in your code sample).
Lua:
function onUse(player, item, fromPosition, target, toPosition, isHotkey)
--local count = getPlayerInstantSpellCount(player)
local text = ""
local spells = {}
for _, spell in ipairs(player:getInstantSpells()) do --for i = 0, count - 1 do
tdump("Iterated spell object", spell)--local spell = getPlayerInstantSpellInfo(player, i)
if spell.maglv >= 0 then
if spell.manapercent > 0 then
spell.mana = spell.manapercent .. "%"
end
spells[#spells + 1] = spell
end
end
table.sort(spells, function(a, b) return a.maglv < b.maglv end)
local prevmaglv = -1
for i, spell in ipairs(spells) do
local line = ""
if prevmaglv ~= spell.maglv then
if i ~= 1 then
line = "\n"
end
line = line .. "Spells for magic maglv " .. spell.maglv .. "\n"
prevmaglv = spell.maglv
end
text = text .. line .. " " .. spell.words .. " - " .. spell.name .. " : " .. spell.mana .. "\n"
end
player:showTextDialog(item:getId(), text)
return true
end
Notice I only did 3 changes, commented out line 2 (dont need count anymore)
replaced line 5 with this better loop method that also loads in our spell objects for each iteration.
So since we already have spell (from the for loop), we can replace line 6 with a tdump function.
This should spam your console when you execute this action, but it will show the key value pairs inside the spell object, so if we are wrong about maglv, just have a look at what the actual name is there. When everything works, remove line 6 to make the console silent again.
Let me know how this works, I have invested enough time into this now that I am curious.
Edit:
As for your actual NPC problem, why don't you post the full code? I would probably do some magic before the communication dialogue lines.