0.0.1
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
|
||||
-- Altering or recreating for local use only is strongly discouraged.
|
||||
|
||||
version '1.0.0'
|
||||
description 'An example money system fountain spawn point.'
|
||||
repository 'https://github.com/citizenfx/cfx-server-data'
|
||||
author 'Cfx.re <root@cfx.re>'
|
||||
|
||||
fx_version 'cerulean'
|
||||
game 'gta5'
|
||||
|
||||
map 'map.lua'
|
||||
|
||||
dependency 'money-fountain'
|
||||
@@ -0,0 +1,4 @@
|
||||
money_fountain 'test_fountain' {
|
||||
vector3(97.334, -973.621, 29.36),
|
||||
amount = 75
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
-- add text entries for all the help types we have
|
||||
AddTextEntry('FOUNTAIN_HELP', 'This fountain currently contains $~1~.~n~Press ~INPUT_PICKUP~ to obtain $~1~.~n~Press ~INPUT_DETONATE~ to place $~1~.')
|
||||
AddTextEntry('FOUNTAIN_HELP_DRAINED', 'This fountain currently contains $~1~.~n~Press ~INPUT_DETONATE~ to place $~1~.')
|
||||
AddTextEntry('FOUNTAIN_HELP_BROKE', 'This fountain currently contains $~1~.~n~Press ~INPUT_PICKUP~ to obtain $~1~.')
|
||||
AddTextEntry('FOUNTAIN_HELP_BROKE_N_DRAINED', 'This fountain currently contains $~1~.')
|
||||
AddTextEntry('FOUNTAIN_HELP_INUSE', 'This fountain currently contains $~1~.~n~You can use it again in ~a~.')
|
||||
|
||||
-- upvalue aliases so that we will be fast if far away
|
||||
local Wait = Wait
|
||||
local GetEntityCoords = GetEntityCoords
|
||||
local PlayerPedId = PlayerPedId
|
||||
|
||||
-- timer, don't tick as frequently if we're far from any money fountain
|
||||
local relevanceTimer = 500
|
||||
|
||||
CreateThread(function()
|
||||
local pressing = false
|
||||
|
||||
while true do
|
||||
Wait(relevanceTimer)
|
||||
|
||||
local coords = GetEntityCoords(PlayerPedId())
|
||||
|
||||
for _, data in pairs(moneyFountains) do
|
||||
-- if we're near this fountain
|
||||
local dist = #(coords - data.coords)
|
||||
|
||||
-- near enough to draw
|
||||
if dist < 40 then
|
||||
-- ensure per-frame tick
|
||||
relevanceTimer = 0
|
||||
|
||||
DrawMarker(29, data.coords.x, data.coords.y, data.coords.z, 0, 0, 0, 0.0, 0, 0, 1.0, 1.0, 1.0, 0, 150, 0, 120, false, true, 2, false, nil, nil, false)
|
||||
else
|
||||
-- put the relevance timer back to the way it was
|
||||
relevanceTimer = 500
|
||||
end
|
||||
|
||||
-- near enough to use
|
||||
if dist < 1 then
|
||||
-- are we able to use it? if not, display appropriate help
|
||||
local player = LocalPlayer
|
||||
local nextUse = player.state['fountain_nextUse']
|
||||
|
||||
-- GetNetworkTime is synced for everyone
|
||||
if nextUse and nextUse >= GetNetworkTime() then
|
||||
BeginTextCommandDisplayHelp('FOUNTAIN_HELP_INUSE')
|
||||
AddTextComponentInteger(GlobalState['fountain_' .. data.id])
|
||||
AddTextComponentSubstringTime(math.tointeger(nextUse - GetNetworkTime()), 2 | 4) -- seconds (2), minutes (4)
|
||||
EndTextCommandDisplayHelp(0, false, false, 1000)
|
||||
else
|
||||
-- handle inputs for pickup/place
|
||||
if not pressing then
|
||||
if IsControlPressed(0, 38 --[[ INPUT_PICKUP ]]) then
|
||||
TriggerServerEvent('money_fountain:tryPickup', data.id)
|
||||
pressing = true
|
||||
elseif IsControlPressed(0, 47 --[[ INPUT_DETONATE ]]) then
|
||||
TriggerServerEvent('money_fountain:tryPlace', data.id)
|
||||
pressing = true
|
||||
end
|
||||
else
|
||||
if not IsControlPressed(0, 38 --[[ INPUT_PICKUP ]]) and
|
||||
not IsControlPressed(0, 47 --[[ INPUT_DETONATE ]]) then
|
||||
pressing = false
|
||||
end
|
||||
end
|
||||
|
||||
-- decide the appropriate help message
|
||||
local youCanSpend = (player.state['money_cash'] or 0) >= data.amount
|
||||
local fountainCanSpend = GlobalState['fountain_' .. data.id] >= data.amount
|
||||
|
||||
local helpName
|
||||
|
||||
if youCanSpend and fountainCanSpend then
|
||||
helpName = 'FOUNTAIN_HELP'
|
||||
elseif youCanSpend and not fountainCanSpend then
|
||||
helpName = 'FOUNTAIN_HELP_DRAINED'
|
||||
elseif not youCanSpend and fountainCanSpend then
|
||||
helpName = 'FOUNTAIN_HELP_BROKE'
|
||||
else
|
||||
helpName = 'FOUNTAIN_HELP_BROKE_N_DRAINED'
|
||||
end
|
||||
|
||||
-- and print it
|
||||
BeginTextCommandDisplayHelp(helpName)
|
||||
AddTextComponentInteger(GlobalState['fountain_' .. data.id])
|
||||
|
||||
if fountainCanSpend then
|
||||
AddTextComponentInteger(data.amount)
|
||||
end
|
||||
|
||||
if youCanSpend then
|
||||
AddTextComponentInteger(data.amount)
|
||||
end
|
||||
|
||||
EndTextCommandDisplayHelp(0, false, false, 1000)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,22 @@
|
||||
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
|
||||
-- Altering or recreating for local use only is strongly discouraged.
|
||||
|
||||
version '1.0.0'
|
||||
description 'An example money system client containing a money fountain.'
|
||||
repository 'https://github.com/citizenfx/cfx-server-data'
|
||||
author 'Cfx.re <root@cfx.re>'
|
||||
|
||||
fx_version 'bodacious'
|
||||
game 'gta5'
|
||||
|
||||
client_script 'client.lua'
|
||||
server_script 'server.lua'
|
||||
|
||||
shared_script 'mapdata.lua'
|
||||
|
||||
dependencies {
|
||||
'mapmanager',
|
||||
'money'
|
||||
}
|
||||
|
||||
lua54 'yes'
|
||||
@@ -0,0 +1,28 @@
|
||||
-- define the money fountain list (SHARED SCRIPT)
|
||||
moneyFountains = {}
|
||||
|
||||
-- index to know what to remove
|
||||
local fountainIdx = 1
|
||||
|
||||
AddEventHandler('getMapDirectives', function(add)
|
||||
-- add a 'money_fountain' map directive
|
||||
add('money_fountain', function(state, name)
|
||||
return function(data)
|
||||
local coords = data[1]
|
||||
local amount = data.amount or 100
|
||||
|
||||
local idx = fountainIdx
|
||||
fountainIdx += 1
|
||||
|
||||
moneyFountains[idx] = {
|
||||
id = name,
|
||||
coords = coords,
|
||||
amount = amount
|
||||
}
|
||||
|
||||
state.add('idx', idx)
|
||||
end
|
||||
end, function(state)
|
||||
moneyFountains[state.idx] = nil
|
||||
end)
|
||||
end)
|
||||
@@ -0,0 +1,107 @@
|
||||
-- track down what we've added to global state
|
||||
local sentState = {}
|
||||
|
||||
-- money system
|
||||
local ms = exports['money']
|
||||
|
||||
-- get the fountain content from storage
|
||||
local function getMoneyForId(fountainId)
|
||||
return GetResourceKvpInt(('money:%s'):format(fountainId)) / 100.0
|
||||
end
|
||||
|
||||
-- set the fountain content in storage + state
|
||||
local function setMoneyForId(fountainId, money)
|
||||
GlobalState['fountain_' .. fountainId] = math.tointeger(money)
|
||||
|
||||
return SetResourceKvpInt(('money:%s'):format(fountainId), math.tointeger(money * 100.0))
|
||||
end
|
||||
|
||||
-- get the nearest fountain to the player + ID
|
||||
local function getMoneyFountain(id, source)
|
||||
local coords = GetEntityCoords(GetPlayerPed(source))
|
||||
|
||||
for _, v in pairs(moneyFountains) do
|
||||
if v.id == id then
|
||||
if #(v.coords - coords) < 2.5 then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- generic function for events
|
||||
local function handleFountainStuff(source, id, pickup)
|
||||
-- if near the fountain we specify
|
||||
local fountain = getMoneyFountain(id, source)
|
||||
|
||||
if fountain then
|
||||
-- and we can actually use the fountain already
|
||||
local player = Player(source)
|
||||
|
||||
local nextUse = player.state['fountain_nextUse']
|
||||
if not nextUse then
|
||||
nextUse = 0
|
||||
end
|
||||
|
||||
-- GetGameTimer ~ GetNetworkTime on client
|
||||
if nextUse <= GetGameTimer() then
|
||||
-- not rate limited
|
||||
local success = false
|
||||
local money = getMoneyForId(fountain.id)
|
||||
|
||||
-- decide the op
|
||||
if pickup then
|
||||
-- if the fountain is rich enough to get the per-use amount
|
||||
if money >= fountain.amount then
|
||||
-- give the player money
|
||||
if ms:addMoney(source, 'cash', fountain.amount) then
|
||||
money -= fountain.amount
|
||||
success = true
|
||||
end
|
||||
end
|
||||
else
|
||||
-- if the player is rich enough
|
||||
if ms:removeMoney(source, 'cash', fountain.amount) then
|
||||
-- add to the fountain
|
||||
money += fountain.amount
|
||||
success = true
|
||||
end
|
||||
end
|
||||
|
||||
-- save it and set the player's cooldown
|
||||
if success then
|
||||
setMoneyForId(fountain.id, money)
|
||||
player.state['fountain_nextUse'] = GetGameTimer() + GetConvarInt('moneyFountain_cooldown', 5000)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- event for picking up fountain->player
|
||||
RegisterNetEvent('money_fountain:tryPickup')
|
||||
AddEventHandler('money_fountain:tryPickup', function(id)
|
||||
handleFountainStuff(source, id, true)
|
||||
end)
|
||||
|
||||
-- event for donating player->fountain
|
||||
RegisterNetEvent('money_fountain:tryPlace')
|
||||
AddEventHandler('money_fountain:tryPlace', function(id)
|
||||
handleFountainStuff(source, id, false)
|
||||
end)
|
||||
|
||||
-- listener: if a new fountain is added, set its current money in state
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(500)
|
||||
|
||||
for _, fountain in pairs(moneyFountains) do
|
||||
if not sentState[fountain.id] then
|
||||
GlobalState['fountain_' .. fountain.id] = math.tointeger(getMoneyForId(fountain.id))
|
||||
|
||||
sentState[fountain.id] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,30 @@
|
||||
local moneyTypes = {
|
||||
cash = `MP0_WALLET_BALANCE`,
|
||||
bank = `BANK_BALANCE`,
|
||||
}
|
||||
|
||||
RegisterNetEvent('money:displayUpdate')
|
||||
|
||||
AddEventHandler('money:displayUpdate', function(type, money)
|
||||
local stat = moneyTypes[type]
|
||||
if not stat then return end
|
||||
StatSetInt(stat, math.floor(money))
|
||||
end)
|
||||
|
||||
TriggerServerEvent('money:requestDisplay')
|
||||
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(0)
|
||||
|
||||
if IsControlJustPressed(0, 20) then
|
||||
SetMultiplayerBankCash()
|
||||
SetMultiplayerWalletCash()
|
||||
|
||||
Wait(4350)
|
||||
|
||||
RemoveMultiplayerBankCash()
|
||||
RemoveMultiplayerWalletCash()
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,16 @@
|
||||
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
|
||||
-- Altering or recreating for local use only is strongly discouraged.
|
||||
|
||||
version '1.0.0'
|
||||
description 'An example money system using KVS.'
|
||||
repository 'https://github.com/citizenfx/cfx-server-data'
|
||||
author 'Cfx.re <root@cfx.re>'
|
||||
|
||||
fx_version 'bodacious'
|
||||
game 'gta5'
|
||||
|
||||
client_script 'client.lua'
|
||||
server_script 'server.lua'
|
||||
|
||||
--dependency 'cfx.re/playerData.v1alpha1'
|
||||
lua54 'yes'
|
||||
119
resources/[cfx-default]/[gameplay]/[examples]/money/server.lua
Normal file
119
resources/[cfx-default]/[gameplay]/[examples]/money/server.lua
Normal file
@@ -0,0 +1,119 @@
|
||||
local playerData = exports['cfx.re/playerData.v1alpha1']
|
||||
|
||||
local validMoneyTypes = {
|
||||
bank = true,
|
||||
cash = true,
|
||||
}
|
||||
|
||||
local function getMoneyForId(playerId, moneyType)
|
||||
return GetResourceKvpInt(('money:%s:%s'):format(playerId, moneyType)) / 100.0
|
||||
end
|
||||
|
||||
local function setMoneyForId(playerId, moneyType, money)
|
||||
local s = playerData:getPlayerById(playerId)
|
||||
|
||||
TriggerEvent('money:updated', {
|
||||
dbId = playerId,
|
||||
source = s,
|
||||
moneyType = moneyType,
|
||||
money = money
|
||||
})
|
||||
|
||||
return SetResourceKvpInt(('money:%s:%s'):format(playerId, moneyType), math.tointeger(money * 100.0))
|
||||
end
|
||||
|
||||
local function addMoneyForId(playerId, moneyType, amount)
|
||||
local curMoney = getMoneyForId(playerId, moneyType)
|
||||
curMoney += amount
|
||||
|
||||
if curMoney >= 0 then
|
||||
setMoneyForId(playerId, moneyType, curMoney)
|
||||
return true, curMoney
|
||||
end
|
||||
|
||||
return false, 0
|
||||
end
|
||||
|
||||
exports('addMoney', function(playerIdx, moneyType, amount)
|
||||
amount = tonumber(amount)
|
||||
|
||||
if amount <= 0 or amount > (1 << 30) then
|
||||
return false
|
||||
end
|
||||
|
||||
if not validMoneyTypes[moneyType] then
|
||||
return false
|
||||
end
|
||||
|
||||
local playerId = playerData:getPlayerId(playerIdx)
|
||||
local success, money = addMoneyForId(playerId, moneyType, amount)
|
||||
|
||||
if success then
|
||||
Player(playerIdx).state['money_' .. moneyType] = money
|
||||
end
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
exports('removeMoney', function(playerIdx, moneyType, amount)
|
||||
amount = tonumber(amount)
|
||||
|
||||
if amount <= 0 or amount > (1 << 30) then
|
||||
return false
|
||||
end
|
||||
|
||||
if not validMoneyTypes[moneyType] then
|
||||
return false
|
||||
end
|
||||
|
||||
local playerId = playerData:getPlayerId(playerIdx)
|
||||
local success, money = addMoneyForId(playerId, moneyType, -amount)
|
||||
|
||||
if success then
|
||||
Player(playerIdx).state['money_' .. moneyType] = money
|
||||
end
|
||||
|
||||
return success
|
||||
end)
|
||||
|
||||
exports('getMoney', function(playerIdx, moneyType)
|
||||
local playerId = playerData:getPlayerId(playerIdx)
|
||||
return getMoneyForId(playerId, moneyType)
|
||||
end)
|
||||
|
||||
-- player display bits
|
||||
AddEventHandler('money:updated', function(data)
|
||||
if data.source then
|
||||
TriggerClientEvent('money:displayUpdate', data.source, data.moneyType, data.money)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('money:requestDisplay')
|
||||
|
||||
AddEventHandler('money:requestDisplay', function()
|
||||
local source = source
|
||||
local playerId = playerData:getPlayerId(source)
|
||||
|
||||
for type, _ in pairs(validMoneyTypes) do
|
||||
local amount = getMoneyForId(playerId, type)
|
||||
TriggerClientEvent('money:displayUpdate', source, type, amount)
|
||||
|
||||
Player(source).state['money_' .. type] = amount
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterCommand('earn', function(source, args)
|
||||
local type = args[1]
|
||||
local amount = tonumber(args[2])
|
||||
|
||||
exports['money']:addMoney(source, type, amount)
|
||||
end, true)
|
||||
|
||||
RegisterCommand('spend', function(source, args)
|
||||
local type = args[1]
|
||||
local amount = tonumber(args[2])
|
||||
|
||||
if not exports['money']:removeMoney(source, type, amount) then
|
||||
print('you are broke??')
|
||||
end
|
||||
end, true)
|
||||
@@ -0,0 +1,41 @@
|
||||
AddEventHandler('gameEventTriggered', function(eventName, args)
|
||||
if eventName == 'CEventNetworkEntityDamage' then
|
||||
local victim = args[1]
|
||||
local culprit = args[2]
|
||||
local isDead = args[4] == 1
|
||||
|
||||
if isDead then
|
||||
local origCoords = GetEntityCoords(victim)
|
||||
local pickup = CreatePickupRotate(`PICKUP_MONEY_VARIABLE`, origCoords.x, origCoords.y, origCoords.z - 0.7, 0.0, 0.0, 0.0, 512, 0, false, 0)
|
||||
local netId = PedToNet(victim)
|
||||
|
||||
local undoStuff = { false }
|
||||
|
||||
CreateThread(function()
|
||||
local self = PlayerPedId()
|
||||
|
||||
while not undoStuff[1] do
|
||||
Wait(50)
|
||||
|
||||
if #(GetEntityCoords(self) - origCoords) < 2.5 and HasPickupBeenCollected(pickup) then
|
||||
TriggerServerEvent('money:tryPickup', netId)
|
||||
|
||||
RemovePickup(pickup)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
undoStuff[1] = true
|
||||
end)
|
||||
|
||||
SetTimeout(15000, function()
|
||||
if not undoStuff[1] then
|
||||
RemovePickup(pickup)
|
||||
undoStuff[1] = true
|
||||
end
|
||||
end)
|
||||
|
||||
TriggerServerEvent('money:allowPickupNear', netId)
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -0,0 +1,15 @@
|
||||
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
|
||||
-- Altering or recreating for local use only is strongly discouraged.
|
||||
|
||||
version '1.0.0'
|
||||
description 'An example money system client.'
|
||||
author 'Cfx.re <root@cfx.re>'
|
||||
repository 'https://github.com/citizenfx/cfx-server-data'
|
||||
|
||||
fx_version 'bodacious'
|
||||
game 'gta5'
|
||||
|
||||
client_script 'client.lua'
|
||||
server_script 'server.lua'
|
||||
|
||||
lua54 'yes'
|
||||
@@ -0,0 +1,42 @@
|
||||
local safePositions = {}
|
||||
|
||||
RegisterNetEvent('money:allowPickupNear')
|
||||
|
||||
AddEventHandler('money:allowPickupNear', function(pedId)
|
||||
local entity = NetworkGetEntityFromNetworkId(pedId)
|
||||
|
||||
Wait(250)
|
||||
|
||||
if not DoesEntityExist(entity) then
|
||||
return
|
||||
end
|
||||
|
||||
if GetEntityHealth(entity) > 100 then
|
||||
return
|
||||
end
|
||||
|
||||
local coords = GetEntityCoords(entity)
|
||||
safePositions[pedId] = coords
|
||||
end)
|
||||
|
||||
RegisterNetEvent('money:tryPickup')
|
||||
|
||||
AddEventHandler('money:tryPickup', function(entity)
|
||||
if not safePositions[entity] then
|
||||
return
|
||||
end
|
||||
|
||||
local source = source
|
||||
local playerPed = GetPlayerPed(source)
|
||||
local coords = GetEntityCoords(playerPed)
|
||||
|
||||
if #(safePositions[entity] - coords) < 2.5 then
|
||||
exports['money']:addMoney(source, 'cash', 40)
|
||||
end
|
||||
|
||||
safePositions[entity] = nil
|
||||
end)
|
||||
|
||||
AddEventHandler('entityRemoved', function(entity)
|
||||
safePositions[entity] = nil
|
||||
end)
|
||||
@@ -0,0 +1,21 @@
|
||||
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
|
||||
-- Altering or recreating for local use only is strongly discouraged.
|
||||
|
||||
version '1.0.0'
|
||||
author 'Cfx.re <root@cfx.re>'
|
||||
description 'A GTA Online-styled theme for the chat resource.'
|
||||
repository 'https://github.com/citizenfx/cfx-server-data'
|
||||
|
||||
file 'style.css'
|
||||
file 'shadow.js'
|
||||
|
||||
chat_theme 'gtao' {
|
||||
styleSheet = 'style.css',
|
||||
script = 'shadow.js',
|
||||
msgTemplates = {
|
||||
default = '<b>{0}</b><span>{1}</span>'
|
||||
}
|
||||
}
|
||||
|
||||
game 'common'
|
||||
fx_version 'adamant'
|
||||
74
resources/[cfx-default]/[gameplay]/chat-theme-gtao/shadow.js
Normal file
74
resources/[cfx-default]/[gameplay]/chat-theme-gtao/shadow.js
Normal file
@@ -0,0 +1,74 @@
|
||||
(function() {
|
||||
var Filters = {}
|
||||
|
||||
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
svg.setAttribute("style", "display:block;width:0px;height:0px");
|
||||
var defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
|
||||
|
||||
var blurFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
|
||||
blurFilter.setAttribute("id", "svgBlurFilter");
|
||||
var feGaussianFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
||||
feGaussianFilter.setAttribute("stdDeviation", "0 0");
|
||||
blurFilter.appendChild(feGaussianFilter);
|
||||
defs.appendChild(blurFilter);
|
||||
Filters._svgBlurFilter = feGaussianFilter;
|
||||
|
||||
// Drop Shadow Filter
|
||||
var dropShadowFilter = document.createElementNS("http://www.w3.org/2000/svg", "filter");
|
||||
dropShadowFilter.setAttribute("id", "svgDropShadowFilter");
|
||||
var feGaussianFilter = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur");
|
||||
feGaussianFilter.setAttribute("in", "SourceAlpha");
|
||||
feGaussianFilter.setAttribute("stdDeviation", "3");
|
||||
dropShadowFilter.appendChild(feGaussianFilter);
|
||||
Filters._svgDropshadowFilterBlur = feGaussianFilter;
|
||||
|
||||
var feOffset = document.createElementNS("http://www.w3.org/2000/svg", "feOffset");
|
||||
feOffset.setAttribute("dx", "0");
|
||||
feOffset.setAttribute("dy", "0");
|
||||
feOffset.setAttribute("result", "offsetblur");
|
||||
dropShadowFilter.appendChild(feOffset);
|
||||
Filters._svgDropshadowFilterOffset = feOffset;
|
||||
|
||||
var feFlood = document.createElementNS("http://www.w3.org/2000/svg", "feFlood");
|
||||
feFlood.setAttribute("flood-color", "rgba(0,0,0,1)");
|
||||
dropShadowFilter.appendChild(feFlood);
|
||||
Filters._svgDropshadowFilterFlood = feFlood;
|
||||
|
||||
var feComposite = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
|
||||
feComposite.setAttribute("in2", "offsetblur");
|
||||
feComposite.setAttribute("operator", "in");
|
||||
dropShadowFilter.appendChild(feComposite);
|
||||
var feComposite = document.createElementNS("http://www.w3.org/2000/svg", "feComposite");
|
||||
feComposite.setAttribute("in2", "SourceAlpha");
|
||||
feComposite.setAttribute("operator", "out");
|
||||
feComposite.setAttribute("result", "outer");
|
||||
dropShadowFilter.appendChild(feComposite);
|
||||
|
||||
var feMerge = document.createElementNS("http://www.w3.org/2000/svg", "feMerge");
|
||||
var feMergeNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
feMerge.appendChild(feMergeNode);
|
||||
var feMergeNode = document.createElementNS("http://www.w3.org/2000/svg", "feMergeNode");
|
||||
feMerge.appendChild(feMergeNode);
|
||||
Filters._svgDropshadowMergeNode = feMergeNode;
|
||||
dropShadowFilter.appendChild(feMerge);
|
||||
defs.appendChild(dropShadowFilter);
|
||||
svg.appendChild(defs);
|
||||
document.documentElement.appendChild(svg);
|
||||
|
||||
const blurScale = 1;
|
||||
const scale = (document.body.clientWidth / 1280);
|
||||
|
||||
Filters._svgDropshadowFilterBlur.setAttribute("stdDeviation",
|
||||
1 * blurScale + " " +
|
||||
1 * blurScale
|
||||
);
|
||||
Filters._svgDropshadowFilterOffset.setAttribute("dx",
|
||||
String(Math.cos(45 * Math.PI / 180) * 1 * scale));
|
||||
Filters._svgDropshadowFilterOffset.setAttribute("dy",
|
||||
String(Math.sin(45 * Math.PI / 180) * 1 * scale));
|
||||
Filters._svgDropshadowFilterFlood.setAttribute("flood-color",
|
||||
'rgba(0, 0, 0, 1)');
|
||||
Filters._svgDropshadowMergeNode.setAttribute("in",
|
||||
"SourceGraphic");
|
||||
|
||||
})();
|
||||
141
resources/[cfx-default]/[gameplay]/chat-theme-gtao/style.css
Normal file
141
resources/[cfx-default]/[gameplay]/chat-theme-gtao/style.css
Normal file
@@ -0,0 +1,141 @@
|
||||
* {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.chat-window {
|
||||
--size: calc(((2.7vh * 1.2)) * 6);
|
||||
|
||||
position: absolute;
|
||||
right: calc(2.77vh);
|
||||
top: calc(50% - (var(--size) / 2));
|
||||
height: var(--size) !important;
|
||||
|
||||
background: inherit !important;
|
||||
|
||||
text-align: right;
|
||||
|
||||
left: auto;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font2';
|
||||
src: url(https://runtime.fivem.net/temp/ChaletLondonNineteenSixty.otf?a);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font2_cond';
|
||||
src: url(https://runtime.fivem.net/temp/chaletcomprime-colognesixty-webfont.ttf?a);
|
||||
}
|
||||
|
||||
.msg {
|
||||
font-family: Font2, sans-serif;
|
||||
color: #fff;
|
||||
|
||||
font-size: calc(1.8vh); /* 13px in 720p, calc'd by width */
|
||||
filter: url(#svgDropShadowFilter);
|
||||
|
||||
line-height: calc(2.7vh * 1.2);
|
||||
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.msg > span > span > b {
|
||||
font-family: Font2_cond, sans-serif;
|
||||
font-weight: normal;
|
||||
|
||||
vertical-align: baseline;
|
||||
padding-right: 11px;
|
||||
|
||||
line-height: 1;
|
||||
|
||||
font-size: calc(2.7vh);
|
||||
}
|
||||
|
||||
.msg > span > span > span {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.msg i:first-of-type {
|
||||
font-style: normal;
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
position: absolute;
|
||||
right: calc(2.77vh);
|
||||
bottom: calc(2.77vh);
|
||||
|
||||
background: inherit !important;
|
||||
|
||||
text-align: right;
|
||||
|
||||
top: auto;
|
||||
left: auto;
|
||||
|
||||
height: auto;
|
||||
|
||||
font-family: Font2, sans-serif;
|
||||
}
|
||||
|
||||
.chat-input > div {
|
||||
background-color: rgba(0, 0, 0, .6) !important;
|
||||
border: calc(0.28vh / 2) solid rgba(180, 180, 180, .6);
|
||||
outline: calc(0.28vh / 2) solid rgba(0, 0, 0, .8); /* to replace margin-background */
|
||||
padding: calc(0.28vh / 2);
|
||||
}
|
||||
|
||||
.chat-input .prefix {
|
||||
margin: 0;
|
||||
margin-left: 0.7%;
|
||||
margin-top: -0.1%;
|
||||
line-height: 2.8vh;
|
||||
}
|
||||
|
||||
.chat-input .prefix.any {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.chat-input .prefix.any:before {
|
||||
content: '[';
|
||||
}
|
||||
|
||||
.chat-input .prefix.any:after {
|
||||
content: ']';
|
||||
}
|
||||
|
||||
.chat-input > div + div {
|
||||
position: absolute;
|
||||
bottom: calc(1.65vh + 0.28vh + 0.28vh + 0.28vh + (0.28vh / 2));
|
||||
width: 99.6%;
|
||||
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.suggestions {
|
||||
border: calc(0.28vh / 2) solid rgba(180, 180, 180, .6);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
textarea {
|
||||
background: transparent;
|
||||
padding: 0.5vh;
|
||||
}
|
||||
|
||||
@media screen and (min-aspect-ratio: 21/9) {
|
||||
.chat-window, .chat-input {
|
||||
right: calc(12.8vw);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-aspect-ratio: 32/9) {
|
||||
.chat-window, .chat-input {
|
||||
right: calc(25vw);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
|
||||
-- Altering or recreating for local use only is strongly discouraged.
|
||||
|
||||
version '1.0.0'
|
||||
description 'A basic resource for storing player identifiers.'
|
||||
author 'Cfx.re <root@cfx.re>'
|
||||
repository 'https://github.com/citizenfx/cfx-server-data'
|
||||
|
||||
fx_version 'bodacious'
|
||||
game 'common'
|
||||
|
||||
server_script 'server.lua'
|
||||
|
||||
provides {
|
||||
'cfx.re/playerData.v1alpha1'
|
||||
}
|
||||
222
resources/[cfx-default]/[gameplay]/player-data/server.lua
Normal file
222
resources/[cfx-default]/[gameplay]/player-data/server.lua
Normal file
@@ -0,0 +1,222 @@
|
||||
--- player-data is a basic resource to showcase player identifier storage
|
||||
--
|
||||
-- it works in a fairly simple way: a set of identifiers is assigned to an account ID, and said
|
||||
-- account ID is then returned/added as state bag
|
||||
--
|
||||
-- it also implements the `cfx.re/playerData.v1alpha1` spec, which is exposed through the following:
|
||||
-- - getPlayerId(source: string)
|
||||
-- - getPlayerById(dbId: string)
|
||||
-- - getPlayerIdFromIdentifier(identifier: string)
|
||||
-- - setting `cfx.re/playerData@id` state bag field on the player
|
||||
|
||||
-- identifiers that we'll ignore (e.g. IP) as they're low-trust/high-variance
|
||||
local identifierBlocklist = {
|
||||
ip = true
|
||||
}
|
||||
|
||||
-- function to check if the identifier is blocked
|
||||
local function isIdentifierBlocked(identifier)
|
||||
-- Lua pattern to correctly split
|
||||
local idType = identifier:match('([^:]+):')
|
||||
|
||||
-- ensure it's a boolean
|
||||
return identifierBlocklist[idType] or false
|
||||
end
|
||||
|
||||
-- our database schema, in hierarchical KVS syntax:
|
||||
-- player:
|
||||
-- <id>:
|
||||
-- identifier:
|
||||
-- <identifier>: 'true'
|
||||
-- identifier:
|
||||
-- <identifier>: <playerId>
|
||||
|
||||
-- list of player indices to data
|
||||
local players = {}
|
||||
|
||||
-- list of player DBIDs to player indices
|
||||
local playersById = {}
|
||||
|
||||
-- a sequence field using KVS
|
||||
local function incrementId()
|
||||
local nextId = GetResourceKvpInt('nextId')
|
||||
nextId = nextId + 1
|
||||
SetResourceKvpInt('nextId', nextId)
|
||||
|
||||
return nextId
|
||||
end
|
||||
|
||||
-- gets the ID tied to an identifier in the schema, or nil
|
||||
local function getPlayerIdFromIdentifier(identifier)
|
||||
local str = GetResourceKvpString(('identifier:%s'):format(identifier))
|
||||
|
||||
if not str then
|
||||
return nil
|
||||
end
|
||||
|
||||
return msgpack.unpack(str).id
|
||||
end
|
||||
|
||||
-- stores the identifier + adds to a logging list
|
||||
local function setPlayerIdFromIdentifier(identifier, id)
|
||||
local str = ('identifier:%s'):format(identifier)
|
||||
SetResourceKvp(str, msgpack.pack({ id = id }))
|
||||
SetResourceKvp(('player:%s:identifier:%s'):format(id, identifier), 'true')
|
||||
end
|
||||
|
||||
-- stores any new identifiers for this player ID
|
||||
local function storeIdentifiers(playerIdx, newId)
|
||||
for _, identifier in ipairs(GetPlayerIdentifiers(playerIdx)) do
|
||||
if not isIdentifierBlocked(identifier) then
|
||||
-- TODO: check if the player already has an identifier of this type
|
||||
setPlayerIdFromIdentifier(identifier, newId)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- registers a new player (increments sequence, stores data, returns ID)
|
||||
local function registerPlayer(playerIdx)
|
||||
local newId = incrementId()
|
||||
storeIdentifiers(playerIdx, newId)
|
||||
|
||||
return newId
|
||||
end
|
||||
|
||||
-- initializes a player's data set
|
||||
local function setupPlayer(playerIdx)
|
||||
-- try getting the oldest-known identity from all the player's identifiers
|
||||
local defaultId = 0xFFFFFFFFFF
|
||||
local lowestId = defaultId
|
||||
|
||||
for _, identifier in ipairs(GetPlayerIdentifiers(playerIdx)) do
|
||||
if not isIdentifierBlocked(identifier) then
|
||||
local dbId = getPlayerIdFromIdentifier(identifier)
|
||||
|
||||
if dbId then
|
||||
if dbId < lowestId then
|
||||
lowestId = dbId
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if this is the default ID, register. if not, update
|
||||
local playerId
|
||||
|
||||
if lowestId == defaultId then
|
||||
playerId = registerPlayer(playerIdx)
|
||||
else
|
||||
storeIdentifiers(playerIdx, lowestId)
|
||||
playerId = lowestId
|
||||
end
|
||||
|
||||
-- add state bag field
|
||||
if Player then
|
||||
Player(playerIdx).state['cfx.re/playerData@id'] = playerId
|
||||
end
|
||||
|
||||
-- and add to our caching tables
|
||||
players[playerIdx] = {
|
||||
dbId = playerId
|
||||
}
|
||||
|
||||
playersById[tostring(playerId)] = playerIdx
|
||||
end
|
||||
|
||||
-- we want to add a player pretty early
|
||||
AddEventHandler('playerConnecting', function()
|
||||
local playerIdx = tostring(source)
|
||||
setupPlayer(playerIdx)
|
||||
end)
|
||||
|
||||
-- and migrate them to a 'joining' ID where possible
|
||||
RegisterNetEvent('playerJoining')
|
||||
|
||||
AddEventHandler('playerJoining', function(oldIdx)
|
||||
-- resource restart race condition
|
||||
local oldPlayer = players[tostring(oldIdx)]
|
||||
|
||||
if oldPlayer then
|
||||
players[tostring(source)] = oldPlayer
|
||||
players[tostring(oldIdx)] = nil
|
||||
else
|
||||
setupPlayer(tostring(source))
|
||||
end
|
||||
end)
|
||||
|
||||
-- remove them if they're dropped
|
||||
AddEventHandler('playerDropped', function()
|
||||
local player = players[tostring(source)]
|
||||
|
||||
if player then
|
||||
playersById[tostring(player.dbId)] = nil
|
||||
end
|
||||
|
||||
players[tostring(source)] = nil
|
||||
end)
|
||||
|
||||
-- and when the resource is restarted, set up all players that are on right now
|
||||
for _, player in ipairs(GetPlayers()) do
|
||||
setupPlayer(player)
|
||||
end
|
||||
|
||||
-- also a quick command to get the current state
|
||||
RegisterCommand('playerData', function(source, args)
|
||||
if not args[1] then
|
||||
print('Usage:')
|
||||
print('\tplayerData getId <dbId>: gets identifiers for ID')
|
||||
print('\tplayerData getIdentifier <identifier>: gets ID for identifier')
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if args[1] == 'getId' then
|
||||
local prefix = ('player:%s:identifier:'):format(args[2])
|
||||
local handle = StartFindKvp(prefix)
|
||||
local key
|
||||
|
||||
repeat
|
||||
key = FindKvp(handle)
|
||||
|
||||
if key then
|
||||
print('result:', key:sub(#prefix + 1))
|
||||
end
|
||||
until not key
|
||||
|
||||
EndFindKvp(handle)
|
||||
elseif args[1] == 'getIdentifier' then
|
||||
print('result:', getPlayerIdFromIdentifier(args[2]))
|
||||
end
|
||||
end, true)
|
||||
|
||||
-- COMPATIBILITY for server versions that don't export provide
|
||||
local function getExportEventName(resource, name)
|
||||
return string.format('__cfx_export_%s_%s', resource, name)
|
||||
end
|
||||
|
||||
function AddExport(name, fn)
|
||||
if not Citizen.Traits or not Citizen.Traits.ProvidesExports then
|
||||
AddEventHandler(getExportEventName('cfx.re/playerData.v1alpha1', name), function(setCB)
|
||||
setCB(fn)
|
||||
end)
|
||||
end
|
||||
|
||||
exports(name, fn)
|
||||
end
|
||||
|
||||
-- exports
|
||||
AddExport('getPlayerIdFromIdentifier', getPlayerIdFromIdentifier)
|
||||
|
||||
AddExport('getPlayerId', function(playerIdx)
|
||||
local player = players[tostring(playerIdx)]
|
||||
|
||||
if not player then
|
||||
return nil
|
||||
end
|
||||
|
||||
return player.dbId
|
||||
end)
|
||||
|
||||
AddExport('getPlayerById', function(playerId)
|
||||
return playersById[tostring(playerId)]
|
||||
end)
|
||||
@@ -0,0 +1,36 @@
|
||||
-- This resource is part of the default Cfx.re asset pack (cfx-server-data)
|
||||
-- Altering or recreating for local use only is strongly discouraged.
|
||||
|
||||
version '1.0.0'
|
||||
author 'Cfx.re <root@cfx.re>'
|
||||
description 'A basic resource for displaying player names.'
|
||||
repository 'https://github.com/citizenfx/cfx-server-data'
|
||||
|
||||
-- add scripts
|
||||
client_script 'playernames_api.lua'
|
||||
server_script 'playernames_api.lua'
|
||||
|
||||
client_script 'playernames_cl.lua'
|
||||
server_script 'playernames_sv.lua'
|
||||
|
||||
-- make exports
|
||||
local exportList = {
|
||||
'setComponentColor',
|
||||
'setComponentAlpha',
|
||||
'setComponentVisibility',
|
||||
'setWantedLevel',
|
||||
'setHealthBarColor',
|
||||
'setNameTemplate'
|
||||
}
|
||||
|
||||
exports(exportList)
|
||||
server_exports(exportList)
|
||||
|
||||
-- add files
|
||||
files {
|
||||
'template/template.lua'
|
||||
}
|
||||
|
||||
-- support the latest resource manifest
|
||||
fx_version 'adamant'
|
||||
game 'gta5'
|
||||
@@ -0,0 +1,80 @@
|
||||
local ids = {}
|
||||
|
||||
local function getTriggerFunction(key)
|
||||
return function(id, ...)
|
||||
-- if on the client, it's easy
|
||||
if not IsDuplicityVersion() then
|
||||
TriggerEvent('playernames:configure', GetPlayerServerId(id), key, ...)
|
||||
else
|
||||
-- if on the server, save configuration
|
||||
if not ids[id] then
|
||||
ids[id] = {}
|
||||
end
|
||||
|
||||
-- save the setting
|
||||
ids[id][key] = table.pack(...)
|
||||
|
||||
-- broadcast to clients
|
||||
TriggerClientEvent('playernames:configure', -1, id, key, ...)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if IsDuplicityVersion() then
|
||||
function reconfigure(source)
|
||||
for id, data in pairs(ids) do
|
||||
for key, args in pairs(data) do
|
||||
TriggerClientEvent('playernames:configure', source, id, key, table.unpack(args))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
AddEventHandler('playerDropped', function()
|
||||
ids[source] = nil
|
||||
end)
|
||||
end
|
||||
|
||||
setComponentColor = getTriggerFunction('setc')
|
||||
setComponentAlpha = getTriggerFunction('seta')
|
||||
setComponentVisibility = getTriggerFunction('tglc')
|
||||
setWantedLevel = getTriggerFunction('setw')
|
||||
setHealthBarColor = getTriggerFunction('sehc')
|
||||
setNameTemplate = getTriggerFunction('tpl')
|
||||
setName = getTriggerFunction('name')
|
||||
|
||||
if not io then
|
||||
io = { write = nil, open = nil }
|
||||
end
|
||||
|
||||
local template = load(LoadResourceFile(GetCurrentResourceName(), 'template/template.lua'))()
|
||||
|
||||
function formatPlayerNameTag(i, templateStr)
|
||||
--return ('%s <%d>'):format(GetPlayerName(i), GetPlayerServerId(i))
|
||||
local str = ''
|
||||
|
||||
template.print = function(txt)
|
||||
str = str .. txt
|
||||
end
|
||||
|
||||
local context = {
|
||||
name = GetPlayerName(i),
|
||||
i = i,
|
||||
global = _G
|
||||
}
|
||||
|
||||
if IsDuplicityVersion() then
|
||||
context.id = i
|
||||
else
|
||||
context.id = GetPlayerServerId(i)
|
||||
end
|
||||
|
||||
TriggerEvent('playernames:extendContext', i, function(k, v)
|
||||
context[k] = v
|
||||
end)
|
||||
|
||||
template.render(templateStr, context, nil, true)
|
||||
|
||||
template.print = print
|
||||
|
||||
return str
|
||||
end
|
||||
@@ -0,0 +1,191 @@
|
||||
local mpGamerTags = {}
|
||||
local mpGamerTagSettings = {}
|
||||
|
||||
local gtComponent = {
|
||||
GAMER_NAME = 0,
|
||||
CREW_TAG = 1,
|
||||
healthArmour = 2,
|
||||
BIG_TEXT = 3,
|
||||
AUDIO_ICON = 4,
|
||||
MP_USING_MENU = 5,
|
||||
MP_PASSIVE_MODE = 6,
|
||||
WANTED_STARS = 7,
|
||||
MP_DRIVER = 8,
|
||||
MP_CO_DRIVER = 9,
|
||||
MP_TAGGED = 10,
|
||||
GAMER_NAME_NEARBY = 11,
|
||||
ARROW = 12,
|
||||
MP_PACKAGES = 13,
|
||||
INV_IF_PED_FOLLOWING = 14,
|
||||
RANK_TEXT = 15,
|
||||
MP_TYPING = 16
|
||||
}
|
||||
|
||||
local function makeSettings()
|
||||
return {
|
||||
alphas = {},
|
||||
colors = {},
|
||||
healthColor = false,
|
||||
toggles = {},
|
||||
wantedLevel = false
|
||||
}
|
||||
end
|
||||
|
||||
local templateStr
|
||||
|
||||
function updatePlayerNames()
|
||||
-- re-run this function the next frame
|
||||
SetTimeout(0, updatePlayerNames)
|
||||
|
||||
-- return if no template string is set
|
||||
if not templateStr then
|
||||
return
|
||||
end
|
||||
|
||||
-- get local coordinates to compare to
|
||||
local localCoords = GetEntityCoords(PlayerPedId())
|
||||
|
||||
-- for each valid player index
|
||||
for _, i in ipairs(GetActivePlayers()) do
|
||||
-- if the player exists
|
||||
if i ~= PlayerId() then
|
||||
-- get their ped
|
||||
local ped = GetPlayerPed(i)
|
||||
local pedCoords = GetEntityCoords(ped)
|
||||
|
||||
-- make a new settings list if needed
|
||||
if not mpGamerTagSettings[i] then
|
||||
mpGamerTagSettings[i] = makeSettings()
|
||||
end
|
||||
|
||||
-- check the ped, because changing player models may recreate the ped
|
||||
-- also check gamer tag activity in case the game deleted the gamer tag
|
||||
if not mpGamerTags[i] or mpGamerTags[i].ped ~= ped or not IsMpGamerTagActive(mpGamerTags[i].tag) then
|
||||
local nameTag = formatPlayerNameTag(i, templateStr)
|
||||
|
||||
-- remove any existing tag
|
||||
if mpGamerTags[i] then
|
||||
RemoveMpGamerTag(mpGamerTags[i].tag)
|
||||
end
|
||||
|
||||
-- store the new tag
|
||||
mpGamerTags[i] = {
|
||||
tag = CreateMpGamerTag(GetPlayerPed(i), nameTag, false, false, '', 0),
|
||||
ped = ped
|
||||
}
|
||||
end
|
||||
|
||||
-- store the tag in a local
|
||||
local tag = mpGamerTags[i].tag
|
||||
|
||||
-- should the player be renamed? this is set by events
|
||||
if mpGamerTagSettings[i].rename then
|
||||
SetMpGamerTagName(tag, formatPlayerNameTag(i, templateStr))
|
||||
mpGamerTagSettings[i].rename = nil
|
||||
end
|
||||
|
||||
-- check distance
|
||||
local distance = #(pedCoords - localCoords)
|
||||
|
||||
-- show/hide based on nearbyness/line-of-sight
|
||||
-- nearby checks are primarily to prevent a lot of LOS checks
|
||||
if distance < 250 and HasEntityClearLosToEntity(PlayerPedId(), ped, 17) then
|
||||
SetMpGamerTagVisibility(tag, gtComponent.GAMER_NAME, true)
|
||||
SetMpGamerTagVisibility(tag, gtComponent.healthArmour, IsPlayerTargettingEntity(PlayerId(), ped))
|
||||
SetMpGamerTagVisibility(tag, gtComponent.AUDIO_ICON, NetworkIsPlayerTalking(i))
|
||||
|
||||
SetMpGamerTagAlpha(tag, gtComponent.AUDIO_ICON, 255)
|
||||
SetMpGamerTagAlpha(tag, gtComponent.healthArmour, 255)
|
||||
|
||||
-- override settings
|
||||
local settings = mpGamerTagSettings[i]
|
||||
|
||||
for k, v in pairs(settings.toggles) do
|
||||
SetMpGamerTagVisibility(tag, gtComponent[k], v)
|
||||
end
|
||||
|
||||
for k, v in pairs(settings.alphas) do
|
||||
SetMpGamerTagAlpha(tag, gtComponent[k], v)
|
||||
end
|
||||
|
||||
for k, v in pairs(settings.colors) do
|
||||
SetMpGamerTagColour(tag, gtComponent[k], v)
|
||||
end
|
||||
|
||||
if settings.wantedLevel then
|
||||
SetMpGamerTagWantedLevel(tag, settings.wantedLevel)
|
||||
end
|
||||
|
||||
if settings.healthColor then
|
||||
SetMpGamerTagHealthBarColour(tag, settings.healthColor)
|
||||
end
|
||||
else
|
||||
SetMpGamerTagVisibility(tag, gtComponent.GAMER_NAME, false)
|
||||
SetMpGamerTagVisibility(tag, gtComponent.healthArmour, false)
|
||||
SetMpGamerTagVisibility(tag, gtComponent.AUDIO_ICON, false)
|
||||
end
|
||||
elseif mpGamerTags[i] then
|
||||
RemoveMpGamerTag(mpGamerTags[i].tag)
|
||||
|
||||
mpGamerTags[i] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getSettings(id)
|
||||
local i = GetPlayerFromServerId(tonumber(id))
|
||||
|
||||
if not mpGamerTagSettings[i] then
|
||||
mpGamerTagSettings[i] = makeSettings()
|
||||
end
|
||||
|
||||
return mpGamerTagSettings[i]
|
||||
end
|
||||
|
||||
RegisterNetEvent('playernames:configure')
|
||||
|
||||
AddEventHandler('playernames:configure', function(id, key, ...)
|
||||
local args = table.pack(...)
|
||||
|
||||
if key == 'tglc' then
|
||||
getSettings(id).toggles[args[1]] = args[2]
|
||||
elseif key == 'seta' then
|
||||
getSettings(id).alphas[args[1]] = args[2]
|
||||
elseif key == 'setc' then
|
||||
getSettings(id).colors[args[1]] = args[2]
|
||||
elseif key == 'setw' then
|
||||
getSettings(id).wantedLevel = args[1]
|
||||
elseif key == 'sehc' then
|
||||
getSettings(id).healthColor = args[1]
|
||||
elseif key == 'rnme' then
|
||||
getSettings(id).rename = true
|
||||
elseif key == 'name' then
|
||||
getSettings(id).serverName = args[1]
|
||||
getSettings(id).rename = true
|
||||
elseif key == 'tpl' then
|
||||
for _, v in pairs(mpGamerTagSettings) do
|
||||
v.rename = true
|
||||
end
|
||||
|
||||
templateStr = args[1]
|
||||
end
|
||||
end)
|
||||
|
||||
AddEventHandler('playernames:extendContext', function(i, cb)
|
||||
cb('serverName', getSettings(GetPlayerServerId(i)).serverName)
|
||||
end)
|
||||
|
||||
AddEventHandler('onResourceStop', function(name)
|
||||
if name == GetCurrentResourceName() then
|
||||
for _, v in pairs(mpGamerTags) do
|
||||
RemoveMpGamerTag(v.tag)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
SetTimeout(0, function()
|
||||
TriggerServerEvent('playernames:init')
|
||||
end)
|
||||
|
||||
-- run this function every frame
|
||||
SetTimeout(0, updatePlayerNames)
|
||||
@@ -0,0 +1,46 @@
|
||||
local curTemplate
|
||||
local curTags = {}
|
||||
|
||||
local activePlayers = {}
|
||||
|
||||
local function detectUpdates()
|
||||
SetTimeout(500, detectUpdates)
|
||||
|
||||
local template = GetConvar('playerNames_template', '[{{id}}] {{name}}')
|
||||
|
||||
if curTemplate ~= template then
|
||||
setNameTemplate(-1, template)
|
||||
|
||||
curTemplate = template
|
||||
end
|
||||
|
||||
template = GetConvar('playerNames_svTemplate', '[{{id}}] {{name}}')
|
||||
|
||||
for v, _ in pairs(activePlayers) do
|
||||
local newTag = formatPlayerNameTag(v, template)
|
||||
if newTag ~= curTags[v] then
|
||||
setName(v, newTag)
|
||||
|
||||
curTags[v] = newTag
|
||||
end
|
||||
end
|
||||
|
||||
for i, tag in pairs(curTags) do
|
||||
if not activePlayers[i] then
|
||||
curTags[i] = nil -- in case curTags doesnt get cleared when the player left, clear it now.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
AddEventHandler('playerDropped', function()
|
||||
curTags[source] = nil
|
||||
activePlayers[source] = nil
|
||||
end)
|
||||
|
||||
RegisterNetEvent('playernames:init')
|
||||
AddEventHandler('playernames:init', function()
|
||||
reconfigure(source)
|
||||
activePlayers[source] = true
|
||||
end)
|
||||
|
||||
detectUpdates()
|
||||
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2014 - 2017 Aapo Talvensaari
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the {organization} nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,478 @@
|
||||
local setmetatable = setmetatable
|
||||
local loadstring = loadstring
|
||||
local loadchunk
|
||||
local tostring = tostring
|
||||
local setfenv = setfenv
|
||||
local require = require
|
||||
local capture
|
||||
local concat = table.concat
|
||||
local assert = assert
|
||||
local prefix
|
||||
local write = io.write
|
||||
local pcall = pcall
|
||||
local phase
|
||||
local open = io.open
|
||||
local load = load
|
||||
local type = type
|
||||
local dump = string.dump
|
||||
local find = string.find
|
||||
local gsub = string.gsub
|
||||
local byte = string.byte
|
||||
local null
|
||||
local sub = string.sub
|
||||
local ngx = ngx
|
||||
local jit = jit
|
||||
local var
|
||||
|
||||
local _VERSION = _VERSION
|
||||
local _ENV = _ENV
|
||||
local _G = _G
|
||||
|
||||
local HTML_ENTITIES = {
|
||||
["&"] = "&",
|
||||
["<"] = "<",
|
||||
[">"] = ">",
|
||||
['"'] = """,
|
||||
["'"] = "'",
|
||||
["/"] = "/"
|
||||
}
|
||||
|
||||
local CODE_ENTITIES = {
|
||||
["{"] = "{",
|
||||
["}"] = "}",
|
||||
["&"] = "&",
|
||||
["<"] = "<",
|
||||
[">"] = ">",
|
||||
['"'] = """,
|
||||
["'"] = "'",
|
||||
["/"] = "/"
|
||||
}
|
||||
|
||||
local VAR_PHASES
|
||||
|
||||
local ok, newtab = pcall(require, "table.new")
|
||||
if not ok then newtab = function() return {} end end
|
||||
|
||||
local caching = true
|
||||
local template = newtab(0, 12)
|
||||
|
||||
template._VERSION = "1.9"
|
||||
template.cache = {}
|
||||
|
||||
local function enabled(val)
|
||||
if val == nil then return true end
|
||||
return val == true or (val == "1" or val == "true" or val == "on")
|
||||
end
|
||||
|
||||
local function trim(s)
|
||||
return gsub(gsub(s, "^%s+", ""), "%s+$", "")
|
||||
end
|
||||
|
||||
local function rpos(view, s)
|
||||
while s > 0 do
|
||||
local c = sub(view, s, s)
|
||||
if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
|
||||
s = s - 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function escaped(view, s)
|
||||
if s > 1 and sub(view, s - 1, s - 1) == "\\" then
|
||||
if s > 2 and sub(view, s - 2, s - 2) == "\\" then
|
||||
return false, 1
|
||||
else
|
||||
return true, 1
|
||||
end
|
||||
end
|
||||
return false, 0
|
||||
end
|
||||
|
||||
local function readfile(path)
|
||||
local file = open(path, "rb")
|
||||
if not file then return nil end
|
||||
local content = file:read "*a"
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
|
||||
local function loadlua(path)
|
||||
return readfile(path) or path
|
||||
end
|
||||
|
||||
local function loadngx(path)
|
||||
local vars = VAR_PHASES[phase()]
|
||||
local file, location = path, vars and var.template_location
|
||||
if sub(file, 1) == "/" then file = sub(file, 2) end
|
||||
if location and location ~= "" then
|
||||
if sub(location, -1) == "/" then location = sub(location, 1, -2) end
|
||||
local res = capture(concat{ location, '/', file})
|
||||
if res.status == 200 then return res.body end
|
||||
end
|
||||
local root = vars and (var.template_root or var.document_root) or prefix
|
||||
if sub(root, -1) == "/" then root = sub(root, 1, -2) end
|
||||
return readfile(concat{ root, "/", file }) or path
|
||||
end
|
||||
|
||||
do
|
||||
if ngx then
|
||||
VAR_PHASES = {
|
||||
set = true,
|
||||
rewrite = true,
|
||||
access = true,
|
||||
content = true,
|
||||
header_filter = true,
|
||||
body_filter = true,
|
||||
log = true
|
||||
}
|
||||
template.print = ngx.print or write
|
||||
template.load = loadngx
|
||||
prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
|
||||
if VAR_PHASES[phase()] then
|
||||
caching = enabled(var.template_cache)
|
||||
end
|
||||
else
|
||||
template.print = write
|
||||
template.load = loadlua
|
||||
end
|
||||
if _VERSION == "Lua 5.1" then
|
||||
local context = { __index = function(t, k)
|
||||
return t.context[k] or t.template[k] or _G[k]
|
||||
end }
|
||||
if jit then
|
||||
loadchunk = function(view)
|
||||
return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
|
||||
end
|
||||
else
|
||||
loadchunk = function(view)
|
||||
local func = assert(loadstring(view))
|
||||
setfenv(func, setmetatable({ template = template }, context))
|
||||
return func
|
||||
end
|
||||
end
|
||||
else
|
||||
local context = { __index = function(t, k)
|
||||
return t.context[k] or t.template[k] or _ENV[k]
|
||||
end }
|
||||
loadchunk = function(view)
|
||||
return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function template.caching(enable)
|
||||
if enable ~= nil then caching = enable == true end
|
||||
return caching
|
||||
end
|
||||
|
||||
function template.output(s)
|
||||
if s == nil or s == null then return "" end
|
||||
if type(s) == "function" then return template.output(s()) end
|
||||
return tostring(s)
|
||||
end
|
||||
|
||||
function template.escape(s, c)
|
||||
if type(s) == "string" then
|
||||
if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
|
||||
return gsub(s, "[\">/<'&]", HTML_ENTITIES)
|
||||
end
|
||||
return template.output(s)
|
||||
end
|
||||
|
||||
function template.new(view, layout)
|
||||
assert(view, "view was not provided for template.new(view, layout).")
|
||||
local render, compile = template.render, template.compile
|
||||
if layout then
|
||||
if type(layout) == "table" then
|
||||
return setmetatable({ render = function(self, context)
|
||||
local context = context or self
|
||||
context.blocks = context.blocks or {}
|
||||
context.view = compile(view)(context)
|
||||
layout.blocks = context.blocks or {}
|
||||
layout.view = context.view or ""
|
||||
return layout:render()
|
||||
end }, { __tostring = function(self)
|
||||
local context = self
|
||||
context.blocks = context.blocks or {}
|
||||
context.view = compile(view)(context)
|
||||
layout.blocks = context.blocks or {}
|
||||
layout.view = context.view
|
||||
return tostring(layout)
|
||||
end })
|
||||
else
|
||||
return setmetatable({ render = function(self, context)
|
||||
local context = context or self
|
||||
context.blocks = context.blocks or {}
|
||||
context.view = compile(view)(context)
|
||||
return render(layout, context)
|
||||
end }, { __tostring = function(self)
|
||||
local context = self
|
||||
context.blocks = context.blocks or {}
|
||||
context.view = compile(view)(context)
|
||||
return compile(layout)(context)
|
||||
end })
|
||||
end
|
||||
end
|
||||
return setmetatable({ render = function(self, context)
|
||||
return render(view, context or self)
|
||||
end }, { __tostring = function(self)
|
||||
return compile(view)(self)
|
||||
end })
|
||||
end
|
||||
|
||||
function template.precompile(view, path, strip)
|
||||
local chunk = dump(template.compile(view), strip ~= false)
|
||||
if path then
|
||||
local file = open(path, "wb")
|
||||
file:write(chunk)
|
||||
file:close()
|
||||
end
|
||||
return chunk
|
||||
end
|
||||
|
||||
function template.compile(view, key, plain)
|
||||
assert(view, "view was not provided for template.compile(view, key, plain).")
|
||||
if key == "no-cache" then
|
||||
return loadchunk(template.parse(view, plain)), false
|
||||
end
|
||||
key = key or view
|
||||
local cache = template.cache
|
||||
if cache[key] then return cache[key], true end
|
||||
local func = loadchunk(template.parse(view, plain))
|
||||
if caching then cache[key] = func end
|
||||
return func, false
|
||||
end
|
||||
|
||||
function template.parse(view, plain)
|
||||
assert(view, "view was not provided for template.parse(view, plain).")
|
||||
if not plain then
|
||||
view = template.load(view)
|
||||
if byte(view, 1, 1) == 27 then return view end
|
||||
end
|
||||
local j = 2
|
||||
local c = {[[
|
||||
context=... or {}
|
||||
local function include(v, c) return template.compile(v)(c or context) end
|
||||
local ___,blocks,layout={},blocks or {}
|
||||
]] }
|
||||
local i, s = 1, find(view, "{", 1, true)
|
||||
while s do
|
||||
local t, p = sub(view, s + 1, s + 1), s + 2
|
||||
if t == "{" then
|
||||
local e = find(view, "}}", p, true)
|
||||
if e then
|
||||
local z, w = escaped(view, s)
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
if z then
|
||||
i = s
|
||||
else
|
||||
c[j] = "___[#___+1]=template.escape("
|
||||
c[j+1] = trim(sub(view, p, e - 1))
|
||||
c[j+2] = ")\n"
|
||||
j=j+3
|
||||
s, i = e + 1, e + 2
|
||||
end
|
||||
end
|
||||
elseif t == "*" then
|
||||
local e = find(view, "*}", p, true)
|
||||
if e then
|
||||
local z, w = escaped(view, s)
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
if z then
|
||||
i = s
|
||||
else
|
||||
c[j] = "___[#___+1]=template.output("
|
||||
c[j+1] = trim(sub(view, p, e - 1))
|
||||
c[j+2] = ")\n"
|
||||
j=j+3
|
||||
s, i = e + 1, e + 2
|
||||
end
|
||||
end
|
||||
elseif t == "%" then
|
||||
local e = find(view, "%}", p, true)
|
||||
if e then
|
||||
local z, w = escaped(view, s)
|
||||
if z then
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
i = s
|
||||
else
|
||||
local n = e + 2
|
||||
if sub(view, n, n) == "\n" then
|
||||
n = n + 1
|
||||
end
|
||||
local r = rpos(view, s - 1)
|
||||
if i <= r then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, r)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
c[j] = trim(sub(view, p, e - 1))
|
||||
c[j+1] = "\n"
|
||||
j=j+2
|
||||
s, i = n - 1, n
|
||||
end
|
||||
end
|
||||
elseif t == "(" then
|
||||
local e = find(view, ")}", p, true)
|
||||
if e then
|
||||
local z, w = escaped(view, s)
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
if z then
|
||||
i = s
|
||||
else
|
||||
local f = sub(view, p, e - 1)
|
||||
local x = find(f, ",", 2, true)
|
||||
if x then
|
||||
c[j] = "___[#___+1]=include([=["
|
||||
c[j+1] = trim(sub(f, 1, x - 1))
|
||||
c[j+2] = "]=],"
|
||||
c[j+3] = trim(sub(f, x + 1))
|
||||
c[j+4] = ")\n"
|
||||
j=j+5
|
||||
else
|
||||
c[j] = "___[#___+1]=include([=["
|
||||
c[j+1] = trim(f)
|
||||
c[j+2] = "]=])\n"
|
||||
j=j+3
|
||||
end
|
||||
s, i = e + 1, e + 2
|
||||
end
|
||||
end
|
||||
elseif t == "[" then
|
||||
local e = find(view, "]}", p, true)
|
||||
if e then
|
||||
local z, w = escaped(view, s)
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
if z then
|
||||
i = s
|
||||
else
|
||||
c[j] = "___[#___+1]=include("
|
||||
c[j+1] = trim(sub(view, p, e - 1))
|
||||
c[j+2] = ")\n"
|
||||
j=j+3
|
||||
s, i = e + 1, e + 2
|
||||
end
|
||||
end
|
||||
elseif t == "-" then
|
||||
local e = find(view, "-}", p, true)
|
||||
if e then
|
||||
local x, y = find(view, sub(view, s, e + 1), e + 2, true)
|
||||
if x then
|
||||
local z, w = escaped(view, s)
|
||||
if z then
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
i = s
|
||||
else
|
||||
y = y + 1
|
||||
x = x - 1
|
||||
if sub(view, y, y) == "\n" then
|
||||
y = y + 1
|
||||
end
|
||||
local b = trim(sub(view, p, e - 1))
|
||||
if b == "verbatim" or b == "raw" then
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
c[j] = "___[#___+1]=[=["
|
||||
c[j+1] = sub(view, e + 2, x)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
else
|
||||
if sub(view, x, x) == "\n" then
|
||||
x = x - 1
|
||||
end
|
||||
local r = rpos(view, s - 1)
|
||||
if i <= r then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, r)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
c[j] = 'blocks["'
|
||||
c[j+1] = b
|
||||
c[j+2] = '"]=include[=['
|
||||
c[j+3] = sub(view, e + 2, x)
|
||||
c[j+4] = "]=]\n"
|
||||
j=j+5
|
||||
end
|
||||
s, i = y - 1, y
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif t == "#" then
|
||||
local e = find(view, "#}", p, true)
|
||||
if e then
|
||||
local z, w = escaped(view, s)
|
||||
if i < s - w then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = sub(view, i, s - 1 - w)
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
if z then
|
||||
i = s
|
||||
else
|
||||
e = e + 2
|
||||
if sub(view, e, e) == "\n" then
|
||||
e = e + 1
|
||||
end
|
||||
s, i = e - 1, e
|
||||
end
|
||||
end
|
||||
end
|
||||
s = find(view, "{", s + 1, true)
|
||||
end
|
||||
s = sub(view, i)
|
||||
if s and s ~= "" then
|
||||
c[j] = "___[#___+1]=[=[\n"
|
||||
c[j+1] = s
|
||||
c[j+2] = "]=]\n"
|
||||
j=j+3
|
||||
end
|
||||
c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
|
||||
return concat(c)
|
||||
end
|
||||
|
||||
function template.render(view, context, key, plain)
|
||||
assert(view, "view was not provided for template.render(view, context, key, plain).")
|
||||
return template.print(template.compile(view, key, plain)(context))
|
||||
end
|
||||
|
||||
return template
|
||||
Reference in New Issue
Block a user