minetest / minetest

Minetest is an open source voxel game-creation platform with easy modding and game creation
https://www.minetest.net/
Other
10.72k stars 2.02k forks source link

Support Faking a Player (for automation) #11477

Open oversword opened 3 years ago

oversword commented 3 years ago

Problem

Most (if not all) interaction callbacks require a player to be passed who is performing the interaction, referred to as the clicker, placer, digger etc. in the API. For the most part, this player can be faked with a table providing all the functions required, however some callbacks - such as the right_click action on entities require a userdata object and will throw an error if one is not provided.

This kind of fake-player is required for automating tasks such as sheering sheep, which can be accomplished automatically in... other voxel games. It should be possible to accomplish this in minetest too.

Solutions

An API method should be added so that programmers can create a fake player, with the ability to override the player's methods if required. The method should return a userdata object which identifies as a player. At a minimum, the programmer should be able to set: the position, the look dir, the wield item, and the inventory.

Alternatives

Additional context

Currently this is my best attempt:

local function fake_player(player, overrides)
    local fake = {}
    setmetatable(fake, {
        __index = function(mytable, key)
            local override = overrides(key)
            if override then
                return override
            end
            -- Default behaviour for everything else
            local v = player[key]
            if type(v) == "function" then
                return function (...)
                    return player[key](player, ...)
                end
            end
            return v
        end
    })

    return fake
end

Where player is an existing logged in player, and overrides is a function that returns the overrides of methods for a given method name. This works for most things, but cannot be passed to entity:right_click(clicker) as the clicker because it is not a userdataobject.

Tying this to an existing and/or logged in player is not a major issue, we could cope if this automation worked only when the owner player is logged in, I feel it's best to tie the "fake player" to a real player so that someone holds responsibility for the nodes placed or items used, if a more generic fake player is implemented I would still want to be able to override things like the player name so it is associated with a real player.

This is my current working code: https://github.com/joe7575/techpack/pull/98/files It can place a node, or use an item on a node, but cannot use an item on an entity because of the limitations described above.

hlqkj commented 3 years ago

See also #11336.

EDIT: an implementation you may find interesting is in pipeworks mod.

oversword commented 3 years ago

@hlqkj Yeah I've seen that, it has the same issues as mine as it is a table and not userdata, I think my implementation is preferable since you don't have to override each method manually

sfan5 commented 3 years ago

We have an implementation of minetest.is_player that also accepts tables to allow this usage on the Lua side: https://github.com/minetest/minetest/blob/40acfc938ce2306dbf6704f8091a89817a73c71b/builtin/game/misc.lua#L80-L84

What you are attempting to do is call into the engine with a fake player which doesn't work and likely won't anytime soon. Instead of calling obj:right_click(fakeplayer) you should be directly calling the Lua callback: obj:get_luaentity():on_rightclick(fakeplayer) (or look into minetest.registered_on_rightclickplayers if it's a player).

I agree it would be more convenient if mods didn't have to reimplement engine functionality but there is a way to make this work right now and I'm not sure how well the interaction would work between a fake player and engine (C++) code.

however some callbacks - such as the on_rightclick callback on entities require a userdata object and will throw an error if one is not provided.

...and this confused me initially because there is nothing about on_rightclick that is preventing from passing a fake player, right_click is a different story.

oversword commented 3 years ago

@sfan5 I was just trying to call on_rightclick through right_click. Let me try your suggestion quickly, I did not see any way to access the on_rightclick callback from where I was at, but I'm just reading the API and dumping out vars

oversword commented 3 years ago

@sfan5 That worked, thanks!

call into the engine with a fake player which doesn't work and likely won't anytime soon

I'm going to leave this ticket up as it should be possible, much like the other 900 tickets that probably should be resolved but likely never will be :P but now that it's got a work-around, I'll mark it low-priority (if I can)

Edit: nope, can't add labels. Anyone reading this may feel free to mark this as unimportant if they like

hlqkj commented 3 years ago

@sfan5

We have an implementation of minetest.is_player that also accepts tables to allow this usage on the Lua side:

then why not add an API to get such a table when needed? Something like pipeworks and other mods does, when needing to impersonate a player. Would not only be handy, but guarantee a good and well maintained (PlayerRef could change/be extended in future) implementation available to mod devs.

you should be directly calling the Lua callback: obj:get_luaentity():on_rightclick(fakeplayer)

Indeed that's the correct way. Thee are many other callbacks which needs a digger, placer, etc. and are legit callable by mods and this imho justify the addition of such an API:

fakeplayer = minetest.create_fake_player(def)
SmallJoker commented 3 years ago

Relevant: #9177

Originally intended for metadata changes, but could be used for a task like this as well, if the implementation is good. Please see the comments there on why it's not a great solution.

oversword commented 3 years ago

I agree with @hlqkj , the biggest problem with the pipeworks solution is that it basically copies the API manually and may fall out of date at any point, causing a crash when some random callback tries to access a new method which is not on the fake player. If creating a fake player were a core-supported feature we could guarantee that the fake player always implements the latest player API.

hlqkj commented 3 years ago

@oversword: exactly what I meant.

@SmallJoker: not related to #9177 (which btw should not have been closed, but a solution found). We aren't talking of impersonating an existing player in the sense of loading its data while offline, but to create a "as valid as possible" player table which can be safely passed to the mentioned callbacks...

For cases like pipeworks (eg. a node breaker/deployer) the fake player would have the same name as the node owner, that's useful for protection checks for example. On the other hand in #11336 a different use case was proposed, where the fake player wouldn't even be an actual registered player!

sfan5 commented 3 years ago

then why not add an API to get such a table when needed?

But that's not what this issue text is about. OP is proposing a function that creates an userdata player that can be used to trick even engine functions.

I can see what you are proposing being useful but a few points have to be considered:

hlqkj commented 3 years ago

Related, as this feature would make it possible to avoid such situations: https://forum.minetest.net/viewtopic.php?f=11&t=11336&p=399579#p399579.

Zughy commented 1 year ago

UP. This would be very useful for my mod arena_lib, to test minigames requiring more than one player. Instead of opening N clients, server admins could just emulate N-1 players and then join to test. This is especially true for old hardware, for team-based minigames, and to test things that are just trial and error, like HUD

sfan5 commented 1 year ago

I think we have to split this issue apart to get a clear view:

oversword commented 1 year ago

A corporeal player would be quite useful for testing as mentioned, it would be great for interaction feature testing in any mod.

A corporeal player would also solve my current issue, as I believe the player API is already capable of shrinking the player down, making them invisible, ...or maybe event not rendering the entity at all. As long as this player can be manipulated from mod code.

OgelGames commented 5 months ago

I thought it would be worth mentioning here, for anyone in need of a fake player, the release of fakelib, which aims to be the go-to solution for fake players, at least until they get implemented in the engine.

ContentDB: https://content.minetest.net/packages/OgelGames/fakelib/ API documentation: https://github.com/OgelGames/fakelib/blob/master/API.md