diff --git a/gamemodes/terrortown/gamemode/client/cl_player_ext.lua b/gamemodes/terrortown/gamemode/client/cl_player_ext.lua
index c270296e6..84f4c65de 100644
--- a/gamemodes/terrortown/gamemode/client/cl_player_ext.lua
+++ b/gamemodes/terrortown/gamemode/client/cl_player_ext.lua
@@ -242,7 +242,7 @@ local function CacheAllPlayerAvatars(ply)
for i = 1, #plys do
local plyid64 = plys[i]:SteamID64()
- draw.RefreshAvatars(plyid64)
+ playeravatar.Refresh(plyid64)
end
end
diff --git a/gamemodes/terrortown/gamemode/client/cl_search.lua b/gamemodes/terrortown/gamemode/client/cl_search.lua
index 319e0b447..67b083297 100644
--- a/gamemodes/terrortown/gamemode/client/cl_search.lua
+++ b/gamemodes/terrortown/gamemode/client/cl_search.lua
@@ -42,7 +42,7 @@ net.Receive("TTT2SendConfirmMsg", function()
sid64 = nil
end
- local img = draw.GetAvatarMaterial(sid64, "medium")
+ local img = playeravatar.GetMaterial(sid64, PLAYERAVATAR_SIZE.medium)
---
-- @realm client
@@ -214,7 +214,7 @@ function SEARCHSCREEN:Show(data)
profileBox:Dock(LEFT)
profileBox:SetModel(data.playerModel)
profileBox:SetPlayerIcon(
- playerDataKnown and draw.GetAvatarMaterial(data.sid64, "medium")
+ playerDataKnown and playeravatar.GetMaterial(data.sid64, PLAYERAVATAR_SIZE.medium)
or materialPlayerIconUnknown
)
profileBox:SetPlayerRoleColor(playerDataKnown and data.roleColor or COLOR_SLATEGRAY)
diff --git a/gamemodes/terrortown/gamemode/shared/hud_elements/tttvoice/pure_skin_voice.lua b/gamemodes/terrortown/gamemode/shared/hud_elements/tttvoice/pure_skin_voice.lua
index 53e35663e..0d32e0dbe 100644
--- a/gamemodes/terrortown/gamemode/shared/hud_elements/tttvoice/pure_skin_voice.lua
+++ b/gamemodes/terrortown/gamemode/shared/hud_elements/tttvoice/pure_skin_voice.lua
@@ -118,7 +118,7 @@ if CLIENT then
yPos,
h,
h,
- draw.GetAvatarMaterial(ply:SteamID64(), "medium"),
+ playeravatar.GetMaterial(ply:SteamID64(), PLAYERAVATAR_SIZE.medium),
255,
COLOR_WHITE
)
diff --git a/gamemodes/terrortown/gamemode/shared/sh_init.lua b/gamemodes/terrortown/gamemode/shared/sh_init.lua
index c39c9a02d..e6339e5e9 100644
--- a/gamemodes/terrortown/gamemode/shared/sh_init.lua
+++ b/gamemodes/terrortown/gamemode/shared/sh_init.lua
@@ -593,6 +593,7 @@ include("ttt2/libraries/events.lua")
include("ttt2/libraries/eventdata.lua")
include("ttt2/libraries/targetid.lua")
include("ttt2/libraries/playermodels.lua")
+include("ttt2/libraries/playeravatar.lua")
include("ttt2/libraries/entspawnscript.lua")
include("ttt2/libraries/bodysearch.lua")
include("ttt2/libraries/keyhelp.lua")
diff --git a/lua/autorun/client/b-draw_lib.lua b/lua/autorun/client/b-draw_lib.lua
deleted file mode 100644
index 8663a0428..000000000
--- a/lua/autorun/client/b-draw_lib.lua
+++ /dev/null
@@ -1,256 +0,0 @@
-if engine.ActiveGamemode() ~= "terrortown" then
- return
-end
-
----
--- A Simple Garry's mod drawing library
--- Copyright (C) 2016 Bull [STEAM_0:0:42437032] [76561198045139792]
--- Freely acquirable at https://github.com/bull29/b_draw-lib
--- You can use this anywhere for any purpose as long as you acredit the work to the original author with this notice.
--- Optionally, if you choose to use this within your own software, it would be much appreciated if you could inform me of it.
--- I love to see what people have done with my code! :)
--- This code got fixed and modified by the TTT2 dev group, ty for the lib @Bull !
---
--- @author Bull
--- @author Alf21
---
--- @module draw
-
-file.CreateDir("downloaded_assets")
-
-local exists = file.Exists
-local write = file.Write
-local delete = file.Delete
-local white = Color(255, 255, 255)
-local surface = surface
-local crc = util.CRC
-local _bot_avatar = Material("vgui/ttt/b-draw/icon_avatar_bot.vmt")
-local _default_avatar = Material("vgui/ttt/b-draw/icon_avatar_default.vmt")
-local mats = {}
-
----
--- Captures the content of a render target as a PNG image
--- @param RenderTarget renderTarget The render target to capture
--- @param Material material The material to use for rendering
--- @param number width The width of the capture
--- @param number height The height of the capture
--- @return string The captured PNG data
--- @realm client
-local function CaptureRenderTarget(renderTarget, material, width, height)
- render.PushRenderTarget(renderTarget)
- cam.Start2D()
- surface.SetMaterial(material)
- cam.End2D()
-
- local data = render.Capture({
- format = "png",
- x = 0,
- y = 0,
- w = width,
- h = height,
- })
-
- render.PopRenderTarget()
-
- return data
-end
-
----
--- Creates an avatar material for a given SteamID64 if it doesn't exist
--- @param string id64 The SteamID64 of the user
--- @param string size The avatar's size, this can be small
, medium
or large
--- @return Material The created avatar material or the default material
--- @realm client
-local function CreateAvatarMaterial(id64, size)
- if not id64 then
- return _bot_avatar
- end
-
- local avatarSize = 32
-
- if size == "large" then
- avatarSize = 184
- elseif size == "medium" then
- avatarSize = 64
- end
-
- local renderTargetName = id64 .. avatarSize
- local crcUrl = crc(renderTargetName)
- local filePath = "downloaded_assets/" .. crcUrl .. ".png"
-
- if mats[renderTargetName] then
- return mats[renderTargetName]
- end
-
- ---
- -- @realm client
- local data = hook.Run("TTT2FetchAvatar", id64, avatarSize)
-
- if data then
- write(filePath, data)
- mats[renderTargetName] = Material("data/" .. filePath)
-
- return mats[renderTargetName]
- end
-
- local avatarImage = vgui.Create("AvatarImage")
- avatarImage:SetSize(avatarSize, avatarSize)
- avatarImage:SetSteamID(id64, avatarSize)
- avatarImage:SetPaintedManually(true)
-
- local renderTarget = GetRenderTargetEx(
- renderTargetName,
- avatarSize,
- avatarSize,
- RT_SIZE_NO_CHANGE,
- MATERIAL_RT_DEPTH_NONE,
- 16,
- CREATERENDERTARGETFLAGS_HDR,
- IMAGE_FORMAT_RGBA8888
- )
-
- if not renderTarget then
- avatarImage:Remove()
-
- return _default_avatar
- end
-
- render.PushRenderTarget(renderTarget)
- cam.Start2D()
- avatarImage:PaintManual(true)
- cam.End2D()
- render.PopRenderTarget()
-
- local material = CreateMaterial(id64 .. avatarSize, "UnlitGeneric", {
- ["$basetexture"] = renderTargetName,
- ["$nodecal"] = 1,
- ["$nolod"] = 1,
- ["$translucent"] = 1,
- ["$vertexalpha"] = 1,
- ["$vertexcolor"] = 1,
- })
-
- local captureData = CaptureRenderTarget(renderTarget, material, avatarSize, avatarSize)
-
- if captureData then
- write(filePath, captureData)
- end
-
- avatarImage:Remove()
- mats[renderTargetName] = material
-
- return material
-end
-
----
--- Refreshes the avatar images by deleting old cached images and generating new ones
--- @param string id64 The steamid64 of the player
--- @realm client
-function draw.RefreshAvatars(id64)
- draw.DropCacheAvatar(id64, "small")
- draw.DropCacheAvatar(id64, "medium")
- draw.DropCacheAvatar(id64, "large")
- draw.CacheAvatar(id64, "small")
- draw.CacheAvatar(id64, "medium")
- draw.CacheAvatar(id64, "large")
-end
-
----
--- Creates the avatar material for a steamid64
--- When an avatar is found it will be cached
--- @param string id64 The steamid64
--- @param string size The avatar's size, this can be small
, medium
or large
--- @realm client
-function draw.CacheAvatar(id64, size)
- CreateAvatarMaterial(id64, size)
-end
-
----
--- Deletes the avatar material for a steamid64
--- When a cached avatar is found it will be destroyed
--- @param string id64 The player's steamid64
--- @param string size The avatar's size, this can be small
, medium
or large
--- @realm client
-function draw.DropCacheAvatar(id64, size)
- local avatarSize = 32
-
- if size == "large" then
- avatarSize = 184
- elseif size == "medium" then
- avatarSize = 64
- end
-
- local key = id64 .. avatarSize
- local crcUrl = crc(key)
- local uri = "downloaded_assets/" .. crcUrl .. ".png"
-
- if exists(uri, "DATA") then
- delete(uri, "DATA")
- end
-
- mats[key] = nil
-end
-
----
--- Draws an Image
--- @param Material material the material to draw
--- @param number x
--- @param number y
--- @param number width
--- @param number height
--- @param Color color
--- @param Angle angle
--- @param boolean cornerorigin If it is set to true
, the WebImage will be centered based on the x- and y-coordinate
--- @realm client
-local function DrawImage(material, x, y, width, height, color, angle, cornerorigin)
- color = color or white
-
- surface.SetDrawColor(color.r, color.g, color.b, color.a)
- surface.SetMaterial(material)
-
- if not angle then
- surface.DrawTexturedRect(x, y, width, height)
- else
- if not cornerorigin then
- surface.DrawTexturedRectRotated(x, y, width, height, angle)
- else
- surface.DrawTexturedRectRotated(x + width * 0.5, y + height * 0.5, width, height, angle)
- end
- end
-end
-
----
--- Draws a SteamAvatar while caching it before
--- @param string id64 The steamid64
--- @param string size The avatar's size, this can be small
, medium
or large
--- @param number x
--- @param number y
--- @param number width
--- @param number height
--- @param Color color
--- @param Angle angle
--- @param boolean cornerorigin If it is set to true
, the WebImage will be centered based on the x- and y-coordinate
--- @realm client
-function draw.SteamAvatar(id64, size, x, y, width, height, color, angle, cornerorigin)
- DrawImage(
- CreateAvatarMaterial(id64, size) or _default_avatar,
- x,
- y,
- width,
- height,
- color,
- angle,
- cornerorigin
- )
-end
-
----
--- Creates and returns the avatar material for a steamid64
--- When an avatar is found it will be cached
--- @param string id64 The steamid64
--- @param string size The avatar's size, this can be small
, medium
or large
--- @return Material The created avatar material or the default material
--- @realm client
-function draw.GetAvatarMaterial(id64, size)
- return CreateAvatarMaterial(id64, size) or _default_avatar
-end
diff --git a/lua/ttt2/libraries/playeravatar.lua b/lua/ttt2/libraries/playeravatar.lua
new file mode 100644
index 000000000..3b1aa16af
--- /dev/null
+++ b/lua/ttt2/libraries/playeravatar.lua
@@ -0,0 +1,212 @@
+---
+--
+--
+-- @author
+--
+-- @module playeravatar
+
+if SERVER then
+ AddCSLuaFile()
+
+ return
+end
+
+file.CreateDir("player_avatars")
+
+local fileExists = file.Exists
+local fileWrite = file.Write
+local fileDelete = file.Delete
+local surface = surface
+local crc = util.CRC
+local bot_avatar = Material("vgui/ttt/b-draw/icon_avatar_bot")
+local default_avatar = Material("vgui/ttt/b-draw/icon_avatar_default")
+local playeravatar_cache = {}
+
+playeravatar = {}
+
+---@enum
+PLAYERAVATAR_SIZE = {
+ small = 32,
+ medium = 64,
+ large = 128,
+ -- `large` was 184, but we use this for the `width/height` parameter of `GetRenderTargetEx`
+ -- which according to the wiki must be a power of 2
+ -- ref.: https://wiki.facepunch.com/gmod/Global.GetRenderTargetEx#arguments
+}
+
+---
+-- Captures the content of a render target as a PNG image
+-- @param ITexture renderTarget The render target to capture
+-- @param IMaterial material The material to use for rendering
+-- @param number width The width of the capture
+-- @param number height The height of the capture
+-- @return string|nil The captured binary PNG data
+-- @realm client
+local function CaptureRenderTarget(renderTarget, material, width, height)
+ render.PushRenderTarget(renderTarget)
+ cam.Start2D()
+ surface.SetMaterial(material)
+ cam.End2D()
+
+ local data = render.Capture({
+ format = "png",
+ x = 0,
+ y = 0,
+ w = width,
+ h = height,
+ })
+
+ render.PopRenderTarget()
+
+ return data
+end
+
+local function GetAvatarCacheKey(id64, avatarSize)
+ return id64 .. avatarSize
+end
+
+local function GetAvatarUri(id64, avatarSize)
+ local key = GetAvatarCacheKey(id64, avatarSize)
+ local crcUrl = crc(key)
+ return "player_avatars/" .. crcUrl .. ".png"
+end
+
+---
+-- Creates an avatar material for a given SteamID64 if it doesn't exist
+-- @param string id64 The SteamID64 of the user
+-- @param PLAYERAVATAR_SIZE avatarSize The avatar's size
+-- @return IMaterial The created avatar material or the default material
+-- @realm client
+local function CreateAvatarMaterial(id64, avatarSize)
+ if not id64 then
+ return bot_avatar
+ end
+
+ local cacheKey = GetAvatarCacheKey(id64, avatarSize)
+ local uri = GetAvatarUri(id64, avatarSize)
+
+ ---
+ -- @realm client
+ -- TODO: declare that this needs to return a PNG, JPEG, GIF, or TGA file
+ -- the file extension does not matter we will append `.png` to it
+ local data = hook.Run("TTT2FetchAvatar", id64, avatarSize)
+
+ if data then
+ fileWrite(uri, data)
+ playeravatar_cache[cacheKey] = Material("data/" .. uri)
+
+ return playeravatar_cache[cacheKey]
+ end
+
+ -- TODO: according to the wiki render targets are not garbage collected and remain in memory
+ -- until disconnect.
+ -- ref: https://wiki.facepunch.com/gmod/Global.GetRenderTarget#description
+ -- Test if this is actually the case and if we can get away with reusing a single render target
+ local renderTarget = GetRenderTargetEx(
+ cacheKey,
+ avatarSize,
+ avatarSize,
+ RT_SIZE_NO_CHANGE,
+ MATERIAL_RT_DEPTH_NONE,
+ 16,
+ CREATERENDERTARGETFLAGS_HDR,
+ IMAGE_FORMAT_RGBA8888
+ )
+
+ if not renderTarget then
+ return default_avatar
+ end
+
+ local avatarImage = vgui.Create("AvatarImage")
+ avatarImage:SetSize(avatarSize, avatarSize)
+ avatarImage:SetSteamID(id64, avatarSize)
+ avatarImage:SetPaintedManually(true)
+
+ render.PushRenderTarget(renderTarget)
+ cam.Start2D()
+ avatarImage:PaintManual(true)
+ cam.End2D()
+ render.PopRenderTarget()
+
+ local material = CreateMaterial(cacheKey, "UnlitGeneric", {
+ ["$basetexture"] = cacheKey,
+ ["$nodecal"] = 1,
+ ["$nolod"] = 1,
+ ["$translucent"] = 1,
+ ["$vertexalpha"] = 1,
+ ["$vertexcolor"] = 1,
+ })
+
+ local captureData = CaptureRenderTarget(renderTarget, material, avatarSize, avatarSize)
+
+ if captureData then
+ fileWrite(uri, captureData)
+ end
+
+ avatarImage:Remove()
+ playeravatar_cache[cacheKey] = material
+
+ return material
+end
+
+---
+-- Refreshes the avatar images by deleting old cached images and generating new ones
+-- @param string id64 The steamid64 of the player
+-- @realm client
+function playeravatar.Refresh(id64)
+ playeravatar.DropCache(id64, PLAYERAVATAR_SIZE.small)
+ playeravatar.DropCache(id64, PLAYERAVATAR_SIZE.medium)
+ playeravatar.DropCache(id64, PLAYERAVATAR_SIZE.large)
+ playeravatar.Cache(id64, PLAYERAVATAR_SIZE.small)
+ playeravatar.Cache(id64, PLAYERAVATAR_SIZE.medium)
+ playeravatar.Cache(id64, PLAYERAVATAR_SIZE.large)
+end
+
+---
+-- Creates the avatar material for a steamid64
+-- When an avatar is found it will be cached
+-- @param string id64 The steamid64
+-- @param PLAYERAVATAR_SIZE size The avatar's size
+-- @realm client
+-- TODO: Make this local? Or actually use this somewhere? Or merge/replace with
+-- `playeravatar.GetMaterial()`
+function playeravatar.Cache(id64, size)
+ CreateAvatarMaterial(id64, size)
+end
+
+---
+-- Deletes the avatar material for a steamid64
+-- When a cached avatar is found it will be destroyed
+-- @param string id64 The player's steamid64
+-- @param PLAYERAVATAR_SIZE avatarSize The avatar's size
+-- @realm client
+-- TODO: Make this local? Or actually use this somewhere?
+-- TODO: Also it does not really make much sense to only drop a single avatarSize by default. We
+-- probably should by default just iterate over small,medium,large
+function playeravatar.DropCache(id64, avatarSize)
+ local key = GetAvatarCacheKey(id64, avatarSize)
+ local uri = GetAvatarUri(id64, avatarSize)
+
+ if fileExists(uri, "DATA") then
+ fileDelete(uri, "DATA")
+ end
+
+ playeravatar_cache[key] = nil
+end
+
+---
+-- Creates and returns the avatar material for a steamid64
+-- When an avatar is found it will be cached
+-- @param string id64 The steamid64
+-- @param PLAYERAVATAR_SIZE avatarSize The avatar's size
+-- @return IMaterial The created avatar material or the default material
+-- @realm client
+function playeravatar.GetMaterial(id64, avatarSize)
+ local rendertargetKey = GetAvatarCacheKey(id64, avatarSize)
+
+ if playeravatar_cache[rendertargetKey] then
+ return playeravatar_cache[rendertargetKey]
+ end
+
+ return CreateAvatarMaterial(id64, avatarSize)
+end