Modding Help Message handlers with access to player table [Solved]

Discussion in 'Starbound Modding' started by JT`, Mar 2, 2019.

  1. JT`

    JT` Phantasmal Quasar

    I've been digging my quarter-inch hair out because every reference I've seen suggests that to access the player table, I need to add message handlers to the player from a scope that has access to it: quests, interface dialogs, or objects. Which is terrible for me, because I'm trying to access player functions from a primary status script.

    There's an unfortunate divide in the documentation: we have Silverfeelin's (excellent) tutorial for message events, but it isn't advanced enough to answer the primary question. In the opposite direction we have bk3k's advice to "add a message handler to _ENV" which is just Greek to me -- I get that there's an _ENV table in Lua that contains every table in the game, but I already did a dump on that table using some debug code I borrowed (and deleted after) from Gardenbot which showed that there is indeed no player table in my current scope.

    And yet, I've seen other mods (e.g., Ztarbound) call functions from the player table in deployment scripts...
    Code:
        { "op": "add", "path" : "/deploymentConfig/scripts/-", "value" : "/zb/zb_player_init.lua"},
    zb_player_init.lua:
    Code:
        -- popping up the update window in a safe enviorment so everything else runs as it should in case of errors with the versioning file
        local passed, err = pcall(updateInfoWindow)
        if not passed then
            sb.logError("[ZB] ERROR INITIALIZING POPUP WINDOW\n%s", err)
            status.setStatusProperty("zb_updatewindow_error", checkingMod)
            player.interact("ScriptPane", "/zb/updateInfoWindow/updateInfoWindowError.config", player.id())
        end
    
        -- Add the scan interaction handler if its missing
        if not player.hasQuest("zb_scaninteraction") then
            sb.logInfo("Readded 'zb_scaninteraction' quest")
            player.startQuest("zb_scaninteraction")
        end
        --...
    So my current attempt was simply to add a bunch of message handlers in a deployment script. Using a PromiseKeeper, I successfully send and receive the messages with world.sendEntityMessage(). However, as soon as the PromiseKeeper calls the callback function that was defined in the deployment script, I receive the usual "attempt to index a nil value (global 'player')" which tells me that the player table isn't accessible.

    jt_player_init.lua:
    Code:
    local initparent = init
    local updateparent = update
    
    function init()
        --Irritating. Starbound does not allow access to the player table even within a stat handling script that runs from
        -- the player's primary script sources. Which is absurd.
        --Thus, we need to fire off a deployment script (which does have access to the player table) to register event
        -- handlers to do all of the things that should normally be directly possible on the client's side but for the
        -- hackers (whom I've never actually heard of or seen and who aren't an issue in single player).
        initparent()
        sb.logInfo("Registering event handler: (bool) is_player_admin")
        message.setHandler("is_player_admin",
            function()
                sb.logInfo("%s receiving event handler call: is_player_admin", entity.id())
                return player.isAdmin()
            end)
        sb.logInfo("Registering event handler: (bool) player_in_deployed_mech (UNIMPLEMENTED)")
        message.setHandler("player_in_deployed_mech",
            function()
                sb.logInfo("%s receiving event handler call: player_in_deployed_mech", entity.id())
                return false
            end)
        sb.logInfo("Registering event handler: (bool) has_itemname")
        message.setHandler("has_itemname",
            function(itemname)
                sb.logInfo("%s receiving event handler call: has_itemname", entity.id())
                return player.hasItem({name = itemname})
            end)
    end
    
    function update(dt)
        updateparent(dt)
    end

    Log:
    Code:
    [00:55:57.877] [Info] -65536 receiving event handler call: has_itemname
    [00:55:57.910] [Error] Exception while invoking lua message handler for message 'has_itemname'. (LuaException) Error code 2, [string "/scripts/player_thirst_init.lua"]:27: attempt to index a nil value (global 'player')
    stack traceback:
        [C]: in metamethod '__index'
        [string "/scripts/jt_player_init.lua"]:27: in function <[string "/scripts/jt_player_init.lua"]:25>
        [C]: in field 'sendEntityMessage'
        [string "/scripts/jt_player.lua"]:111: in upvalue 'updateparent'
        [string "/scripts/jt_player_init.lua"]:58: in upvalue 'FR_old_update'
        [string "/scripts/raceEffects.lua"]:16: in upvalue 'FR_old_update'
        [string "/scripts/raceEffects.lua"]:16: in function <[string "/scripts/raceEffects.lua"]:15>
    [0] 13f3878c3 Star::captureStack
    [1] 13f38664e Star::StarException::StarException
    [2] 13f35aa81 Star::LuaEngine::handleError
    [3] 13f54baf4 Star::LuaEngine::callFunction<Star::String,bool,Star::LuaVariadic<Star::Json> >
    [4] 13f54f31a Star::LuaFunction::invoke<Star::Json,Star::String,bool,Star::LuaVariadic<Star::Json> >
    [5] 13f7b160c Star::LuaMessageHandlingComponent<Star::LuaActorMovementComponent<Star::LuaUpdatableComponent<Star::LuaWorldComponent<Star::LuaBaseComponent> > > >::handleMessage
    [6] 13f7b6535 Star::StatusController::receiveMessage
    [7] 13f68ee92 Star::Player::receiveMessage
    [8] 13f8d0801 Star::WorldClient::sendEntityMessage
    [9] 13fa9deb2 Star::LuaBindings::WorldEntityCallbacks::sendEntityMessage
    [10] 13fa510f5 std::_Invoker_functor::_Call<Star::RpcPromise<Star::Json,Star::String> (__cdecl*const & __ptr64)(Star::World * __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String const & __ptr64,Star::LuaVariadic<Star::Json>),Star::World * __ptr64 const & __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String,Star::LuaVariadic<Star::Json> >
    [11] 13fa6495a std::invoke<Star::RpcPromise<Star::Json,Star::String> (__cdecl*const & __ptr64)(Star::World * __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String const & __ptr64,Star::LuaVariadic<Star::Json>),Star::World * __ptr64 const & __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String,Star::LuaVariadic<Star::Json> >
    [12] 13fa555bb std::_Invoke_ret<Star::RpcPromise<Star::Json,Star::String> (__cdecl*const & __ptr64)(Star::World * __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String const & __ptr64,Star::LuaVariadic<Star::Json>),Star::World * __ptr64 const & __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String,Star::LuaVariadic<Star::Json> >
    [13] 13fa5270a std::_Call_binder<std::_Unforced,0,1,2,3,Star::RpcPromise<Star::Json,Star::String> (__cdecl*const)(Star::World * __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String const & __ptr64,Star::LuaVariadic<Star::Json>),std::tuple<Star::World * __ptr64,std::_Ph<1>,std::_Ph<2>,std::_Ph<3> > const ,std::tuple<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> && __ptr64,Star::String && __ptr64,Star::LuaVariadic<Star::Json> && __ptr64> >
    [14] 13fa4c78f std::_Binder<std::_Unforced,Star::RpcPromise<Star::Json,Star::String> (__cdecl&)(Star::World * __ptr64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String const & __ptr64,Star::LuaVariadic<Star::Json>),Star::World * __ptr64 & __ptr64,std::_Ph<1> const & __ptr64,std::_Ph<2> const & __ptr64,std::_Ph<3> const & __ptr64>::operator()<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::String,Star::LuaVariadic<Star::Json> >
    [15] 13fa86495 <lambda_e3fe17e4d27728072b12b0ff558a42ff>::operator()
    [16] 13fa4f93f std::_Invoker_functor::_Call<<lambda_e3fe17e4d27728072b12b0ff558a42ff> & __ptr64,Star::LuaEngine & __ptr64,unsigned __int64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> * __ptr64>
    [17] 13fa63f90 std::invoke<<lambda_e3fe17e4d27728072b12b0ff558a42ff> & __ptr64,Star::LuaEngine & __ptr64,unsigned __int64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> * __ptr64>
    [18] 13fa57979 std::_Invoke_ret<Star::Variant<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::LuaVariadic<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> > >,<lambda_e3fe17e4d27728072b12b0ff558a42ff> & __ptr64,Star::LuaEngine & __ptr64,unsigned __int64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> * __ptr64>
    [19] 13fa8b71b std::_Func_impl<<lambda_e3fe17e4d27728072b12b0ff558a42ff>,std::allocator<int>,Star::Variant<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::LuaVariadic<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> > >,Star::LuaEngine & __ptr64,unsigned __int64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> * __ptr64>::_Do_call
    [20] 13f354e8d std::_Func_class<Star::Variant<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,Star::LuaVariadic<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> > >,Star::LuaEngine & __ptr64,unsigned __int64,Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData> * __ptr64>::operator()
    [21] 13f353e1f <lambda_a03bcae4599b53751a446949639a4d5e>::operator()
    [22] 13f2dbf48 luaD_precall
    [23] 13f2f4cf1 luaV_execute
    [24] 13f2dba53 luaD_call
    [25] 13f2dc22b luaD_rawrunprotected
    [26] 13f2dbc60 luaD_pcall
    [27] 13f2d2174 lua_pcallk
    [28] 13f35bef0 Star::LuaEngine::pcallWithTraceback
    [29] 13f54b4de Star::LuaEngine::callFunction<float>
    [30] 13f54eaa5 Star::LuaFunction::invoke<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,float>
    [31] 13f54e9c0 Star::LuaBaseComponent::invoke<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,float>
    [32] 13f55574c Star::LuaUpdatableComponent<Star::LuaWorldComponent<Star::LuaBaseComponent> >::update<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,float>
    [33] 13f5556bc Star::LuaActorMovementComponent<Star::LuaUpdatableComponent<Star::LuaWorldComponent<Star::LuaBaseComponent> > >::update<Star::Variant<Star::Empty,bool,__int64,double,Star::String,Star::LuaTable,Star::LuaFunction,Star::LuaThread,Star::LuaUserData>,float>
    [34] 13f7b8554 Star::StatusController::tickMaster
    [35] 13f693e65 Star::Player::update
    [36] 13f8ba928 <lambda_9fc580ae40b93070d912cafafa15880e>::operator()
    [37] 13f4dc527 Star::EntityMap::updateAllEntities
    [38] 13f8d319b Star::WorldClient::update
    [39] 13f83bde3 Star::UniverseClient::update
    [40] 13f2ce76b Star::ClientApplication::updateRunning
    [41] 13f2ccfb0 Star::ClientApplication::update
    [42] 13faccc24 Star::SdlPlatform::run
    [43] 13faccddd Star::runMainApplication
    [44] 13f2d04a1 WinMain
    [45] 13fcc849f __scrt_common_main_seh
    [46] 777059cd BaseThreadInitThunk
    [47] 7786383d RtlUserThreadStart

    So, in other words, the script is bouncing on the player.hasItem() call, claiming that it doesn't have access to the player table rather than being an inherent problem with the call itself. (This also occurs even if I don't pass any parameters in the calling message and simply hard-code the item to check for.)

    This would all be infinitely easier if the freaking primary status script just had access to the player table...

    ----------

    Essentially, this boils down to two questions:

    1) How can it be possible to call player functions in the deployment script, but not call player functions in message handlers defined in the deployment script?

    2) Is there a good, working demonstration on how to add a message handler that has access to the player table and whose message can be received from a primary status script?
     
    Last edited: Mar 2, 2019
  2. Errors4l

    Errors4l Spaceman Spiff

    Deployment scripts were created after primary script sources, so they got access to the (newer) player table. CF should probably have added it, but a lot of script related things seem to have been added when they actually need it. CF didn't need the player table in primary script sources, so it's not there (of course, it could have a better reason we're unaware of).

    Here's a simple repository by Magicks that exposes a message handler for a player function: https://github.com/Mehgugs/Starbound-RemoteInterfaces

    When you're talking about primary status scripts, I assume you're referring to primary script sources in the status controller. I don't really see what's wrong with your current code, but maybe it will help you to look at a short and simple example that works.
    Attached is a mod that both logs the content of the player table and returns the result of player.hasItem (silverore) every 5 seconds.
    Note that I am not using a promise keeper because both scripts are executed locally, meaning the message handler will return its result immediately (something covered here).
    Note that the first two parameters of the message handler are messageType (string) and isLocal (bool). You missed that in your has_itemname handler (something covered here).

    PS. When hooking onto existing functions, your current structure works fine, but it tends to lead to a long call chain when a bunch of mods do this. You could use this script to both make it easier to hook functions and reduce the call chain:
    https://gist.github.com/Silverfeelin/3a6286d443d829eed58fa781790e2de5
    Usage: hook("init", myInit)
     

    Attached Files:

    G.Xyon and JT` like this.
  3. JT`

    JT` Phantasmal Quasar

    Hmm. It seems to say that it also only works from a player context, though, which is the same problem I have -- trying to call from the primary script source, I won't have access to the player table.

    Yeah, I was using that interchangeably. It's a script defined with--

    SONOFAGUN.

    Found the problem. =)

    Here was how I was triggering my "deployment script":
    Code:
    { "op": "add", "path": "/statusControllerSettings/primaryScriptSources/-", "value": "/scripts/jt_player_init.lua" }
    ...which is very obviously not a deployment script. Because it triggered at all the times that a deployment script would trigger, I thought it was working just fine, and the absence of the player table was just a gigantic mystery. Now I know why: it was a vile FORGERY! Hoo!

    (For anyone trying to read this and somehow managing to forge through the Greek, the proper path for a deployment script patched into the game is "/deploymentConfig/scripts/-".)

    Yeah, oops, that was just a tweak from the one that ignored all the parameters and called a hard-coded item name, gently massaged when I posted on the forum. I usually do use the "_,_,parameter" format but forgot to apply it to the example code I put up.

    That's... actually quite brilliant. I like that.

    It seems to be throwing errors when trying to call createHook internally, though.
     
    Last edited: Mar 3, 2019
  4. Errors4l

    Errors4l Spaceman Spiff

    Glad you figured it out.
    For the function hook you should be using hook and not createHook. The script only exposes the function hook and all other code can only be used internally (by the script itself). This is why those other functions are marked local.
    Put the file somewhere in your mod, require it at the top of your script then call hook("functionname", yourFunc).
     
  5. JT`

    JT` Phantasmal Quasar

    Nope, I was calling hook("init", init_jt) (having renamed my init function accordingly). It would spit out an error on the line of
    Code:
      hook[func] = true
    in the library -- in other words, createHook internally wasn't successfully creating that index.

    I ultimately just went back to the daisy-chained init function, since it works.
     
    Last edited: Mar 3, 2019
  6. Errors4l

    Errors4l Spaceman Spiff

    Hmm, could you send me the log after it throws an error? I've never had problems with the script myself but I'll have to update the script if there's an issue with it.
     

Share This Page