Adding Player variables and functions (Extending PlayerResource)

edited October 2017 in Tutorials

I spend the last hours searching for the "best way" to add varaibles to players. I wanted to share and discuss the solution I came up with here.

While this tutorial is really basic, it's still very important, especially for people new to modding and/or lua.

Extending PlayerResource

We could create our own class and add all our functions there, but it looks better and is more consistent to extend the existing "PlayerResource" class.

First create a lua file. In this example the file will be called player.lua. Make sure to require the file in addon_game_mode.lua.

The trick in extending existing classes is to use their original identifier not the accessor so in this case we will use CDOTA_PlayerResource not PlayerResource to add variables and functions.

Custom functions

Adding a custom function is pretty straight forward, you can just add a fucntion to the CDOTA_PlayerResource

-- player.lua

function CDOTA_PlayerResource:KillHero(plyID)
    local hero = self:GetSelectedHeroEntity(plyID)
    hero:Kill(nil, hero)
end

Store data for each player

To store data we will need to create a data table. We will than add additional tables for each player. That way we can access a players data via the playerID.

-- player.lua

if not CDOTA_PlayerResource.UserIDToPlayerID then
    CDOTA_PlayerResource.UserIDToPlayerID = {}
    CDOTA_PlayerResource.PlayerIDs = {}
    CDOTA_PlayerResource.PlayerData = {}
end

Since the data is stored in a table we need to initialize the table once a player connects. We do that by listening to the player_fully_connect event. This steps asumes you already know how to setup a basic gamemode.

-- addon_game_mode.lua or wherever you store your game modes base class

function GameMode:Init()
    ListenToGameEvent("player_connect_full", Dynamic_Wrap(GameMode, "OnPlayerConnect"), self)
end

function GameMode:OnPlayerConnect(event)
    PlayerResource:OnPlayerConnect(event)
end

-- player.lua
function CDOTA_PlayerResource:OnPlayerConnect(eventData)
    local userID = eventData.userid
    local playerID = eventData.index
    if not self.PlayerData[playerID] then
        self.UserIDToPlayerID[userID] = playerID
        table.insert(self.PlayerIDs, playerID)
        self.PlayerData[playerID] = {}
    end
end

After that we can simply store and retrive data from the players data table.

-- player.lua

function CDOTA_PlayerResource:SetScore(playerID, score)
    self.PlayerData[playerID].iScore = score
end

function CDOTA_PlayerResource:GetScore(playerID)
    return self.PlayerData[playerID].iScore or 0
    -- Since we don't want to initialize the vairable for each player,
    -- we will make sure the function doesn't return nil by adding "or 0"
end

Somewhere in your gamemode you can then simply access a player's score by calling:

local score = PlayerResource:GetScore(plyID)

I hope this Tutorial proves to be helpful for some people, feel free to leave suggestions

Github: Profile Steam: Profile

Comments

  • Posts: 405

    The whole second part is wrong, don't do that, kids, this will ruin your day. Never use player entity for this kind of stuff, don't store it, don't use it, throw it away. Player entity is destroyed upon disconnection and a new one created upon reconnection, so that just won't work.

    There is actually a proper way to do errors:

    https://github.com/DoctorGester/crumbling-island-arena/blob/master/content/panorama/scripts/custom_game/game_hud.js#L289-L292

    You only need to panorama part because you can't fire a default event from lua to a specific player.

  • edited June 2016 Posts: 1,668

    Yeah doing that will completely break your game as soon as anyone disconnects. It's better to keep your own class and store values by player ID (which never changes).

    But to ellaborate on the first part, when you want to extend an API's class, it's better to use the whole CDOTA_Classname so that all the instances contain the function. PlayerResource is singleton of CDOTA_PlayerResource in that case, but you could extend methods of a hero by defining a function CDOTA_BaseNPC_Hero:MethodName(...) and all hero instances will have the method.

    The concept of Modding Community doesn't go well together with Competitive Business
    My Project Page || My GitHub Profile ||

  • edited June 2016 Posts: 108

    Well, you are completly correct. I totally forgot that players have the ability to reconnect :sweat_smile:. I corrected the post accordingly.

    @Noya I am not sure what you mean. I am extending CDOTA_PlayerResource I just index it locally as PlayerResource to have faster access. It's probably a bit confusing that I called the local variable PlayerResource aswell.

    Github: Profile Steam: Profile

  • Posts: 271

    Is this tutorial wrong or something? I couldn't find it in tutorial index.

    Which entities get destroyed after disconnecting beside player entity? And which entities can be used after reconnecting?

    @lolle Add this to the store data part:

    if not CDOTA_PlayerResource.UserIDToPlayerID then
        CDOTA_PlayerResource.UserIDToPlayerID = {}
        CDOTA_PlayerResource.PlayerIDs = {}
        CDOTA_PlayerResource.PlayerData = {}
    end
    
    -- call this via "player_connect_full" event pass event data to this function
    function CDOTA_PlayerResource:OnPlayerConnect(eventData)
        local userID = eventData.userid
        local playerID = eventData.index
        if not self.PlayerData[playerID] then
            self.UserIDToPlayerID[userID] = playerID
            table.insert(self.PlayerIDs, playerID)
            self.PlayerData[playerID] = {}
        end
    end
    

    its your code btw, from some other post.

  • Posts: 108

    The tutorial was indeed a bit weird in the first version. I updated it again and added the code I posted in that other thread. Should be pretty solid now.

    Github: Profile Steam: Profile

  • Posts: 271

    You are storing PlayerData to Gamemode instead of PlayerResource. Its not wrong, just not the same thing you write about in tutorial. It should be:

    -- player.lua
    
    function CDOTA_PlayerResource:OnPlayerConnect(event)
        local userID = event.userid
        local playerID = event.index
    
        if not self.PlayerData[playerID] then
            self.UserIDToPlayerID[userID] = playerID
            self.PlayerData[playerID] = {}
        end
    end
    
    -- addon_game_mode.lua or wherever you store your game modes base class
    
    function GameMode:Init()
        ListenToGameEvent("player_connect_full", Dynamic_Wrap(GameMode, "OnPlayerConnect"), self)
    end
    
    function GameMode:OnPlayerConnect(event)
        PlayerResource:OnPlayerConnect(event)
    end
    
  • Posts: 108

    You are right, thanks. Fixed it

    Github: Profile Steam: Profile