diff --git a/resources/[gameplay]/playernames/__resource.lua b/resources/[gameplay]/playernames/__resource.lua new file mode 100644 index 0000000..2714468 --- /dev/null +++ b/resources/[gameplay]/playernames/__resource.lua @@ -0,0 +1,27 @@ +-- 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 +resource_manifest_version '05cfa83c-a124-4cfa-a768-c24a5811d8f9' \ No newline at end of file diff --git a/resources/[gameplay]/playernames/playernames_api.lua b/resources/[gameplay]/playernames/playernames_api.lua new file mode 100644 index 0000000..e592946 --- /dev/null +++ b/resources/[gameplay]/playernames/playernames_api.lua @@ -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 \ No newline at end of file diff --git a/resources/[gameplay]/playernames/playernames_cl.lua b/resources/[gameplay]/playernames/playernames_cl.lua new file mode 100644 index 0000000..8a4bf60 --- /dev/null +++ b/resources/[gameplay]/playernames/playernames_cl.lua @@ -0,0 +1,196 @@ +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 = 0, 255 do + -- if the player exists + if NetworkIsPlayerActive(i) and 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 + print(id, 'id') + print(getSettings(id)) + + 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) + print(getSettings(GetPlayerServerId(i))) + + 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) \ No newline at end of file diff --git a/resources/[gameplay]/playernames/playernames_sv.lua b/resources/[gameplay]/playernames/playernames_sv.lua new file mode 100644 index 0000000..389c927 --- /dev/null +++ b/resources/[gameplay]/playernames/playernames_sv.lua @@ -0,0 +1,36 @@ +local curTemplate +local curTags = {} + +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 ipairs(GetPlayers()) do + local newTag = formatPlayerNameTag(v, template) + + if newTag ~= curTags[v] then + setName(v, newTag) + + curTags[v] = newTag + end + end +end + + + +RegisterNetEvent('playernames:init') +AddEventHandler('playernames:init', function() + reconfigure(source) +end) + +SetTimeout(500, detectUpdates) +detectUpdates() \ No newline at end of file diff --git a/resources/[gameplay]/playernames/template/LICENSE b/resources/[gameplay]/playernames/template/LICENSE new file mode 100644 index 0000000..f0e57b1 --- /dev/null +++ b/resources/[gameplay]/playernames/template/LICENSE @@ -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. diff --git a/resources/[gameplay]/playernames/template/template.lua b/resources/[gameplay]/playernames/template/template.lua new file mode 100644 index 0000000..0de71bd --- /dev/null +++ b/resources/[gameplay]/playernames/template/template.lua @@ -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