# Poker

## About

This system will bring in a 5 card Poker to your server with a modern and clean UI, fulfilled UX, and many additional features.

{% embed url="<https://www.youtube.com/watch?ab_channel=SquirrelBracket&feature=youtu.be&v=uADTuHgbU8k>" %}
Preview video
{% endembed %}

### Features

* Animated minimal UI;
* Multi tables - create as many poker tables as you want assigining different parameters for each of them, like amount of seats, currency used, min buy in, blind amounts and more;
* Dealer animations - dealer ped with their actions being animated;
* Custom table prop - a system contains a 3D table model created for this script, which matches Diamond Casino theme;
* Secure - encrypted events and their parameters, server side calculations, to ensure security.

## Installation

Installation difficulty

&#x20;🟠⚫⚫⚫⚫ - very easy

* Download the resource from [cfx keymaster](https://keymaster.fivem.net/asset-grants);
* Unzip the resource archive;
* \[Optional] if you are willing to use `casino_chips` (or alternative) as a currency instead of cash make sure to add that as your framework item;
* Add `sb-poker-assets` and `sb-poker` folders to your server `resources` folder and start them by adding the following to your `server.cfg`:\
  `ensure sb-poker-assets`\
  `ensure sb-poker`
* \[Optional] Adjust `sb-poker/configs/config.lua` as well as `client/framework.lua` and `server/framework.lua` if necessary, as no changes should be required if you are using not modified `es_extended legacy` or `qb-core` frameworks, it should be automatically detected and no changes should be done;

## Config

{% tabs %}
{% tab title="configs/config.lua" %}
{% code lineNumbers="true" %}

```lua
Config = {}

Config.Framework = 'auto' -- 'auto' | 'esx' | 'qbcore' | 'custom' (make sure to configure */famework.lua files if you set the custom framework)
Config.FrameworkData = { -- ONLY change these if you have custom names set on your framework
    SharedObjectEventNameCL = nil, -- Framework shared object event name for client side
    SharedObjectEventNameSV = nil, -- Framework shared object event name for server side
}
Config.Notifications = 'framework'

Config.Locale = 'en'

Config.ChipsItem = 'casino_chips'

Config.Tables = {
    {
        name = 'casino_02',
        buyIn = 100,
        bigBlind = 10,
        currency = 'chips', -- chips | cash | custom. Can be customized / added more currency methods in server/framework.lua (CheckPlayerEligible, RemoveCurrency, AddCurrency)
        tableModel = 'dzp_sd_prop_casino_poker_table_01',
        chairModel = 'dzp_sd_prop_casino_poker_chair_01',
        tableCoords = vec(994.8425, 54.8804, 68.4329, 192.9074),
        dealerOffset = vec(-1.15, 0.0, 0.03, 90.0),
        printOffsets = false,
        cards = {
            offsets = {
                {-0.555, -0.263, 0.943},
                {-0.555, -0.13, 0.943},
                {-0.555, -0.003, 0.943},
                {-0.555, 0.132, 0.943},
                {-0.555, 0.267, 0.943},
            },
            objects = {},
        },
        chairs = {
            {offset = vec(-1.205, 1.207, 0.04, 150.975)},
            {offset = vec(-0.588, 1.862, 0.04, 119.641)},
            {offset = vec(0.635, 1.892, 0.04, 62.110)},
            {offset = vec(1.256, 1.246, 0.04, 27.327)},
            {offset = vec(1.300, 0.346, 0.04, -0.118)},
            {offset = vec(1.292, -0.421, 0.04, 0.396)},
            {offset = vec(1.098, -1.369, 0.04, -32.112)},
            {offset = vec(0.453, -1.882, 0.04, -72.839)},
            {offset = vec(-0.581, -1.853, 0.04, -114.813)},
            {offset = vec(-1.297, -1.133, 0.04, -156.873)},
        }
    },
    {
        name = 'casino_03',
        buyIn = 1000,
        bigBlind = 100,
        currency = 'chips', -- chips | cash | custom. Can be customized / added more currency methods in server/framework.lua
        tableModel = 'dzp_sd_prop_casino_poker_table_01',
        chairModel = 'dzp_sd_prop_casino_poker_chair_01',
        tableCoords = vec(1000.1622, 56.0508, 68.4329, 192.0431),
        dealerOffset = vec(-1.15, 0.0, 0.03, 90.0),
        printOffsets = false,
        cards = {
            offsets = {
                {-0.555, -0.263, 0.943},
                {-0.555, -0.13, 0.943},
                {-0.555, -0.003, 0.943},
                {-0.555, 0.132, 0.943},
                {-0.555, 0.267, 0.943},
            },
            objects = {},
        },
        chairs = {
            {offset = vec(-1.205, 1.207, 0.04, 150.975)},
            {offset = vec(-0.588, 1.862, 0.04, 119.641)},
            {offset = vec(0.635, 1.892, 0.04, 62.110)},
            {offset = vec(1.256, 1.246, 0.04, 27.327)},
            {offset = vec(1.300, 0.346, 0.04, -0.118)},
            {offset = vec(1.292, -0.421, 0.04, 0.396)},
            {offset = vec(1.098, -1.369, 0.04, -32.112)},
            {offset = vec(0.453, -1.882, 0.04, -72.839)},
            {offset = vec(-0.581, -1.853, 0.04, -114.813)},
            {offset = vec(-1.297, -1.133, 0.04, -156.873)},
        }
    },
}

Config.Controls = {
    SitAtTable = {0, 38},
}

Strings = {
    ['en'] = {
        ['notification_sit_at_table'] = '[E] - Start playing',
        ['header_label_winners'] = 'Winners: %s',
        ['header_label_idle'] = 'Waiting...',
        ['header_label_waiting_new_game'] = 'Waiting for new game...',
        ['subheader_label_turn'] = 'Is current actor',
        ['subheader_label_not_enough_players'] = 'Not enough players',
        ['alert_not_actor'] = 'You are not the current actor',
        ['alert_action_not_legal'] = 'This action is not currently legal',
        ['alert_amount_invalid'] = 'The bet amount is not valid',
        ['alert_seat_taken'] = 'This seat is already taken, please choose another one',
    },
    ['lt'] = {
        ['notification_sit_at_table'] = '[E] - Pradėti žaisti',
        ['header_label_winners'] = 'Laimėjo: %s',
        ['header_label_idle'] = 'Laukiama žaidėjų...',
        ['header_label_waiting_new_game'] = 'Laukiama naujo žaidimo pradžios...',
        ['subheader_label_turn'] = 'Eilė',
        ['subheader_label_not_enough_players'] = 'Not enough players',
        ['alert_not_actor'] = 'You are not the current actor',
        ['alert_action_not_legal'] = 'This action is not currently legal',
        ['alert_amount_invalid'] = 'The bet amount is not valid',
        ['alert_seat_taken'] = 'This seat is already taken, please choose another one',
    },
}

```

{% endcode %}
{% endtab %}

{% tab title="server/framework.lua" %}
{% code lineNumbers="true" %}

```lua
Poker = {}
Poker.FrameworkObject = nil -- Holds ESX or QBCore objects
Poker.FrameworkType = ''
Poker.ResourceFolderName = nil

local function fetchSharedObject(resourceName, exportName, eventName)
    Poker.ResourceFolderName = resourceName
    try(function()
        Poker.FrameworkObject = exports[resourceName][exportName]()
    end, function (err)
        print(err)
        TriggerEvent(eventName, function (Obj)
            Poker.FrameworkObject = Obj
        end)
        Citizen.CreateThread(function()
            Citizen.Wait(200)
            if not Poker.FrameworkObject then
                debugPrint('^8ERROR^7', -1, 'Framework loading failed. Given parameters:')
                debugPrint('^8ERROR^7', -1, ('Framework name: ^4%s^7, event name: ^4%s^7, export function name: ^4%s^7, used type: ^4%s^7'):format(Config.Framework, eventName, exportName, Poker.FrameworkType))
                debugPrint('NOTE', -1, 'If any of the parameters do not match your server settings please check your ^4*/framework.lua^7 files')
                Poker.FrameworkType = 'custom'
            end
        end)
    end)
end

local function parseResourceNameFromPath (path)
    local index = string.find(path, "/[^/]*$")
    return string.sub(path, index + 1)
end

Citizen.CreateThread(function()
    if Config.Framework == 'auto' then
        if GetResourceState('es_extended') == 'started' then
            Poker.FrameworkType = 'esx'
        elseif GetResourceState('qb-core') == 'started' then
            Poker.FrameworkType = 'qbcore'
        else
            Poker.FrameworkType = 'custom'
        end
    elseif Config.Framework == 'esx' then
        Poker.FrameworkType = 'esx'
    elseif Config.Framework == 'qbcore' then
        Poker.FrameworkType = 'qbcore'
    else
        Poker.FrameworkType = 'custom'
    end
    
    local evName = IsDuplicityVersion() and Config.FrameworkData.SharedObjectEventNameSV or Config.FrameworkData.SharedObjectEventNameCL
    if Poker.FrameworkType == 'esx' then
        fetchSharedObject(parseResourceNameFromPath(GetResourcePath('es_extended')), 'getSharedObject', evName or 'esx:getSharedObject')
    elseif Poker.FrameworkType == 'qbcore' then
        fetchSharedObject(parseResourceNameFromPath(GetResourcePath('qb-core')), 'GetCoreObject', evName or 'QBCore:GetObject')
    elseif Poker.FrameworkType == 'custom' then
        -- Add code for your custom framework
        debugPrint('^8ERROR^7', -1, 'Framework loading failed. No code for custom framework provided, check your ^4*/framework.lua^7 files')
    end
end)

Poker.CheckPlayerEligible = function(source, tableName, buyIn)
    local tableData = getTableCfgByName(tableName)
    if Poker.FrameworkType == 'esx' then
        local currencryChecks = {
            ['cash'] = function(playerId, buyIn)
                local xPlayer = Poker.FrameworkObject.GetPlayerFromId(playerId)
                if xPlayer.getAccount('money').money > buyIn then
                    return true
                end
            end,
            ['chips'] = function(playerId, buyIn)
                -- ------------------------------------------
                -- Example of item 'chips' implementation (using chips as inventory item):
                -- ------------------------------------------
                local xPlayer = Poker.FrameworkObject.GetPlayerFromId(playerId)
                if xPlayer.getInventoryItem(Config.ChipsItem).count > buyIn then
                    return true
                end
            end,
            ['custom'] = function(playerId, money)
                -- Add code for custom currency
            end
        }

        return currencryChecks[tableData.currency](source, buyIn)
    elseif Poker.FrameworkType == 'qbcore' then
        local currencryChecks = {
            ['cash'] = function(playerId, buyIn)
                local ply = Poker.FrameworkObject.Functions.GetPlayer(playerId)
                return ply.getAccount("bank")?.money > buyIn
            end,
            ['chips'] = function(playerId, buyIn)
                -- ------------------------------------------
                -- Example of item 'chips' implementation (using chips as inventory item):
                -- ------------------------------------------
                local ply = Poker.FrameworkObject.Functions.GetPlayer(playerId)
                if ply.getInventoryItem(Config.ChipsItem).count > tableData.buyIn then
                    return true
                end
            end,
            ['custom'] = function(playerId, money)
                -- Add code for custom currency
            end
        }
        return currencryChecks[tableData.currency](source, tableData.buyIn)
    end
    return false
end

Poker.RemoveCurrency = function(source, tableName, amount)
    local tableData = getTableCfgByName(tableName)
    debugPrint('RemoveCurrency', 3, Poker.FrameworkType, source, tableName, amount)
    if Poker.FrameworkType == 'esx' then
        local currencryChecks = {
            ['cash'] = function(playerId, money)
                local xPlayer = Poker.FrameworkObject.GetPlayerFromId(playerId)
                xPlayer.removeAccountMoney('money', money)
            end,
            ['chips'] = function(playerId, money)
                -- ------------------------------------------
                -- Example of item 'chips' implementation (using chips as inventory item):
                -- ------------------------------------------
                local xPlayer = Poker.FrameworkObject.GetPlayerFromId(playerId)
                xPlayer.removeInventoryItem(Config.ChipsItem, money, {})
            end,
            ['custom'] = function(playerId, money)
                -- Add code for custom currency
            end
        }

        return currencryChecks[tableData.currency](source, amount)
    elseif Poker.FrameworkType == 'qbcore' then
        local currencryChecks = {
            ['cash'] = function(playerId, money)
                local ply = Poker.FrameworkObject.Functions.GetPlayer(playerId)
                ply.Functions.RemoveMoney('cash', money)
            end,
            ['chips'] = function(playerId, money)
                -- ------------------------------------------
                -- Example of item 'chips' implementation (using chips as inventory item):
                -- ------------------------------------------
                local ply = Poker.FrameworkObject.Functions.GetPlayer(playerId)
                ply.mergeItems(Config.ChipsItem, -money)
            end,
            ['custom'] = function(playerId, money)
                -- Add code for custom currency
            end
        }

        return currencryChecks[tableData.currency](source, amount)

    end
end

Poker.AddCurrency = function(source, tableName, amount)
    local tableData = getTableCfgByName(tableName)
    debugPrint('AddCurrency', 3, Poker.FrameworkType, source, tableName, amount)
    if Poker.FrameworkType == 'esx' then
        local currencryChecks = {
            ['cash'] = function(playerId, money)
                local xPlayer = Poker.FrameworkObject.GetPlayerFromId(playerId)
                xPlayer.addAccountMoney('money', money)
            end,
            ['chips'] = function(playerId, money)
                -- ------------------------------------------
                -- Example of item 'chips' implementation (using chips as inventory item):
                -- ------------------------------------------
                local xPlayer = Poker.FrameworkObject.GetPlayerFromId(playerId)
                xPlayer.addInventoryItem(Config.ChipsItem, money, {})
            end,
            ['custom'] = function(playerId, money)
                -- Add code for custom currency
            end
        }

        return currencryChecks[tableData.currency](source, amount)
    elseif Poker.FrameworkType == 'qbcore' then

        local currencryChecks = {
            ['cash'] = function(playerId, money)
                local ply = Poker.FrameworkObject.Functions.GetPlayer(playerId)
                ply.Functions.RemoveMoney('cash', money)
            end,
            ['chips'] = function(playerId, money)
                -- ------------------------------------------
                -- Example of item 'chips' implementation (using chips as inventory item):
                -- ------------------------------------------
                local ply = Poker.FrameworkObject.Functions.GetPlayer(playerId)
                ply.mergeItems(Config.ChipsItem, money)
            end,
            ['custom'] = function(playerId, money)
                -- Add code for custom currency
            end
        }
        return currencryChecks[tableData.currency](source, amount)
    end
end

Poker.GetPlayerName = function (source)
    if Poker.FrameworkType == 'esx' then
        local xPlayer = Poker.FrameworkObject.GetPlayerFromId(source)
        return xPlayer.getName and xPlayer.getName() or GetPlayerName(source)
    elseif Poker.FrameworkType == 'qbcore' then
        local ply = Poker.FrameworkObject.Functions.GetPlayer(source)
        if ply.PlayerData.charinfo?.firstname then
            return ply.PlayerData.charinfo.firstname
        else
            return ply.PlayerData.name
        end
    else
        return GetPlayerName(source)
    end
end

onServerCB(SVCB.FETCH_FRAMEWORK_RESOURCE_NAME, function (_, cb)
    cb(Poker.ResourceFolderName)
end)
```

{% endcode %}
{% endtab %}

{% tab title="client/framework.lua" %}

```lua
Poker = {}
Poker.FrameworkType = ''
Poker.FrameworkObject = nil

local function fetchSharedObject(resourceName, exportName, eventName)
    try(function()
        Poker.FrameworkObject = exports[resourceName][exportName]()
    end, function (err)
        print(err)
        TriggerEvent(eventName, function (Obj)
            Poker.FrameworkObject = Obj
        end)
        Citizen.CreateThread(function()
            Citizen.Wait(200)
            if not Poker.FrameworkObject then
                debugPrint('^9ERROR^7', -1, 'Framework loading failed. Given parameters:')
                debugPrint('^9ERROR^7', -1, ('Framework name: ^4%s^7, event name: ^4%s^7, export function name: ^4%s^7, used type: ^4%s^7'):format(Config.Framework, eventName, exportName, Poker.FrameworkType))
                debugPrint('NOTE', -1, 'If any of the parameters do not match your server settings please check your ^4*/framework.lua^7 files')
                Poker.FrameworkType = 'custom'
            end
        end)
    end)
end

Citizen.CreateThread(function()
    if Config.Framework == 'auto' then
        if GetResourceState('es_extended') == 'started' then
            Poker.FrameworkType = 'esx'
        elseif GetResourceState('qb-core') == 'started' then
            Poker.FrameworkType = 'qbcore'
        else
            Poker.FrameworkType = 'custom'
        end
    elseif Config.Framework == 'esx' then
        Poker.FrameworkType = 'esx'
    elseif Config.Framework == 'qbcore' then
        Poker.FrameworkType = 'qbcore'
    else
        Poker.FrameworkType = 'custom'
    end
    
    local evName = IsDuplicityVersion() and Config.FrameworkData.SharedObjectEventNameSV or Config.FrameworkData.SharedObjectEventNameCL
    if Poker.FrameworkType == 'esx' then
        emitNetCB(SVCB.FETCH_FRAMEWORK_RESOURCE_NAME, function (resName)
            print('resName', resName)
            fetchSharedObject(resName, 'getSharedObject', evName or 'esx:getSharedObject')
        end)
    elseif Poker.FrameworkType == 'qbcore' then
        emitNetCB(SVCB.FETCH_FRAMEWORK_RESOURCE_NAME, function (resName)
            fetchSharedObject(resName, 'GetCoreObject', evName or 'QBCore:GetObject')
        end)
    elseif Poker.FrameworkType == 'custom' then
        -- Add code for your custom framework
        debugPrint('^6ERROR^7', -1, 'Framework loading failed. No code for custom framework provided, check your ^4*/framework.lua^7 files')
    end
end)

Poker.ShowNotification = function(msg)
    if Config.Notifications == 'framework' then
        if Config.Framework == 'qbcore' or Config.Framework == 'qb' then
            
        elseif Config.Framework == 'esx' then
            Poker.FrameworkObject.ShowNotification(msg)
        end
    end
end

Poker.ShowButtonNotification = function(msg)
    if Config.Notifications == 'framework' and Poker.FrameworkObject then
        if Poker.FrameworkType == 'qbcore' then
            
        elseif Poker.FrameworkType == 'esx' then
            Poker.FrameworkObject.ShowHelpNotification(msg)
        end
    end
end

```

{% endtab %}
{% endtabs %}

## Locales

{% code title="configs/config.lua" %}

```lua
Strings = {
    ['en'] = {
        ['notification_sit_at_table'] = '[E] - Start playing',
        ['header_label_winners'] = 'Winners: %s',
        ['header_label_idle'] = 'Waiting...',
        ['header_label_waiting_new_game'] = 'Waiting for new game...',
        ['subheader_label_turn'] = 'Is current actor',
        ['subheader_label_not_enough_players'] = 'Not enough players',
        ['alert_not_actor'] = 'You are not the current actor',
        ['alert_action_not_legal'] = 'This action is not currently legal',
        ['alert_amount_invalid'] = 'The bet amount is not valid',
        ['alert_seat_taken'] = 'This seat is already taken, please choose another one',
    },
    ['lt'] = {
        ['notification_sit_at_table'] = '[E] - Pradėti žaisti',
        ['header_label_winners'] = 'Laimėjo: %s',
        ['header_label_idle'] = 'Laukiama žaidėjų...',
        ['header_label_waiting_new_game'] = 'Laukiama naujo žaidimo pradžios...',
        ['subheader_label_turn'] = 'Eilė',
        ['subheader_label_not_enough_players'] = 'Not enough players',
        ['alert_not_actor'] = 'You are not the current actor',
        ['alert_action_not_legal'] = 'This action is not currently legal',
        ['alert_amount_invalid'] = 'The bet amount is not valid',
        ['alert_seat_taken'] = 'This seat is already taken, please choose another one',
    },
}
```

{% endcode %}

{% code title="ui/build/configs/strings.js" lineNumbers="true" %}

```javascript
var STRINGS = {
  currencySign: '$',
  currencyString: 'dollars',
  controls: {
    check: 'Check',
    fold: 'Fold',
    raise: 'Raise',
    bet: 'Bet',
    call: 'Call',
    exit: 'Leave',
    confirm: 'Confirm',
    dealerAction: 'Dealer Action',
    betAmountPlaceholder: 'Bet amount',
    showCardsButtonLabel: 'Show Cards',
  },
  tableInfo: {
    windowLabel: 'Poker Table info',
    bigBlindLabel: 'Big Blind',
    smallBlindLabel: 'Small Blind',
    minBuyIn: 'Minimum Buy In',
    buyInInputPlaceHolder: 'Enter Buy In amount...',
    joinTableButtonLabel: 'Join Table',
    exitTableButtonLabel: 'Exit',
  }
};

```

{% endcode %}

## Changelog

#### v1.0.1

* Ability to switch camera modes between freemode, top view and default camera when at the table, by clicking newly added buttons on UI, this will let players to move the camera around if wanted;
* Cards models on table will now match the ones displayed in UI;
* Fixed a bug which did not allow players to rejoin table in some cases;
* Notifications fix;
* Rearranged UI center panel, should be more clean and will have a better resolution responsibility;&#x20;
* All not folded players cards will be displayed at the end of the game even without them clicking on "show cards" button.

#### QUICKFIX v1.0.1b

* Fixed the issue with player peds getting stuck in infinite loop after trying to sit at the table if table config was not having some of the values.

#### v1.0.2

Fixes:&#x20;

* Raising action in some cases used to get server stuck and ended up crashing it;&#x20;
* Player will now get properly removed from table if their bank gets to 0;&#x20;
* Debug level config fix;&#x20;
* Some incorrect qbcore functions usage.
