Modding Help Attempt to index a nil value (global 'player')

Discussion in 'Starbound Modding' started by PistolRcks, Jul 30, 2017.

Tags:
  1. PistolRcks

    PistolRcks Void-Bound Voyager

    I'm outputting functions to strings so I can concatenate them. However, stuff like this happens.
    Tech output:
    Code:
    [Info] Current tech aim position: table: 0x7f2fa0be7540
    [Error] Exception while calling script init: (LuaException) Error code 2, [string "/tech/distortionsphere/techdebug.lua"]:12: attempt to index a nil value (global 'player')
    stack traceback:
        [C]: in metamethod '__index'
        [string "/tech/distortionsphere/techdebug.lua"]:12: in global 'debugCommands'
        [string "/tech/distortionsphere/techdebug.lua"]:6: in function <[string "/tech/distortionsphere/techdebug.lua"]:5>
    
    Tech Lua:
    Code:
    require "/scripts/vec2.lua"
    require "/scripts/keybinds.lua"
    
    --create binding on f to run the thing
    function init()
      Bind.create("f", debugCommands())
    end
    
    --the meat of the thing
    function debugCommands()
      sb.logInfo("Current tech aim position: " .. tostring(tech.aimPosition()))
      sb.logInfo("Current gender/species: " .. tostring(player.gender()) .. " / " .. tostring(player.species()))
      sb.logInfo("Current player entity ID: " .. tostring(player.id()))
      sb.logInfo("Current unique player ID: " .. tostring(player.uniqueID()))
      if player.isAdmin() == true then
        sb.logInfo("Player is currently the Admin.")
      else
        sb.logInfo("Player is not currently the Admin.")
      end
    end
    function update(args)
    end
    function uninit()
    end
    
    I attempted to use typeof() on player, but that didn't help as it returned a similar result (attempt to index a nil value).
    I have little to no clue what's going on anymore.
     
  2. cpeosphoros

    cpeosphoros Orbital Explorer

  3. PistolRcks

    PistolRcks Void-Bound Voyager

    Didn't help. I attempted to call the functions as a variable in init(), but the same error happened with init(). However, debugCommands() didn't fail this time, but outputted "nil" for all the player.*() functions.
    Output:
    Code:
    [Info] Current tech aim position: table: 0x7f59b6d3c180
    [Info] Current gender/species: nil / nil
    [Info] Current player entity ID: nil
    [Info] Current unique player ID: nil
    [Error] Exception while calling script init: (LuaException) Error code 2, [string "/tech/distortionsphere/techdebug.lua"]:19: attempt to index a nil value (global 'player')
    stack traceback:
        [C]: in metamethod '__index'
        [string "/tech/distortionsphere/techdebug.lua"]:19: in global 'debugCommands'
        [string "/tech/distortionsphere/techdebug.lua"]:6: in function <[string "/tech/distortionsphere/techdebug.lua"]:5>
    
    New code:
    Code:
    require "/scripts/vec2.lua"
    require "/scripts/keybinds.lua"
    
    --create binding on f to run the thing
    function init()
      Bind.create("f", debugCommands())
      gender = player.gender()
      species = player.species()
      pid = player.id()
      uid = player.uniqueId()
    end
    
    --the meat of the thing
    function debugCommands()
      sb.logInfo("Current tech aim position: " .. tostring(tech.aimPosition()))
      sb.logInfo("Current gender/species: " .. tostring(gender) .. " / " .. tostring(species))
      sb.logInfo("Current player entity ID: " .. tostring(pid))
      sb.logInfo("Current unique player ID: " .. tostring(uid))
      if player.isAdmin() == true then
        sb.logInfo("Player is currently the Admin.")
      else
        sb.logInfo("Player is not currently the Admin.")
      end
    end
    function update(args)
    end
    function uninit()
    end
    
    EDIT: Maybe I read the post you linked wrong. I don't know.
    (Also I should make it pull values from the table instead of just turning it into a string, but whatever. That's a problem for another time.)
     
  4. cpeosphoros

    cpeosphoros Orbital Explorer

    That only means you don't have a populated player table in the context you are calling this script. Did you try calling it from update() instead of init()?

    About the post I quoted, did you read all the thread? It's about possible contexts for accessing `player`
     
    Last edited: Jul 31, 2017
  5. PistolRcks

    PistolRcks Void-Bound Voyager

    Tried that; here's the code:
    Code:
    require "/scripts/vec2.lua"
    require "/scripts/keybinds.lua"
    
    function init()
    end
    
    --the meat of the thing
    function debugCommands()
      sb.logInfo("Current tech aim position: " .. tostring(tech.aimPosition()))
      sb.logInfo("Current gender/species: " .. tostring(gender) .. " / " .. tostring(species))
      sb.logInfo("Current player entity ID: " .. tostring(pid))
      sb.logInfo("Current unique player ID: " .. tostring(uid))
      if adminCheck then
        sb.logInfo("Player is currently the Admin.")
      else
        sb.logInfo("Player is not currently the Admin.")
      end
    end
    function update(args)
      gender = player.gender()
      species = player.species()
      pid = player.id()
      uid = player.uniqueId()
      adminCheck = player.isAdmin()
      if args.moves["special1"] then
        debugCommands()
      end
    end
    function uninit()
    end
    
    Now the code whines about the exact same thing but within update.
    As for that, I thought I read that correctly. Only the first half would be applicable here. Also, may I note that this is my first time using Lua?
    Maybe I should just read up on Lua a tad more...
     
  6. PistolRcks

    PistolRcks Void-Bound Voyager

    Nevermind; I didn't realize that you couldn't get player from the tech Lua.
     
  7. cpeosphoros

    cpeosphoros Orbital Explorer

    Never tried to mod techs, but you probably can send messages between techs and entities which have access to player
     
  8. PistolRcks

    PistolRcks Void-Bound Voyager

    Which entities would have access to player (other than quests)?

    My basic understanding of how the quest thing might work (although as of right now I have little to no clue how to code quests) is this:
    1. Quest calls player functions
    2. Quest outputs player functions to message handler
    3. Message handler outputs to tech
    4. Tech outputs to log
     
  9. cpeosphoros

    cpeosphoros Orbital Explorer

    Or: 2. If logging is all you want, you can log directly from the quest itself.
    Code:
    The player table contains functions with privileged access to the player which
    run in a few contexts on the client such as scripted interface panes, quests,
    and player companions.
     
  10. PistolRcks

    PistolRcks Void-Bound Voyager

    But then the problem is that then my log would get flooded to no end. Anyway, thanks for all the help and sticking with me 'till the end, @cpeosphoros .
     
  11. cpeosphoros

    cpeosphoros Orbital Explorer

    Code:
    local doOnce = true
    function update(dt)
        -- ...
        if doOnce then
           doOnce = false
           -- log
        end
        -- ...
    end
     
  12. bk3k

    bk3k Oxygen Tank

    That's correct. You can't access the player table from techs. To access the info from there, you'd need to use the messaging system, and hook the player scripts to add a handler to receive the message.

    But there should be easier ways, given that the world table is accessible from techs and should be able to get that info.
    Code:
    local id = entity.id()
    local species = world.entitySpecies(id)
    local gender = world.entityGender(id)
    local UUID = world.entityUniqueId(id)
    
    and so forth

    edit :

    If you want something to dump tables to the log nicely
    Code:
    makeString = function(someTable, baseName, str)--debug purposes only
      str = str or ""
      local isEmpty = true
     
      if someTable == nil then
        str = str .. baseName .. " : nil\n"
        return str
      end
     
      local blank = function(v)
        for _, __ in pairs(v) do
          return false
        end
        return true
      end
     
      for k, v in pairs(someTable) do
        isEmpty = false
        if type(v) == "table" then
          if (#v == 2) and (type(v[1]) == "number") and (type(v[2]) == "number") then  --coordinate table
            str = str .. baseName .. "." .. tostring(k) .. " : {" .. v[1] .. ", " .. v[2]  .. "}\n"
          elseif blank(v) then
            str = str .. baseName .. "." .. tostring(k) .. " : { }\n"
          else
            str = str .. makeString(v , baseName .. "." .. tostring(k), "") --.. "\n"
            --recursive calls don't necessarily need the original string because the main function will still have it
          end
    
        elseif (type(v) == "string") then
          str = str .. baseName .. "." .. tostring(k) .. " : \"" .. v .. "\"\n"
        elseif not (type(v) == "function") then
          str = str .. baseName .. "." .. tostring(k) .. " : " .. tostring(v) .. "\n"
        end
      end
     
      if isEmpty then
        str = str .. baseName .. " : { }\n"
      end
     
      return str
    end
    
    The first argument is a table you pass to it.
    The second argument is a string that describes the table - usually you'd use the table's own name
    The 3rd argument is an optional string that would go at the beginning.

    It will call itself recursively to handle nested tables.
    Use it like this -
    Code:
    local dump = makeString(storage, "storage", "\n")
    dump = makeString(self, "self", dump)
    sb.logInfo(dump)
    
    or
    Code:
    local dump = makeString(storage, "storage", "\n")
    dump = dump .. makeString(self, "self")
    sb.logInfo(dump)
    
    etc. I'll probably improve on it later, but I think you'll find that nice enough for your debugging needs as is. I'm also writing something similar to turn most LUA tables(some can't be translated) into copy/paste ready JSON.
     
    Last edited: Aug 3, 2017
    cpeosphoros likes this.
  13. cpeosphoros

    cpeosphoros Orbital Explorer

    If you go recursively you must treat the special case of looping references.

    Simple case:

    a = {1, 2, 3}
    a.b = a

    This will throw a stack overflow exception with table recursion. You will ran into it both with table-to-string and with deep copy algorithms. Here is my take on these:

    Code:
    -- @LICENSE MIT
    -- @author: CPE
    
    -- convenience function
    local function add(results, value)
        table.insert(results, value)
    end
    
    local cache
    
    -- add a value to results, quoting value if it's a string
    local function addValue(results, vPrefix, value, trail)
    
        local vValue
    
        if (type(value)=="string") then
            vValue = value:quotes()
        elseif (type(value)=="table") then
            vValue =  tostring(value) .. " *"
        else
            vValue =  tostring(value)
        end
    
        add(results, vPrefix .. vValue .. trail)
    end
    
    local _keys = {}
    local revKeys = {}
    local traverse = {}
    local traversing = false
    local errorLevel = 1
    
    -- add a key,value pair to results, recursively if value is a table
    local function addPair(results, key, value, indent)
        errorLevel = errorLevel + 1
    
        if table.isEmpty(_keys) or revKeys[key] or traversing then
    
        local oldTraversing = traversing
    
        traversing = traverse[key] ~= nil
    
            local vPrefix = indent .. tostring(key) .." : "
    
            -- check for recursivity - cache[value] is to avoid recursion loops
            if (type(value)=="table") and not cache[value] then
    
                cache[value] = true
    
                addValue(results, vPrefix, value, " {" )
    
                table.formatted( value, _keys,
                    indent .. string.rep(" ", tostring(key):len() + 8 ), results
                )
    
                add(results, indent .. string.rep(" ", tostring(key):len() + 5 ).."},")
            else
                addValue(results, vPrefix , value, ",")
            end
    
            traversing = oldTraversing
    
        end
    
        errorLevel = errorLevel - 1
    end
    
    
    function table:reverse()
        errorLevel = errorLevel + 1
        -- not a table? Why are we messing with it?
        local rev = {}
        for i,v in ipairs(self) do
            rev[v] = i
        end
        errorLevel = errorLevel - 1
        return rev
    end
    
    function table:isEmpty()
        return next(self) == nil
    end
    
    --
    -- handle the first level special case
    -- @return a list of strings, not ending in "\n" formatted for printing
    function table:formatted (keys, indent, results)
        errorLevel = errorLevel + 1
    
        if not keys or keys == "" then keys = {} end
        if type(keys) ~= "table" then keys = {keys} end
    
        _keys = keys
        traverse = table.reverse(_keys.traverse or {})
        revKeys = table.reverse(_keys)
    
        --defaults
        indent = indent or ""
        local oldIndent = indent
        local firstLevel
    
        --first level
        if not results then
            results = {}
            cache = {}
            firstLevel = true
        end
    
        if firstLevel then
            --blank line, then curly oopen
            add(results, "")
            addValue(results, indent, self, " {" )
            indent = indent .."  "
        end
    
        -- adds all key,value pairs into results
        for key,value in pairs(self) do
            addPair(results, key, value, indent)
        end
    
        -- close the first level curly, then blank line
        if firstLevel then
            add(results, oldIndent.."}")
            add(results, "")
            --TODO: include flag for this
            --add(results, "Tables traversed:")
            --table.formatted (cache, "", results)
        end
    
        errorLevel = errorLevel - 1
    
        -- the recursive call should ignore this. If it doesn't, an ugly mess insues
        return results
    end
    
    -- pretty printable string from a table
    function table:tostring( keys, indent)
        errorLevel = errorLevel + 1
        local result = table.concat(table.formatted(self, keys, indent), "\n")
        errorLevel = errorLevel - 1
        return result
    end
    
    -- convenience function
    function table:print(keys, indent)
        errorLevel = errorLevel + 1
        print( table.tostring(self, keys, indent ) )
        errorLevel = errorLevel - 1
    end
    
    return table
    Code:
    -- @LICENSE MIT
    -- @author: CPE
    
    local cCache
    local cPromises
    local cLevel = 0
    
    function table:deepCopy()
    
        cLevel = cLevel + 1
        if cLevel == 1 then
            cCache    = {}
            cPromises = {}
        end
        local copy
        if type(self) == 'table' then
            print(cLevel ..": ".. tostring(self))
            if(cCache[self]) then
                -- print("Returning cached", cCache[self], "for", self)
                copy = cCache[self]
            else
                cPromises[self] = {}
                copy = {}
                for k, v in next, self, nil do
                    print(k, v)
                    local key   = cPromises[k] or table.deepCopy(k)
                    local value = cPromises[v] or table.deepCopy(v)
                    copy[key] = value
                end
                setmetatable(copy, table.deepCopy(getmetatable(self)))
                -- print("Caching", copy, "for", self)
                cCache[self] = copy
                cPromises[self] = copy
            end
        else -- number, string, boolean, etc
            copy = self
        end
    
        cLevel = cLevel - 1
        return copy
    end
    
    function table:shallowCopy()
        local copy = {}
        for k, v in pairs(self) do
            copy[k] = v
        end
        return copy
    end
    
    return table


    I already have working code for that, based on this great library: http://regex.info/blog/lua/json

    Code:
    -- @LICENSE MIT
    -- @author: CPE
    
    -- Strip C style comments from a string
    return function (pInput, pOutput)
        local function strip(str)
            local _, _, pre, com, pos = string.find(str, "^(.*)/%*(.+)%*/(.*)$")
            pre = pre or ""
            com = com or ""
            pos = pos or ""
            if com ~= "" then str = strip(pre .. pos) end
            return str
        end
        local function eolCom(str, pattern, subs)
            while str:find(pattern) do
                str = string.gsub(str, pattern, subs)
            end
            return str
        end
        repeat
            local str
            if type(pInput) == "function" then
                str = pInput()
            else
                str = pInput
            end
            if str then
                str = strip(str)
                str = eolCom(str,"(\".-\",)(%s*%/[%/%*].-)\n", "%1\n")
                str = eolCom(str,"(\n%s*%/[%/%*].-)\n", "\n")
                str = eolCom(str,"^(%s*%/[%/%*].-)\n", "\n")
                str = eolCom(str,"(%],?)%s*%/[%/%*].-\n", "%1\n")
            end
            if type(pOutput) == "function" then
                pOutput(str)
            else
                return str
            end
        until not str
    end
    Code:
    -- @LICENSE MIT
    -- @author: CPE
    
    local stripComments = require ("stripComments")
    
    -- Decodes json files into tables. We use Jeffrey Friedl's JSON.lua for being
    -- pure Lua and providing error treatment callbacks, which we need for stray C
    -- style comments.
    return function (pInput, pOutput)
        local JSON = require "JSON"
    
        local decodeError = {}
        function JSON:onDecodeError(message, text, location)
            decodeError = {location, message, text}
        end
    
        local function tryFix(text, pos)
            local prefix = string.sub(text, 1, pos - 1)
            local suffix = string.sub(text, pos)
    
            local str = prefix..stripComments(suffix)
    
            if str ~= text then
                return JSON:decode(str)
            end
        end
    
        function JSON:onTrailingGarbage(json_text, location)
            local res = tryFix(json_text, location)
            if res then return res end
            return nil, "trailing garbage"
        end
    
        local str
    
        if type(pInput) == "function" then str = pInput() end
        if type(pInput) == "string" then str = pInput end
    
        if not str then return end
    
        local res = JSON:decode(str)
    
        local isError = #decodeError ~= 0
    
        local fakeErrors ={
            ["expected comma or '}'"] = true,
            ["expected colon"       ] = true,
        }
    
        if isError and fakeErrors[decodeError[2]] then
            local pos  = decodeError[1]
            local text = decodeError[3]
            local fix  = tryFix(text, pos)
            if fix then
                res = fix
                isError = false
            end
        end
    
        local function returns()
            return isError, isError and decodeError or res
        end
    
        if type(pOutput) == "function" then
            pOutput(returns())
        else
            return returns()
        end
    end
    Usage:
    Code:
    -- @LICENSE MIT
    -- @author: CPE
    
    local stripComments = require "stripComments"
    local jsonDecode = require "jsonDecode"
    
    local assetCache = {}
    function getJsonAsset(path)
        if not assetCache[path] then
            local src = readfile(path)
            src = stripComments(src)
            local isError, res = jsonDecode(src)
            if isError then
                error ("Invalid json file "..path.." "..res[2].." at pos "..res[1]..".")
            end
            assetCache[path] = res
        end
        return assetCache[path]
    end
    local table = getJsonAsset("/path/to/whatever.object")
    readfile() is a function which reads the entire contents of a file, preserving line breaks.

    As for transforming a table into json, JSON:encode(tbl) , from Jeffrey Friedl's JSON.lua, will handle almost any imaginable case.
     
    Last edited: Aug 3, 2017
  14. PistolRcks

    PistolRcks Void-Bound Voyager

    This is... a lot. Thanks for the in-depth support! Will dive into when I can.
     

Share This Page