commit 64f8029c06e4502c9cea7073185dcbcfa0132153 Author: pavetr Date: Sun Mar 15 14:54:49 2026 +0300 Initial commit diff --git a/addons/!kostich/lua/autorun/client/!event_stuff.lua b/addons/!kostich/lua/autorun/client/!event_stuff.lua new file mode 100644 index 0000000..6ebfdb3 --- /dev/null +++ b/addons/!kostich/lua/autorun/client/!event_stuff.lua @@ -0,0 +1,2144 @@ +local base_url = "http://dev.pavetr.ru/server/senwai-mmk/posters/" + +local agits = { + { + pos = Vector(2351.97, -62, 160), + ang = Angle(0, 180, 90), + url = base_url .. "soup.png", + width = 500, + height = 400, + scale = 0.1, + show = "m" + }, + { + pos = Vector(2270, -62, 160), + ang = Angle(0, 180, 90), + url = base_url .. "whatissup.png", + width = 500, + height = 400, + scale = 0.1, + show = "u" + }, + { + pos = Vector(-626.13, -965, 160), + ang = Angle(0, 0, 90), + url = base_url .. "supforpidor.png", + width = 400, + height = 500, + scale = 0.1, + show = "m" + }, + { + pos = Vector(2661.47, -1540, 160), + ang = Angle(0, 180, 90), + url = base_url .. "senwaidox.png", + width = 500, + height = 400, + scale = 0.1, + show = "u" + }, + { + pos = Vector(89.12, 380, 160), + ang = Angle(0, 0, 90), + url = base_url .. "wantsup.png", + width = 500, + height = 650, + scale = 0.1, + show = "m" + }, + { + pos = Vector(5470, 1340, 160), + ang = Angle(0, -90, 90), + url = base_url .. "7733sup.png", + width = 500, + height = 500, + scale = 0.1, + show = "u" + }, + { + pos = Vector(1580.32, 2175, 245), + ang = Angle(0, 0, 90), + url = base_url .. "jockddos.png", + width = 500, + height = 550, + scale = 0.1, + show = "m" + }, + { + pos = Vector(4300, -4157, 160), + ang = Angle(0, 180, 90), + url = base_url .. "10sup.png", + width = 500, + height = 500, + scale = 0.1, + show = "u" + }, + { + pos = Vector(7885, 5905, 1145), + ang = Angle(0, 90, 90), + url = base_url .. "sup.png", + width = 500, + height = 500, + scale = 0.1, + }, + { + pos = Vector(3776.82, 382, 160), + ang = Angle(0, 0, 90), + url = base_url .. "gafich.png", + width = 550, + height = 500, + scale = 0.1, + }, + { + pos = Vector(644, -1280, 160), + ang = Angle(0, 90, 90), + url = base_url .. "skipper.png", + width = 700, + height = 500, + scale = 0.1, + }, + { + pos = Vector(-1035, -2280, 180), + ang = Angle(0, 90 + 40, 90), + url = base_url .. "snegiri.png", + width = 1500, + height = 800, + scale = 0.1, + }, +} +local u_cnt = 0 +local m_cnt = 0 +for z, v in ipairs(agits) do + if v.show == "m" then + m_cnt = m_cnt + 1 + elseif v.show == "u" then + u_cnt = u_cnt + 1 + end +end + +local cur_params +local save = "kostich_saves.dat" + +timer.Create("removeRagdolls", 3, 0, function() game.RemoveRagdolls() end) + +local function save_settings() + file.Write(save, util.Base64Encode(util.TableToJSON(cur_params))) +end + +local def_cfg = { + hud = "kostich", + tab = "kostich", + cmenu = "kostich", + selector = "kostich", + posters = "", + plib = true +} + +local function load_settings() + if file.Exists(save, "DATA") then + cur_params = util.JSONToTable(util.Base64Decode(file.Read(save, "DATA"))) + for z, v in ipairs(def_cfg) do + if not cur_params[z] then + cur_params[z] = v + end + end + end +end + +if not file.Exists(save, "DATA") then + cur_params = def_cfg + save_settings() +else + load_settings() +end + +gameevent.Listen("client_disconnect") +hook.Add("client_disconnect", "kostich.saveOnExit", function(data) + save_settings() +end) + +kostich = kostich or {} +function kostich.getValue(key) + return cur_params[key] +end + +local red = Color(255, 0, 0) + +-- 5.5 sec +function kostich.ss1() + surface.PlaySound("ambient/levels/labs/electric_explosion1.wav") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + timer.Simple(0.3, function() + cur_params["hud"] = "drp" + save_settings() + chat.AddText(red, "[ИВЕНТ] ", color_white, "Ваш худ от сапа исчез и заменился на даркрпшный") + end) + + timer.Simple(0.5, function() + surface.PlaySound("pavetr_mmk/vo/player1.mp3") + chat.AddText(color_white, "- Блять, что за хуйня!? Куда худ пропал") + timer.Simple(2.5, function() + surface.PlaySound("pavetr_mmk/vo/player2.mp3") + chat.AddText(color_white, "- Я даже хз кто это мог сделать, а хотя...") + timer.Simple(2.5, function() + surface.PlaySound("pavetr_mmk/vo/player3.mp3") + chat.AddText(color_white, "- Точно! Надо у сенвая спросить, он пиздюк еще тот") + end) + end) + end) +end + +-- 8.5 sec +function kostich.ss2() + surface.PlaySound("ambient/levels/labs/electric_explosion1.wav") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + timer.Simple(0.3, function() + cur_params["plib"] = false + save_settings() + chat.AddText(red, "[ИВЕНТ] ", color_white, "У вас отъебнуло plibv2") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + end) + + timer.Simple(1, function() + surface.PlaySound("pavetr_mmk/vo/player4.mp3") + chat.AddText(color_white, "- Сука что на этот раз") + timer.Create("plib_emit_errors", 0.2, 5, function() + ErrorNoHalt("У вас наебнулся plib_v2") + end) + timer.Simple(1.5, function() + surface.PlaySound("pavetr_mmk/vo/player5.mp3") + chat.AddText(color_white, "- Ебаный рот какие-то ошибки сука") + timer.Simple(2, function() + surface.PlaySound("pavetr_mmk/vo/player6.mp3") + chat.AddText(color_white, "- Чооо plib2, пиздец сенвай так всю сборку расхуярит") + timer.Simple(4, function() + surface.PlaySound("pavetr_mmk/vo/player7.mp3") + chat.AddText(color_white, "- Надо быстрее к паветру идти") + end) + end) + end) + end) +end + +-- 2 sec +function kostich.myarena_ddosed() + surface.PlaySound("ambient/levels/labs/electric_explosion1.wav") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + timer.Simple(0.3, function() + surface.PlaySound("ambient/explosions/explode_8.wav") + chat.AddText(red, "[ИВЕНТ] ", color_white, "В датацентре что-то взорвалось") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + end) + + timer.Simple(0.5, function() + surface.PlaySound("plats/elevbell1.wav") + chat.AddText(color_white, "Filip Bozhenko: Ку мне Васты крашат, я хз чем") + timer.Simple(0.5, function() + surface.PlaySound("plats/elevbell1.wav") + chat.AddText(color_white, "whiteroom: Ку мне Джасты крашат, я хз чем") + timer.Simple(0.5, function() + surface.PlaySound("plats/elevbell1.wav") + chat.AddText(color_white, "senwai: Босс Инферно не дудось пж") + timer.Simple(0.5, function() + surface.PlaySound("pavetr_mmk/vo/player8.mp3") + chat.AddText(color_white, "- Пиздец там все разнесло конечно") + end) + end) + end) + end) +end + +function kostich.hacker_game() + local stat + sound.PlayFile("sound/pavetr_mmk/music/Pavetr-Give_Talon.mp3", "noplay", function(station, errCode, errStr) + if IsValid(station) then + station:Play() + station:SetVolume(2) + stat = station + else + print("Error playing sound!", errCode, errStr) + end + end) + local ply = LocalPlayer() + local rows, cols = 6, 9 + local cellSize = 64 + local spacing = 4 + + local win = vgui.Create("DFrame") + win:SetTitle("Жигуль бекдур™ Loader") + win:SetSize(ScrW(), ScrH()) + win:Center() + win:MakePopup() + win:SetDraggable(false) + win:ShowCloseButton(true) + local col1 = Color(20, 20, 25, 255) + local col2 = Color(0, 150, 255) + + local drops = {} + local next_char_change = 0 + local char_cache = {} + for i = 33, 126 do table.insert(char_cache, string.char(i)) end + + win.OnClose = function() + if IsValid(stat) then + stat:Stop() + end + end + + win.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, col1 or Color(0, 0, 0, 240)) + + local fontSize = 18 + local columns = math.floor(w / fontSize) + + if #drops == 0 then + for i = 1, columns do + drops[i] = { + y = math.random(-h, 0), + speed = math.random(10, 30), + text = {} + } + for j = 1, 15 do drops[i].text[j] = table.Random(char_cache) end + end + end + + surface.SetFont("ui.18") + + local time = RealTime() + local do_change = false + if time > next_char_change then + do_change = true + next_char_change = time + 0.05 + end + + for i = 1, columns do + local drop = drops[i] + + for j = 1, 12 do + local charY = drop.y - (j * fontSize) + + if charY < h + fontSize and charY > -fontSize then + local alpha = 255 - (j * 20) + if alpha > 0 then + if j == 1 then + surface.SetTextColor(200, 255, 200, alpha) + else + surface.SetTextColor(0, 255, 0, alpha) + end + + surface.SetTextPos((i - 1) * fontSize, charY) + + if do_change and math.random() > 0.9 then + drop.text[j] = table.Random(char_cache) + end + + surface.DrawText(drop.text[j] or "0") + end + end + end + + drop.y = drop.y + (drop.speed * FrameTime() * 10) + + if drop.y > h + 200 then + drop.y = -fontSize * 2 + drop.speed = math.random(10, 35) + end + end + draw.RoundedBox(0, 0, 0, w, 24, col2 or Color(50, 50, 50)) + draw.SimpleText("Если вы закроете окно, пропишите play_hacker в консоль чтобы начать игру снова", "ui.20", 12, 36, + color_white) + end + + local grid = {} + local visited = {} + local stack = {} + + for y = 1, rows do + grid[y] = {} + for x = 1, cols do + grid[y][x] = { + type = 0, + rot = 0, + powered = false, + neighbors = { top = false, right = false, bottom = false, left = false } + } + end + end + + local current = { x = 1, y = 1 } + visited[1] = { [1] = true } + table.insert(stack, current) + + while #stack > 0 do + local neighbors = {} + local dirs = { + { dx = 0, dy = -1, name = "top", op = "bottom" }, + { dx = 1, dy = 0, name = "right", op = "left" }, + { dx = 0, dy = 1, name = "bottom", op = "top" }, + { dx = -1, dy = 0, name = "left", op = "right" } + } + + for _, d in ipairs(dirs) do + local nx, ny = current.x + d.dx, current.y + d.dy + if nx > 0 and nx <= cols and ny > 0 and ny <= rows then + visited[ny] = visited[ny] or {} + if not visited[ny][nx] then + table.insert(neighbors, { x = nx, y = ny, dir = d.name, op = d.op }) + end + end + end + + if #neighbors > 0 then + local nextCell = neighbors[math.random(#neighbors)] + grid[current.y][current.x].neighbors[nextCell.dir] = true + grid[nextCell.y][nextCell.x].neighbors[nextCell.op] = true + current = { x = nextCell.x, y = nextCell.y } + visited[current.y][current.x] = true + table.insert(stack, current) + else + current = table.remove(stack) + end + end + + for y = 1, rows do + for x = 1, cols do + local cell = grid[y][x] + local c = 0 + if cell.neighbors.top then c = c + 1 end + if cell.neighbors.right then c = c + 2 end + if cell.neighbors.bottom then c = c + 4 end + if cell.neighbors.left then c = c + 8 end + + if c == 5 or c == 10 then + cell.type = 1 + elseif c == 3 or c == 6 or c == 9 or c == 12 then + cell.type = 2 + elseif c == 7 or c == 11 or c == 13 or c == 14 then + cell.type = 3 + elseif c == 15 then + cell.type = 4 + else + cell.type = 1 + end + + cell.rot = math.random(0, 3) + end + end + + local function GetConnections(type, rot) + local conns = { false, false, false, false } + if type == 1 then + conns = { true, false, true, false } + elseif type == 2 then + conns = { true, true, false, false } + elseif type == 3 then + conns = { true, true, true, false } + elseif type == 4 then + conns = { true, true, true, true } + end + + local rotated = {} + for i = 1, 4 do + local index = (i - 1 - rot) % 4 + 1 + rotated[i] = conns[index] + end + return rotated + end + + local function UpdatePower() + for y = 1, rows do + for x = 1, cols do + grid[y][x].powered = false + end + end + + local q = { { x = 1, y = 1 } } + grid[1][1].powered = true + + local head = 1 + while head <= #q do + local curr = q[head] + head = head + 1 + local cx, cy = curr.x, curr.y + local cell = grid[cy][cx] + local conns = GetConnections(cell.type, cell.rot) + + local dirs = { + { dx = 0, dy = -1, idx = 1, opp = 3 }, + { dx = 1, dy = 0, idx = 2, opp = 4 }, + { dx = 0, dy = 1, idx = 3, opp = 1 }, + { dx = -1, dy = 0, idx = 4, opp = 2 } + } + + for _, d in ipairs(dirs) do + if conns[d.idx] then + local nx, ny = cx + d.dx, cy + d.dy + if nx > 0 and nx <= cols and ny > 0 and ny <= rows then + local neighbor = grid[ny][nx] + local nConns = GetConnections(neighbor.type, neighbor.rot) + if nConns[d.opp] and not neighbor.powered then + neighbor.powered = true + table.insert(q, { x = nx, y = ny }) + end + end + end + end + end + + if grid[rows][cols].powered then + chat.AddText(Color(0, 255, 0), "[ЖИГУЛЬ] ", Color(255, 255, 255), + "Успех! Файл `bruhdecomp.exe` загружен на сервер Senwai-VDS.") + surface.PlaySound("buttons/button14.wav") + RunConsoleCommand("success_hacking") + win:Close() + end + end + + local container = vgui.Create("DGrid", win) + container:SetSize(cols * cellSize + 20, rows * cellSize + 50) + container:SetCols(cols) + container:SetColWide(cellSize) + container:SetRowHeight(cellSize) + container:Center() + + + for y = 1, rows do + for x = 1, cols do + local btn = vgui.Create("DButton") + btn:SetText("") + btn:SetSize(cellSize, cellSize) + + btn.DoClick = function() + grid[y][x].rot = (grid[y][x].rot + 1) % 4 + surface.PlaySound("common/wpn_select.wav") + UpdatePower() + end + + btn.Paint = function(s, w, h) + local cell = grid[y][x] + local isPowered = cell.powered + local col = isPowered and Color(0, 255, 100) or Color(50, 50, 60) + if x == 1 and y == 1 then col = Color(0, 150, 255) end + if x == cols and y == rows then col = isPowered and Color(0, 255, 0) or Color(255, 50, 50) end + + draw.RoundedBox(0, 0, 0, w, h, Color(30, 30, 35)) + + local cx, cy = w / 2, h / 2 + local thick = 8 + local conns = GetConnections(cell.type, cell.rot) + + surface.SetDrawColor(col) + if conns[1] then surface.DrawRect(cx - thick / 2, 0, thick, cy) end + if conns[2] then surface.DrawRect(cx, cy - thick / 2, cx, thick) end + if conns[3] then surface.DrawRect(cx - thick / 2, cy, thick, cy) end + if conns[4] then surface.DrawRect(0, cy - thick / 2, cx, thick) end + + surface.DrawRect(cx - thick, cy - thick, thick * 2, thick * 2) + end + + container:AddItem(btn) + end + end + + UpdatePower() +end + +function kostich.bank_game() + local stat + + sound.PlayFile("sound/pavetr_mmk/music/Senwai-Avtoritet.mp3", "noplay", function(station, errCode, errStr) + if IsValid(station) then + station:Play() + station:SetVolume(1) + stat = station + end + end) + + local ply = LocalPlayer() + + -- Настройки окна + local win = vgui.Create("DFrame") + win:SetTitle("SENWAI_OS v3.0 // BANK_SECURITY_BREACH") + win:SetSize(ScrW(), ScrH()) + win:Center() + win:MakePopup() + win:SetDraggable(false) + win:ShowCloseButton(true) + + -- Цвета (в стиле твоего примера) + local col_bg = Color(20, 20, 25, 255) + local col_accent = Color(0, 150, 255) + local col_accent_dim = Color(0, 150, 255, 50) + local col_text = Color(200, 255, 200) + local col_highlight = Color(255, 200, 50) -- Цвет для активной строки/столбца + + -- == ФОНОВАЯ АНИМАЦИЯ (МАТРИЦА) == + local drops = {} + local char_cache = {} + for i = 33, 126 do table.insert(char_cache, string.char(i)) end + + win.OnClose = function() + if IsValid(stat) then stat:Stop() end + end + + win.Paint = function(s, w, h) + -- Отрисовка фона + draw.RoundedBox(0, 0, 0, w, h, col_bg) + + -- Логика дождя символов (из твоего кода) + local fontSize = 18 + surface.SetFont("ui.18") + local columns = math.floor(w / fontSize) + + if #drops == 0 then + for i = 1, columns do + drops[i] = { + y = math.random(-h, 0), + speed = math.random(10, 30), + text = {} + } + for j = 1, 15 do drops[i].text[j] = table.Random(char_cache) end + end + end + + for i = 1, columns do + local drop = drops[i] + for j = 1, 12 do + local charY = drop.y - (j * fontSize) + if charY < h + fontSize and charY > -fontSize then + local alpha = 100 - (j * 5) -- Сделал чуть тусклее, чтобы не мешало игре + if alpha > 0 then + surface.SetTextColor(0, 150, 255, alpha) -- Синий оттенок для кибер-стиля + surface.SetTextPos((i - 1) * fontSize, charY) + if math.random() > 0.95 then drop.text[j] = table.Random(char_cache) end + surface.DrawText(drop.text[j] or "0") + end + end + end + drop.y = drop.y + (drop.speed * FrameTime() * 10) + if drop.y > h + 200 then + drop.y = -fontSize * 2 + drop.speed = math.random(10, 35) + end + end + end + + -- == ЛОГИКА ИГРЫ == + local gridSize = 6 -- Размер сетки 6x6 + local hexValues = { "1C", "BD", "55", "E9", "7A", "FF" } + local gridData = {} + local requiredSequence = {} + local playerSequence = {} + + local timerStart = CurTime() + local timeLimit = 45 -- Секунд на взлом + local isGameOver = false + + -- Состояние курсора: + -- axis: "row" (горизонтально) или "col" (вертикально) + -- index: номер текущей строки или столбца + local activeState = { axis = "row", index = 1 } + + -- Генерация уровня так, чтобы он был проходим + local function GenerateLevel() + -- 1. Создаем пустую сетку + for y = 1, gridSize do + gridData[y] = {} + for x = 1, gridSize do + gridData[y][x] = { val = table.Random(hexValues), used = false } + end + end + + -- 2. Генерируем гарантированно правильный путь (4 шага) + local pathLength = 4 + local cx, cy = math.random(1, gridSize), 1 -- Начинаем всегда с первой строки + local pathAxis = "row" -- Первый ход выбирается из строки + + -- Записываем первый шаг + gridData[cy][cx].val = table.Random(hexValues) + table.insert(requiredSequence, gridData[cy][cx].val) + + -- Симуляция следующих шагов + local simX, simY = cx, cy + local simAxis = "col" -- После выбора в строке, следующий выбор в столбце + + for i = 2, pathLength do + local nextVal = table.Random(hexValues) + + if simAxis == "col" then + -- Выбираем новую строку в том же столбце + local newY = math.random(1, gridSize) + while newY == simY do newY = math.random(1, gridSize) end + simY = newY + else + -- Выбираем новый столбец в той же строке + local newX = math.random(1, gridSize) + while newX == simX do newX = math.random(1, gridSize) end + simX = newX + end + + gridData[simY][simX].val = nextVal + table.insert(requiredSequence, nextVal) + + -- Инвертируем ось для следующего шага + simAxis = (simAxis == "row") and "col" or "row" + end + + -- Заполняем остальные ячейки случайным мусором (уже сделано в шаге 1, но перезапишем путь) + -- Путь уже записан в сетку, так что всё ок. + end + + GenerateLevel() + + -- == ИНТЕРФЕЙС ИГРЫ == + + local mainPanel = vgui.Create("DPanel", win) + mainPanel:SetSize(800, 600) + mainPanel:Center() + mainPanel.Paint = function(s, w, h) + -- Рамка вокруг игровой зоны + surface.SetDrawColor(col_accent) + surface.DrawOutlinedRect(0, 0, w, h, 2) + draw.RoundedBox(0, 0, 0, w, h, Color(10, 10, 15, 240)) + end + + -- Верхняя панель: Таймер и Цель + local infoPanel = vgui.Create("DPanel", mainPanel) + infoPanel:SetPos(20, 20) + infoPanel:SetSize(760, 100) + infoPanel.Paint = function(s, w, h) + if isGameOver then return end + + -- Таймер / Полоса обнаружения + local timeLeft = math.max(0, timeLimit - (CurTime() - timerStart)) + local pct = timeLeft / timeLimit + + draw.SimpleText("ОБНАРУЖЕНИЕ ВТОРЖЕНИЯ:", "DermaDefaultBold", 0, 0, Color(255, 50, 50)) + draw.RoundedBox(0, 0, 20, w, 20, Color(50, 0, 0)) + draw.RoundedBox(0, 0, 20, w * (1 - pct), 20, Color(255, 0, 0)) -- Красная полоса растет + + if timeLeft <= 0 then + isGameOver = true + surface.PlaySound("buttons/button10.wav") + chat.AddText(Color(255, 0, 0), "[ВЗЛОМ] ", Color(255, 255, 255), "Время истекло! Система заблокирована.") + win:Close() + kostich.bank_game() + end + + -- Отображение необходимой последовательности + draw.SimpleText("ТРЕБУЕМЫЙ БУФЕР:", "DermaDefaultBold", 0, 50, col_accent) + for i, code in ipairs(requiredSequence) do + local col = Color(100, 100, 100) + -- Если этот код уже собран + if i <= #playerSequence then + col = Color(0, 255, 0) + -- Если это следующий код + elseif i == #playerSequence + 1 then + col = Color(255, 255, 255) + end + + draw.SimpleText(code, "DermaLarge", 150 + (i * 60), 45, col) + end + end + + -- Сетка кнопок + local gridBaseX, gridBaseY = 150, 150 + local cellSize = 60 + local gap = 10 + + for y = 1, gridSize do + for x = 1, gridSize do + local btn = vgui.Create("DButton", mainPanel) + btn:SetPos(gridBaseX + (x - 1) * (cellSize + gap), gridBaseY + (y - 1) * (cellSize + gap)) + btn:SetSize(cellSize, cellSize) + btn:SetText(gridData[y][x].val) + btn:SetFont("DermaLarge") + btn:SetColor(col_text) + + btn.Paint = function(s, w, h) + local isHovered = s:IsHovered() + local cell = gridData[y][x] + + -- Логика подсветки доступности + local isActive = false + if not isGameOver and not cell.used then + if activeState.axis == "row" and activeState.index == y then isActive = true end + if activeState.axis == "col" and activeState.index == x then isActive = true end + end + + -- Отрисовка фона кнопки + local drawCol = Color(40, 40, 45) + if cell.used then + drawCol = Color(20, 20, 20) -- Уже нажата + s:SetColor(Color(50, 50, 50)) + elseif isActive then + drawCol = Color(60, 60, 70) + if isHovered then drawCol = col_accent end -- Подсветка при наведении + -- Рисуем линию-подсветку через всю строку/столбец (визуальный эффект) + else + s:SetColor(Color(100, 100, 100, 50)) -- Неактивные тусклые + end + + draw.RoundedBox(4, 0, 0, w, h, drawCol) + + -- Рамка для активной оси + if isActive and not isHovered then + surface.SetDrawColor(col_accent_dim) + surface.DrawOutlinedRect(0, 0, w, h) + end + end + + btn.DoClick = function() + if isGameOver then return end + local cell = gridData[y][x] + + -- Проверка валидности хода + local isValidMove = false + if not cell.used then + if activeState.axis == "row" and activeState.index == y then isValidMove = true end + if activeState.axis == "col" and activeState.index == x then isValidMove = true end + end + + if not isValidMove then + surface.PlaySound("buttons/button10.wav") -- Звук ошибки + return + end + + -- Логика хода + + cell.used = true + table.insert(playerSequence, cell.val) + + -- Проверка правильности + local currentStep = #playerSequence + if playerSequence[currentStep] ~= requiredSequence[currentStep] then + -- ОШИБКА! Сброс прогресса (немного упростим - сбросим только последние ходы или просто проигрыш) + -- Хардкорный вариант: ошибка сбрасывает часть буфера + surface.PlaySound("buttons/button8.wav") + playerSequence = {} -- Полный сброс буфера игрока при ошибке + -- Опционально: уменьшить время таймера при ошибке + timeLimit = timeLimit - 5 + else + surface.PlaySound("buttons/button3.wav") + end + + -- Смена оси + if activeState.axis == "row" then + activeState.axis = "col" + activeState.index = x + else + activeState.axis = "row" + activeState.index = y + end + + -- Проверка победы + if #playerSequence == #requiredSequence then + isGameOver = true + surface.PlaySound("buttons/button14.wav") + chat.AddText(Color(0, 255, 0), "[SenwaiOS] ", Color(255, 255, 255), + "Эксплоит установлен! Деньги переводятся...") + net.Start("pavetr.done_bank") + net.SendToServer() + win:Close() + end + end + end + end + + -- Декоративные линии для понимания оси + local overlay = vgui.Create("DPanel", mainPanel) + overlay:SetSize(800, 600) + overlay:SetMouseInputEnabled(false) -- Чтобы кликать сквозь + overlay.Paint = function(s, w, h) + if isGameOver then return end + + local startX, startY = gridBaseX, gridBaseY + local fullSize = (cellSize + gap) * gridSize + + surface.SetDrawColor(Color(255, 200, 0, 30)) -- Желтая подсветка текущей линии + + if activeState.axis == "row" then + local yPos = startY + (activeState.index - 1) * (cellSize + gap) + draw.RoundedBox(0, startX - 10, yPos - 5, fullSize, cellSize + 10, Color(0, 150, 255, 20)) + else + local xPos = startX + (activeState.index - 1) * (cellSize + gap) + draw.RoundedBox(0, xPos - 5, startY - 10, cellSize + 10, fullSize, Color(0, 150, 255, 20)) + end + end +end + +function kostich.satellite_game() + local stat + sound.PlayFile("sound/music/hl2_song6.mp3", "noplay", function(station, errCode, errStr) + if IsValid(station) then + station:Play() + station:SetVolume(0.5) + stat = station + else + print("Error playing sound!", errCode, errStr) + end + end) + + local ply = LocalPlayer() + + local rows, cols = 8, 8 + local cellSize = 50 + local warehouseCount = 8 + + local win = vgui.Create("DFrame") + win:SetTitle("SATELLITE-GPS: Target Locator v2.0") + win:SetSize(ScrW(), ScrH()) + win:Center() + win:MakePopup() + win:SetDraggable(false) + win:ShowCloseButton(true) + + local colBG = Color(10, 15, 20, 250) + local colGrid = Color(0, 150, 255) + local colHidden = Color(30, 35, 40) + local colScanned = Color(50, 50, 55) + local colTarget = Color(255, 50, 50) + local colText = Color(100, 255, 100) + + local drops = {} + local next_char_change = 0 + local char_cache = {} + for i = 33, 126 do table.insert(char_cache, string.char(i)) end + + win.OnClose = function() + if IsValid(stat) then stat:Stop() end + end + + win.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, colBG) + + local fontSize = 18 + local columns = math.floor(w / fontSize) + if #drops == 0 then + for i = 1, columns do + drops[i] = { y = math.random(-h, 0), speed = math.random(10, 30), text = {} } + for j = 1, 15 do drops[i].text[j] = table.Random(char_cache) end + end + end + + surface.SetFont("DermaDefault") + local time = RealTime() + local do_change = false + if time > next_char_change then + do_change = true + next_char_change = time + 0.05 + end + + for i = 1, columns do + local drop = drops[i] + for j = 1, 12 do + local charY = drop.y - (j * fontSize) + if charY < h + fontSize and charY > -fontSize then + local alpha = 100 - (j * 8) + if alpha > 0 then + surface.SetTextColor(0, 255, 0, alpha) + surface.SetTextPos((i - 1) * fontSize, charY) + if do_change and math.random() > 0.9 then drop.text[j] = table.Random(char_cache) end + surface.DrawText(drop.text[j] or "0") + end + end + end + drop.y = drop.y + (drop.speed * FrameTime() * 10) + if drop.y > h + 200 then + drop.y = -fontSize * 2 + drop.speed = math.random(10, 35) + end + end + + draw.SimpleText("ЗАДАЧА: ОБНАРУЖИТЬ СКЛАДЫ (" .. warehouseCount .. " шт)", "DermaLarge", w / 2, 50, + Color(255, 200, 0), TEXT_ALIGN_CENTER) + draw.SimpleText("Клик по сектору для сканирования. Цифры указывают количество целей рядом.", "DermaDefault", w / + 2, 85, color_white, TEXT_ALIGN_CENTER) + end + + local grid = {} + local foundCount = 0 + local isGameOver = false + + for y = 1, rows do + grid[y] = {} + for x = 1, cols do + grid[y][x] = { + isTarget = false, + revealed = false, + neighborCount = 0 + } + end + end + + local placed = 0 + while placed < warehouseCount do + local rx, ry = math.random(1, cols), math.random(1, rows) + if not grid[ry][rx].isTarget then + grid[ry][rx].isTarget = true + placed = placed + 1 + end + end + + for y = 1, rows do + for x = 1, cols do + if not grid[y][x].isTarget then + local count = 0 + for dy = -1, 1 do + for dx = -1, 1 do + local ny, nx = y + dy, x + dx + if ny >= 1 and ny <= rows and nx >= 1 and nx <= cols then + if grid[ny][nx].isTarget then count = count + 1 end + end + end + end + grid[y][x].neighborCount = count + end + end + end + + local container = vgui.Create("DGrid", win) + container:SetSize(cols * cellSize + 20, rows * cellSize + 20) + container:SetCols(cols) + container:SetColWide(cellSize) + container:SetRowHeight(cellSize) + container:Center() + + for y = 1, rows do + for x = 1, cols do + local btn = vgui.Create("DButton") + btn:SetText("") + btn:SetSize(cellSize, cellSize) + + btn.Paint = function(s, w, h) + local cell = grid[y][x] + + surface.SetDrawColor(0, 0, 0, 200) + surface.DrawOutlinedRect(0, 0, w, h) + + if not cell.revealed then + draw.RoundedBox(0, 1, 1, w - 2, h - 2, colHidden) + surface.SetDrawColor(0, 50, 100, 100) + if math.random() > 0.99 then + surface.SetDrawColor(100, 255, 255, 150) + end + surface.DrawOutlinedRect(2, 2, w - 4, h - 4) + else + if cell.isTarget then + draw.RoundedBox(0, 1, 1, w - 2, h - 2, colTarget) + draw.SimpleText("TARGET", "DermaDefault", w / 2, h / 2, color_black, TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + else + draw.RoundedBox(0, 1, 1, w - 2, h - 2, colScanned) + if cell.neighborCount > 0 then + local intense = math.min(255, cell.neighborCount * 50) + draw.SimpleText(cell.neighborCount, "DermaLarge", w / 2, h / 2, + Color(255, 255 - intense, 255 - intense), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + if s:IsHovered() and not cell.revealed then + surface.SetDrawColor(255, 255, 255, 30) + surface.DrawRect(0, 0, w, h) + end + end + + btn.DoClick = function() + local cell = grid[y][x] + if cell.revealed or isGameOver then return end + + cell.revealed = true + + if cell.isTarget then + surface.PlaySound("buttons/blip1.wav") + foundCount = foundCount + 1 + + if foundCount >= warehouseCount then + isGameOver = true + chat.AddText(Color(0, 255, 0), "[СПУТНИК] ", Color(255, 255, 255), + "Все цели обнаружены. Координаты отправлены.") + surface.PlaySound("buttons/button14.wav") + RunConsoleCommand("success_sat") + win:Close() + end + else + surface.PlaySound("buttons/lightswitch2.wav") + end + end + + container:AddItem(btn) + end + end +end + +-- 2.5 sec +function kostich.boom() + surface.PlaySound("ambient/explosions/explode_3.wav") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + timer.Simple(0.3, function() + surface.PlaySound("ambient/explosions/explode_2.wav") + chat.AddText(red, "[ИВЕНТ] ", color_white, "Слышны взрывы") + end) + + timer.Simple(2, function() + surface.PlaySound("pavetr_mmk/vo/player9.mp3") + chat.AddText(color_white, "- Это еще че за хуйня") + timer.Simple(1, function() + surface.PlaySound("npc/overwatch/radiovoice/off2.wav") + chat.AddText(color_white, "[Рация] Это Мейби Пастер бомбит склады сенвая") + timer.Simple(2, function() + surface.PlaySound("pavetr_mmk/vo/player10.mp3") + chat.AddText(color_white, "- Аа нихуя себе") + timer.Simple(2, function() + surface.PlaySound("pavetr_mmk/vo/player11.mp3") + chat.AddText(color_white, "- Надо быстрее нападать пока они не опомнились") + end) + end) + end) + end) +end + +concommand.Add("play_hacker", function() + kostich.hacker_game() +end) + +concommand.Add("play_gps", function() + kostich.satellite_game() +end) + +function kostich.addMarlyaPosters() + local posters = kostich.getValue("posters") + if not string.find(posters:lower(), "m") then + cur_params["posters"] = cur_params["posters"] .. "m" + end + chat.AddText(Color(0, 255, 0), "[ПОСТЕРЫ] ", Color(255, 255, 255), + "На карте появились постеры Марли (" .. m_cnt .. "шт.)") + save_settings() +end + +function kostich.addGenaPosters() + local posters = kostich.getValue("posters") + if not string.find(posters:lower(), "u") then + cur_params["posters"] = cur_params["posters"] .. "u" + end + chat.AddText(Color(0, 255, 0), "[ПОСТЕРЫ] ", Color(255, 255, 255), + "На карте появились постеры Урбанички (" .. u_cnt .. "шт.)") + save_settings() +end + +function kostich.myarena() + timer.Create("fx_myarena", 1, 6, function() + surface.PlaySound("ambient/explosions/explode_3.wav") + local pos = Vector(2727.877686, -1700.273682, 109.490601) + local effectdata = EffectData() + effectdata:SetOrigin(pos) + effectdata:SetMagnitude(2) + effectdata:SetScale(1) + effectdata:SetRadius(5) + util.Effect("HelicopterMegaBomb", effectdata) + end) +end + +concommand.Add("fx_test", function() + kostich.myarena() +end) + +if not file.Exists("mmk_img", "DATA") then + file.CreateDir("mmk_img") +end + +local MatCache = {} +local DownloadQueue = {} + +local function GetPosterMat(url) + if MatCache[url] then return MatCache[url] end + + local crc = util.CRC(url) + local path = "mmk_img/" .. crc .. ".png" + + if file.Exists(path, "DATA") then + local mat = Material("../data/" .. path, "noclamp smooth") + MatCache[url] = mat + return mat + end + + if not DownloadQueue[url] then + DownloadQueue[url] = true + + http.Fetch(url, function(body, len, headers, code) + if code ~= 200 then return end + + file.Write(path, body) + + DownloadQueue[url] = nil + end, function(err) + print("[Poster Error] Failed to load: " .. url) + print(err) + DownloadQueue[url] = nil + end) + end + + return nil +end + +local peekDist = 400 * 400 +hook.Add("PostDrawTranslucentRenderables", "DrawPostersMMK", function(bDepth, bSkybox) + if bSkybox then return end + + for _, data in ipairs(agits) do + if (not data.show or string.find(kostich.getValue("posters"):lower(), data.show) and (data.pos:DistToSqr(LocalPlayer():GetPos()) <= peekDist) or MQS.CCam) then + local mat = GetPosterMat(data.url) + local scale = data.scale or 0.1 + + cam.Start3D2D(data.pos, data.ang, scale) + + if mat then + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(mat) + surface.DrawTexturedRect(0, 0, data.width, data.height) + else + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(0, 0, data.width, data.height) + + draw.SimpleText("Загрузка...", "DermaLarge", data.width / 2, data.height / 2, color_white, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + end + + cam.End3D2D() + end + end +end) + + +concommand.Add("p_getpos", function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local pos = ply:GetPos() + local code = string.format("Vector(%.2f, %.2f, %.2f)", pos.x, pos.y, pos.z) + + SetClipboardText(code) + chat.AddText(Color(100, 255, 100), "[Cpy] ", color_white, "Позиция скопирована: ", Color(200, 200, 0), code) +end) + +concommand.Add("p_getang", function() + local ply = LocalPlayer() + if not IsValid(ply) then return end + + local ang = ply:EyeAngles() + + local code = string.format("Angle(%.2f, %.2f, %.2f)", ang.p, ang.y, ang.r) + + SetClipboardText(code) + chat.AddText(Color(100, 255, 100), "[Cpy] ", color_white, "Угол скопирован: ", Color(200, 200, 0), code) +end) + +-- function kostich.menu_pasting() +-- local stat +-- sound.PlayFile("sound/ambient/machines/combine_terminal_loop1.wav", "noplay", function(station, errCode, errStr) +-- if IsValid(station) then +-- station:Play() +-- station:SetVolume(0.3) +-- stat = station +-- end +-- end) + +-- local code_lines = { +-- { +-- correct = 2, +-- variants = { +-- "local frame = vgui.Destroy('DFrame')", +-- "local frame = vgui.Create('DFrame')", +-- "dim frame as new Window()", +-- "function frame:Create()" +-- } +-- }, +-- { +-- correct = 1, +-- variants = { +-- "frame:SetSize(500, 400)", +-- "frame.width = 500; frame.height = 400", +-- "frame:Resize_Window(500, 400)", +-- "SetSize(frame, 500, 400)" +-- } +-- }, +-- { +-- correct = 3, +-- variants = { +-- "frame.center()", +-- "frame:Align('center')", +-- "frame:Center()", +-- "util.Center(frame)" +-- } +-- }, +-- { +-- correct = 2, +-- variants = { +-- "frame:ShowCloseButton(false)", +-- "frame:MakePopup()", +-- "frame:EnableMouse()", +-- "input.EnableCursor()" +-- } +-- }, +-- { +-- correct = 4, +-- variants = { +-- "paint(function(w,h) end)", +-- "frame.Paint = new function()", +-- "hook.Add('Paint', frame)", +-- "frame.Paint = function(s, w, h)" +-- } +-- }, +-- { +-- correct = 1, +-- variants = { +-- " draw.RoundedBox(0, 0, 0, w, h, Color(40, 0, 60))", +-- " surface.DrawRect(red, 0, 0, w, h)", +-- " draw.Box(0, 0, w, h, purple)", +-- " render.Clear(40, 0, 60)" +-- } +-- }, +-- { +-- correct = 3, +-- variants = { +-- "end function", +-- "return true", +-- "end", +-- "}" +-- } +-- } +-- } + +-- for k, v in ipairs(code_lines) do +-- v.current = math.random(1, #v.variants) +-- if v.current == v.correct then +-- v.current = (v.current % #v.variants) + 1 +-- end +-- end + +-- local win = vgui.Create("DFrame") +-- win:SetTitle("VSCode - purple_menu.lua [READ ONLY - CLICK TO PATCH]") +-- win:SetSize(ScrW(), ScrH()) +-- win:Center() +-- win:MakePopup() +-- win:ShowCloseButton(true) +-- win:SetDraggable(false) + +-- local colBG = Color(30, 30, 35) +-- local colSidebar = Color(25, 25, 30) +-- local colLineNum = Color(100, 100, 120) +-- local colHighlight = Color(45, 40, 50) + +-- local colFunc = Color(200, 100, 255) +-- local colStr = Color(150, 255, 150) +-- local colErr = Color(255, 100, 100) +-- local colNorm = Color(220, 220, 220) + +-- local lineHeight = 40 +-- local startY = 100 +-- local isWin = false + +-- win.OnClose = function() +-- if IsValid(stat) then stat:Stop() end +-- end + +-- local consoleStatus = "ERROR: Syntax error in line 1..." +-- local consoleCol = colErr + +-- local function CheckWin() +-- local errors = 0 +-- local firstErrorLine = -1 + +-- for k, v in ipairs(code_lines) do +-- if v.current ~= v.correct then +-- errors = errors + 1 +-- if firstErrorLine == -1 then firstErrorLine = k end +-- end +-- end + +-- if errors == 0 then +-- isWin = true +-- consoleStatus = "SUCCESS: 0 Errors, 0 Warnings. Compiling..." +-- consoleCol = Color(0, 255, 0) +-- surface.PlaySound("buttons/button14.wav") + +-- timer.Simple(1.5, function() +-- if IsValid(win) then +-- surface.PlaySound("ambient/levels/labs/electric_explosion1.wav") +-- LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) +-- timer.Simple(0.3, function() +-- cur_params["hud"] = "kostich" +-- save_settings() +-- chat.AddText(red, "[ИВЕНТ] ", color_white, "В сборку вернулся худ от сапа") +-- end) +-- RunConsoleCommand("success_menu") +-- win:Close() +-- end +-- end) +-- else +-- consoleStatus = "COMPILER ERROR: Unexpected symbol near line " .. firstErrorLine +-- consoleCol = colErr +-- end +-- end + +-- win.Paint = function(s, w, h) +-- draw.RoundedBox(0, 0, 0, w, h, colBG) + +-- draw.RoundedBox(0, 0, 0, 60, h, colSidebar) + +-- draw.SimpleText("LUA DEBUGGER", "DermaLarge", 80, 20, Color(200, 100, 255)) +-- draw.SimpleText("Нажмите на строку с ошибкой, чтобы исправить код.", "DermaDefault", 80, 55, Color(150, 150, 150)) + +-- draw.RoundedBox(0, 0, h - 40, w, 40, Color(20, 20, 20)) +-- draw.SimpleText("> " .. consoleStatus, "DermaDefaultBold", 10, h - 28, consoleCol) +-- end + +-- for i, lineData in ipairs(code_lines) do +-- local btn = vgui.Create("DButton", win) +-- btn:SetPos(60, startY + (i - 1) * lineHeight) +-- btn:SetSize(win:GetWide() - 60, lineHeight) +-- btn:SetText("") + +-- btn.Paint = function(s, w, h) +-- if s:IsHovered() and not isWin then +-- draw.RoundedBox(0, 0, 0, w, h, colHighlight) +-- end + +-- local text = lineData.variants[lineData.current] +-- local isCorrect = (lineData.current == lineData.correct) + +-- local txtCol = colNorm +-- if isWin then +-- txtCol = Color(100, 255, 100) +-- elseif isCorrect then +-- txtCol = colFunc +-- else +-- txtCol = Color(255, 100, 100) +-- end + +-- draw.SimpleText(text, "DermaLarge", 20, h / 2, txtCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + +-- surface.SetTextColor(colLineNum) +-- surface.SetFont("DermaLarge") +-- surface.SetTextPos(-40, h / 2 - 10) +-- surface.DrawText(tostring(i)) +-- end + +-- btn.DoClick = function() +-- if isWin then return end + +-- lineData.current = lineData.current + 1 +-- if lineData.current > #lineData.variants then +-- lineData.current = 1 +-- end + +-- surface.PlaySound("buttons/lightswitch2.wav") +-- CheckWin() +-- end +-- end + +-- CheckWin() +-- end + +function kostich.deploy_game() + local music + sound.PlayFile("sound/pavetr_mmk/music/Sugraal&Pavetr-Sup_Paster.mp3", "noplay", function(station, err, str) + if IsValid(station) then + station:Play() + station:SetVolume(1.5) + music = station + end + end) + + local function RestartGame() + surface.PlaySound("buttons/button10.wav") + chat.AddText(Color(255, 50, 50), "[SFTP] ", color_white, + "ОШИБКА: Вам афродетка сервак ебет хеппи мил бекдуром! Экстренное отключение...") + + local owner = vgui.GetWorldPanel() + for _, child in ipairs(owner:GetChildren()) do + if child.IsDeployGame then child:Close() end + end + + timer.Simple(1.5, function() kostich.deploy_game() end) + end + + local files = { + { name = "init.lua", type = "lua", size = "12 KB", bad = false }, + { name = "cl_init.lua", type = "lua", size = "24 KB", bad = false }, + { name = "shared.lua", type = "lua", size = "5 KB", bad = false }, + { name = "sv_db.lua", type = "lua", size = "8 KB", bad = false }, + { name = "config.lua", type = "lua", size = "2 KB", bad = false }, + { name = "brush_decompile.exe", type = "exe", size = "5 MB", bad = true }, + { name = "happy_meal_backdoor.dll", type = "dll", size = "1 MB", bad = true }, + { name = "utils.lua", type = "lua", size = "4 KB", bad = false }, + } + + local needed = 0 + for _, f in ipairs(files) do if not f.bad then needed = needed + 1 end end + local current = 0 + + local win = vgui.Create("DFrame") + win:SetTitle("WinSCP") + win:SetSize(900, 600) + win:Center() + win:MakePopup() + win.IsDeployGame = true + win:ShowCloseButton(true) + + win.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(30, 30, 35)) + draw.RoundedBox(0, 0, 0, w, 25, Color(60, 0, 90)) + draw.RoundedBox(0, 0, h - 30, w, 30, Color(20, 20, 20)) + draw.SimpleText("PENDING: " .. (needed - current) .. " files", "DermaDefault", 10, h - 22, Color(150, 150, 150)) + end + + win.OnClose = function() + if IsValid(music) then + music:Stop() + end + end + + local function CreatePanel(parent, title, x, colHeader) + local p = vgui.Create("DPanel", parent) + p:SetPos(x, 40) + p:SetSize(440, 520) + p.Paint = function(s, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(40, 40, 45)) + draw.RoundedBox(0, 0, 0, w, 30, colHeader) + draw.SimpleText(title, "DermaDefaultBold", 10, 8, color_white) + end + + local scroll = vgui.Create("DScrollPanel", p) + scroll:Dock(FILL) + scroll:DockMargin(5, 35, 5, 5) + + local layout = vgui.Create("DIconLayout", scroll) + layout:Dock(FILL) + layout:SetSpaceY(5) + + return layout + end + + local leftLayout = CreatePanel(win, "LOCAL DISK (Click to Upload)", 5, Color(70, 70, 80)) + local rightLayout = CreatePanel(win, "REMOTE SERVER", 455, Color(0, 100, 50)) + + for _, f in ipairs(files) do + local btn = leftLayout:Add("DButton") + btn:SetSize(420, 40) + btn:SetText("") + + btn.IsUploading = false + btn.Progress = 0 + btn.UploadSpeed = 0 + btn.IsUploaded = false + + btn.DoClick = function(s) + if s.IsUploaded or s.IsUploading then return end + + if f.bad then + RestartGame() + return + end + + surface.PlaySound("buttons/button17.wav") + s.IsUploading = true + s.StartTime = CurTime() + s.Duration = math.Rand(0.5, 1.5) + end + + btn.Think = function(s) + if s.IsUploading then + local fraction = (CurTime() - s.StartTime) / s.Duration + s.Progress = math.Clamp(fraction, 0, 1) + + if fraction >= 1 then + s.IsUploading = false + s.IsUploaded = true + + s:SetParent(rightLayout) + s:Dock(TOP) + leftLayout:InvalidateLayout() + rightLayout:InvalidateLayout() + + surface.PlaySound("buttons/combine_button7.wav") + + current = current + 1 + + if current >= needed then + surface.PlaySound("buttons/button14.wav") + chat.AddText(Color(0, 255, 0), "[SFTP] ", color_white, "ПЛИБ ЗАЛИТ!") + surface.PlaySound("ambient/levels/labs/electric_explosion1.wav") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + timer.Simple(0.3, function() + cur_params["plib"] = true + save_settings() + chat.AddText(red, "[ИВЕНТ] ", color_white, "В сборку вернулся plib") + end) + RunConsoleCommand("success_plib") + win:Close() + end + end + end + end + + btn.Paint = function(s, w, h) + local col = Color(60, 60, 65) + if s.IsUploaded then col = Color(30, 60, 30) end + draw.RoundedBox(4, 0, 0, w, h, col) + + if s.IsUploading then + draw.RoundedBox(4, 0, 0, w * s.Progress, h, Color(0, 200, 255, 50)) + draw.RoundedBox(0, 0, h - 2, w * s.Progress, 2, Color(0, 255, 255)) + end + + local iconCol = Color(100, 200, 255) + if f.bad then iconCol = Color(255, 100, 100) end + draw.RoundedBox(0, 10, 10, 20, 20, iconCol) + + draw.SimpleText(f.name, "DermaDefaultBold", 40, 12, color_white) + + if s.IsUploading then + local pct = math.Round(s.Progress * 100) + draw.SimpleText(pct .. "%", "DermaDefaultBold", w - 50, 12, Color(0, 255, 255)) + elseif s.IsUploaded then + draw.SimpleText("OK", "DermaDefaultBold", w - 40, 12, Color(100, 255, 100)) + else + draw.SimpleText(f.size, "DermaDefault", w - 50, 12, Color(150, 150, 150)) + if s:IsHovered() then + draw.SimpleText("UPLOAD", "DermaDefaultBold", w - 110, 12, Color(0, 255, 255)) + end + end + end + end +end + +function kostich.titles() + local credits = { + { type = "gap", val = 100 }, + { type = "header", val = "МЕРТВАЯ МАМА КОСТ ИЧА РП" }, + { type = "text", val = "Ивент 'Сенвай украл сапы'" }, + { type = "gap", val = 50 }, + + { type = "header", val = "РАЗРАБОТЧИКИ" }, + { type = "text", val = "Pavetr" }, + { type = "text", val = "Encoded - мини-игра про менюшки" }, + { type = "text", val = "MacTavish - пиздец забагованные McQuests+SimpleNPCs" }, + { type = "gap", val = 30 }, + + { type = "header", val = "САУНД ДИЗАЙНЕРЫ" }, + { type = "text", val = "Marlya" }, + { type = "text", val = "Pavetr" }, + { type = "gap", val = 30 }, + + { type = "header", val = "ОЗВУЧКА" }, + { type = "text", val = "Encoded" }, + { type = "text", val = "Spawncode" }, + { type = "text", val = "Caramelka" }, + { type = "text", val = "Rayz" }, + { type = "text", val = "Applio RVC - Urbanichka, Кост Ич" }, + { type = "text", val = "ElevenLabs - Остальные голоса" }, + { type = "gap", val = 30 }, + + { type = "header", val = "ИВЕНТ ПОСВЯЩАЕТСЯ" }, + { type = "text", val = "Shiten (uxcx) - его недавно забрали в ВСРФ" }, + { type = "text", val = "Senwai" }, + { type = "text", val = "Vegaban" }, + { type = "text", val = "Sugraal" }, + { type = "text", val = "Кост Ич (aka @PIXO1166)" }, + { type = "gap", val = 30 }, + + { type = "image", val = base_url .. "gmod.png", height = 300 }, + { type = "text", val = "Powered by Garry's Mod" }, + { type = "gap", val = 60 }, + + { type = "header", val = "ОТДЕЛЬНАЯ БЛАГОДАРНОСТЬ" }, + { type = "text", val = "Jock" }, + { type = "text", val = "Inferno" }, + { type = "text", val = "Spawncode" }, + { type = "text", val = "Spac3e" }, + { type = "gap", val = 30 }, + + { type = "header", val = "ВЫРАЖАЮ БЛАГОДАРНОСТЬ ВСЕМ" }, + { type = "text", val = "упомянутым в сюжетке персонажам" }, + + { type = "gap", val = 30 }, + { type = "header", val = "СПАСИБО ЗА ИГРУ" }, + { type = "text", val = "Заходите на pavetr.ru/ds чтобы не пропустить подобные ивенты" }, + { type = "gap", val = ScrH() * 0.4 }, + + { type = "image", val = base_url .. "senwai_out.png", height = 300 }, + { type = "gap", val = 30 } + } + + local music + sound.PlayFile("sound/pavetr_mmk/music/Marlya-My_Kirik_Luk.mp3", "noplay", function(station, err, str) + if IsValid(station) then + station:Play() + station:SetVolume(3) + music = station + end + end) + + local pnl = vgui.Create("DPanel") + pnl:SetSize(ScrW(), ScrH()) + pnl:MakePopup() + pnl:SetKeyboardInputEnabled(true) + + pnl.OnKeyCodePressed = function() end + gui.EnableScreenClicker(true) + + local scrollY = ScrH() + local contentHeight = 0 + local baseSpeed = 50 + local fastSpeed = 400 + local currentSpeed = baseSpeed + + local container = vgui.Create("DPanel", pnl) + container:SetWide(ScrW() * 0.6) + container:SetPos(ScrW() * 0.2, scrollY) + container.Paint = function() end + + local yCursor = 0 + for _, item in ipairs(credits) do + if item.type == "header" then + local lbl = vgui.Create("DLabel", container) + lbl:SetFont("ui.40") + lbl:SetText(item.val) + lbl:SetTextColor(Color(0, 255, 255)) + lbl:SizeToContents() + lbl:SetPos((container:GetWide() - lbl:GetWide()) / 2, yCursor) + yCursor = yCursor + 50 + elseif item.type == "text" then + local lbl = vgui.Create("DLabel", container) + lbl:SetFont("ui.30") + lbl:SetText(item.val) + lbl:SetTextColor(Color(200, 200, 200)) + lbl:SizeToContents() + lbl:SetPos((container:GetWide() - lbl:GetWide()) / 2, yCursor) + yCursor = yCursor + 30 + elseif item.type == "image" then + local img = vgui.Create("DHTML", container) + local h = item.height or 200 + local w = h * 1.77 + img:SetSize(w, h) + img:SetPos((container:GetWide() - w) / 2, yCursor) + img:SetHTML([[]]) + img:SetMouseInputEnabled(false) + yCursor = yCursor + h + 20 + elseif item.type == "gap" then + yCursor = yCursor + item.val + end + end + + container:SetTall(yCursor) + contentHeight = yCursor + + pnl.Paint = function(s, w, h) + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, w, h) + + if math.random() > 0.95 then + surface.SetDrawColor(255, 0, 0, 5) + surface.DrawRect(0, math.random(0, h), w, 2) + end + + local targetSpeed = baseSpeed + if input.IsKeyDown(KEY_SPACE) or input.IsMouseDown(MOUSE_LEFT) then + targetSpeed = fastSpeed + draw.SimpleText(">>", "DermaLarge", w - 50, h - 50, + Color(255, 50, 50, math.abs(math.sin(CurTime() * 10) * 255))) + end + + currentSpeed = Lerp(FrameTime() * 5, currentSpeed, targetSpeed) + scrollY = scrollY - (currentSpeed * FrameTime()) + container:SetPos(ScrW() * 0.2, scrollY) + + if scrollY + contentHeight < 0 then + if IsValid(music) then + music:Stop() + end + + s:Remove() + gui.EnableScreenClicker(false) + chat.AddText(Color(0, 255, 0), "Спасибо за прохождение сюжетки ММК РП!") + end + end +end + +-- Miha Cheater +do + local createFont = surface.CreateFont + + createFont('quest.title', { + font = 'Roboto Medium', + size = 24, + weight = 500 + }) +end + +local function mat(name) + return Material('eui_quest/' .. name .. '.png') +end + +local ELEMENTS = { + { + icon = mat('Frame 1169754813'), + size = { 1497, 877 }, + pos = { 98, 249 }, + correctPos = { 500, 250 }, + mul = 4, + placed = false, + }, + { + icon = mat('Group 37086'), + size = { 369, 155 }, + pos = { 120, 259 }, + correctPos = { 800, 250 }, + placed = false, + }, + { + icon = mat('Frame 1169754810'), + size = { 701, 70 }, + pos = { 540, 319 }, + correctPos = { 1100, 300 }, + placed = false, + }, + { + icon = mat('Bar'), + size = { 738, 367 }, + pos = { 230, 450 }, + correctPos = { 600, 450 }, + placed = false, + }, + { + icon = mat('Frame 1169754811'), + size = { 46, 352 }, + pos = { 140, 450 }, + correctPos = { 750, 500 }, + placed = false, + }, + { + icon = mat('Button'), + size = { 435, 206 }, + pos = { 230, 810 }, + correctPos = { 900, 750 }, + placed = false, + }, + { + icon = mat('Frame 1169754812'), + size = { 727, 744 }, + pos = { 930, 300 }, + correctPos = { 1050, 350 }, + placed = false, + }, + { + icon = mat('CLOSE'), + size = { 120, 50 }, + pos = { 1350, 329 }, + correctPos = { 1200, 350 }, + placed = false, + }, +} + +local backgroundColor = Color(15, 11, 19) +local secondaryColor = Color(30, 24, 37) +local accentColor = Color(59, 32, 59) + +local RNDX = RNDX +local rect = RNDX.Draw +local BOX_ROUNDED = 16 + +local PANEL = {} + +function PANEL:Init() + self.startTime = CurTime() + sound.PlayFile("sound/pavetr_mmk/music/Pavetr-Chelyabinskii_Central_vol2.mp3", "noplay", function(station, err, str) + if IsValid(station) then + station:Play() + station:SetVolume(3) + self.music = station + end + end) + + + self:AddHeader() + self:AddContainer() + self:AddElements() + self:AddZones() +end + +function PANEL:AddHeader() + local header = self:Add('Panel') + header:Dock(TOP) + header:SetTall(64) + + function header:Paint(w, h) + rect(BOX_ROUNDED, 0, 0, w, h, secondaryColor, RNDX.NO_BL + RNDX.NO_BR) + end + + local title = header:Add('DLabel') + title:Dock(LEFT) + title:DockMargin(10, 0, 0, 0) + title:SetText('PURPLE QUEST') + -- title:SetContentAlignment(5) + title:SetFont('quest.title') + title:SizeToContentsX() + + local close = header:Add('DButton') + close:Dock(RIGHT) + close:DockMargin(0, 10, 10, 10) + close:SetText('') + close:SetWide(44) + + close.scale = 1 + + function close:Paint(w, h) + local matrix = Matrix() + + local x, y = self:LocalToScreen(w / 2, h / 2) + local center = Vector(x, y) + + local isHovered = self:IsHovered() + self.scale = Lerp(FrameTime() * 8, self.scale, isHovered and 1.05 or 1) + + matrix:Translate(center) + matrix:Scale(Vector(1, 1, 0) * self.scale) + matrix:Translate(-center) + + cam.PushModelMatrix(matrix) + rect(BOX_ROUNDED / 2, 0, 0, w, h, accentColor) + draw.SimpleText('✕', 'quest.title', w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.PopModelMatrix() + end + + local frame = self + function close:DoClick() + if frame.music then + frame.music:Stop() + end + frame:Remove() + end +end + +function PANEL:AddContainer() + local container = self:Add('Panel') + container:Dock(FILL) + + self.container = container +end + +function PANEL:AddElements() + local container = self.container + + local side = container:Add('DScrollPanel') + side:Dock(LEFT) + side:DockPadding(10, 0, 0, 0) + side:SetWide(460) + side.VBar:SetHideButtons(true) + side.VBar.Paint = nil + side.VBar.btnGrip.Paint = nil + + function side:Paint(w, h) + rect(BOX_ROUNDED, 0, 0, w, h, secondaryColor, RNDX.NO_TL + RNDX.NO_TR + RNDX.NO_BR) + end + + for i, currentElement in RandomPairs(ELEMENTS) do + local size = currentElement.size + local mul = currentElement.mul or 2 + + local sizeW, sizeH = size[1] / mul, size[2] / mul + + local element = side:Add('DPanel') + element:Dock(TOP) + element:DockMargin(0, 0, 0, 8) + element:SetTall(sizeH) + element:Droppable('purple-menu') + element:SetCursor('hand') + + element.i = i + element.scale = 1 + + function element:Paint(w, h) + local matrix = Matrix() + + local x, y = self:LocalToScreen(w / 2, h / 2) + local center = Vector(x, y) + + local isHovered = self:IsHovered() + self.scale = Lerp(FrameTime() * 8, self.scale, isHovered and 1.05 or 1) + + matrix:Translate(center) + matrix:Scale(Vector(1, 1, 0) * self.scale) + matrix:Translate(-center) + + cam.PushModelMatrix(matrix) + surface.SetMaterial(currentElement.icon) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(w / 2 - sizeW / 2, 0, sizeW, sizeH) + cam.PopModelMatrix() + end + end +end + +function PANEL:AddZones() + local fr = self + local list = self.container:Add('Panel') + list:Dock(FILL) + list:DockMargin(10, 10, 0, 0) + + local currentElements = 1 + + local zonePanles = {} + + for i = 1, 8 do + local element = ELEMENTS[i] + + local icon = element.icon + + local size = element.size + local sizeW, sizeH = size[1] / 2, size[2] / 2 + + local pos = element.pos + local posX, posY = pos[1] / 2, pos[2] / 2 + + local zone = list:Add('DPanel') + zone:SetSize(sizeW, sizeH) + zone:SetPos(posX, posY) + + if i ~= 1 then + zone:SetVisible(false) + end + + zone.startTime = CurTime() + + function zone:Paint(w, h) + local matrix = Matrix() + + local startTime = self.startTime + + local timeElapsed = (CurTime() - startTime) * 4 + local value = math.sin(timeElapsed) * 0.5 + 0.5 + local currentAlpha = Lerp(value, 0.5, 1) + + local mul = self.placed and 1 or currentAlpha + + cam.PushModelMatrix(matrix) + surface.SetMaterial(icon) + surface.SetDrawColor(255 * mul, 255 * mul, 255 * mul, self.placed and 255 or 50) + surface.DrawTexturedRect(w / 2 - sizeW / 2, 0, sizeW, sizeH) + cam.PopModelMatrix() + end + + zone:Receiver('purple-menu', function(self, panels, dropped) + if not dropped then return end + + local firstPanel = panels[1] + + if firstPanel.i == i and not self.placed and currentElements == firstPanel.i then + self.placed = true + + currentElements = currentElements + 1 + + local nextPanel = zonePanles[currentElements] + if nextPanel then + nextPanel:SetVisible(true) + end + + if currentElements == (#ELEMENTS + 1) then + if fr.music then + fr.music:Stop() + end + surface.PlaySound("ambient/levels/labs/electric_explosion1.wav") + LocalPlayer():ScreenFade(SCREENFADE.IN, Color(0, 0, 0, 255), 0.3, 0) + timer.Simple(0.3, function() + cur_params["hud"] = "kostich" + save_settings() + chat.AddText(red, "[ИВЕНТ] ", color_white, "В сборку вернулся худ от сапа") + end) + RunConsoleCommand("success_menu") + fr:Remove() + end + end + end) + + zonePanles[i] = zone + end +end + +do + local colorAlpha = ColorAlpha(accentColor, 255) + + function PANEL:Paint(w, h) + local startTime = self.startTime + + local timeElapsed = CurTime() - startTime + local value = math.sin(timeElapsed) * 0.5 + 0.5 + local currentAlpha = Lerp(value, 0.5, 1) + + colorAlpha = ColorAlpha(colorAlpha, 255 * currentAlpha) + + RNDX().Rect(0, 0, w, h) + :Color(colorAlpha) + :Shadow(20, 20) + :Rad(BOX_ROUNDED) + :Draw() + + rect(BOX_ROUNDED, 0, 0, w, h, backgroundColor) + end +end + +vgui.Register('quest.purpleMenu', PANEL, 'EditablePanel') + +function kostich.menu_pasting() + local menu = vgui.Create('quest.purpleMenu') + menu:SetSize(1300, 800) + menu:Center() + menu:MakePopup() +end + +concommand.Add("play_menu", function() + kostich.menu_pasting() +end) + +concommand.Add("play_plib", function() + kostich.deploy_game() +end) + +concommand.Add("play_bank", function() + kostich.bank_game() +end) + +concommand.Add("show_titles", function() + kostich.titles() +end) + + +local vol = 4 +net.Receive("pavetr.sendSound", function(_, ply) + local snd = net.ReadString() + sound.PlayFile(snd, "noplay", function(station, errCode, errStr) + if (IsValid(station)) then + station:Play() + station:SetVolume(vol) + else + print("[QuestSay] Error playing sound", snd, errCode, errStr) + end + end) +end) + +local highlight = Color(0, 155, 255) +local delay = 60 * 5 +local msgs = { + { highlight, "[MMK] ", color_white, "Правила и информация о персонажах находятся в ", highlight, "F4->Вики" }, + { highlight, "[MMK] ", color_white, "Нет озвучки или музыки? Скачайте контент по инструкции в ", highlight, "F4->Вики" }, + { highlight, "[MMK] ", color_white, "Заходите на ", highlight, "pavetr.ru/ds ", color_white, "чтобы не пропустить новые ивенты" }, + { highlight, "[MMK] ", color_white, "Вы можете покупать хилки и броню в ", highlight, "F4->Магазин" }, +} +timer.Create("chat_msg", delay, 0, function() + local msg = msgs[math.random(#msgs)] + chat.AddText(unpack(msg)) +end) diff --git a/addons/!kostich/lua/autorun/client/hud.lua b/addons/!kostich/lua/autorun/client/hud.lua new file mode 100644 index 0000000..ab84163 --- /dev/null +++ b/addons/!kostich/lua/autorun/client/hud.lua @@ -0,0 +1,571 @@ +surface.CreateFont('ui.60', {font = 'roboto', size = 60, weight = 700, extended = true}) +surface.CreateFont('ui.40', {font = 'roboto', size = 40, weight = 500, extended = true}) +surface.CreateFont('ui.39', {font = 'roboto', size = 39, weight = 500, extended = true}) +surface.CreateFont('ui.38', {font = 'roboto', size = 38, weight = 500, extended = true}) +surface.CreateFont('ui.37', {font = 'roboto', size = 37, weight = 500, extended = true}) +surface.CreateFont('ui.36', {font = 'roboto', size = 36, weight = 500, extended = true}) +surface.CreateFont('ui.35', {font = 'roboto', size = 35, weight = 500, extended = true}) +surface.CreateFont('ui.34', {font = 'roboto', size = 34, weight = 500, extended = true}) +surface.CreateFont('ui.33', {font = 'roboto', size = 33, weight = 500, extended = true}) +surface.CreateFont('ui.32', {font = 'roboto', size = 32, weight = 500, extended = true}) +surface.CreateFont('ui.31', {font = 'roboto', size = 31, weight = 500, extended = true}) +surface.CreateFont('ui.30', {font = 'roboto', size = 30, weight = 500, extended = true}) +surface.CreateFont('ui.29', {font = 'roboto', size = 29, weight = 500, extended = true}) +surface.CreateFont('ui.28', {font = 'roboto', size = 28, weight = 500, extended = true}) +surface.CreateFont('ui.27', {font = 'roboto', size = 27, weight = 400, extended = true}) +surface.CreateFont('ui.26', {font = 'roboto', size = 26, weight = 400, extended = true}) +surface.CreateFont('ui.25', {font = 'roboto', size = 25, weight = 400, extended = true}) +surface.CreateFont('ui.24', {font = 'roboto', size = 24, weight = 400, extended = true}) +surface.CreateFont('ui.23', {font = 'roboto', size = 23, weight = 400, extended = true}) +surface.CreateFont('ui.22', {font = 'roboto', size = 22, weight = 400, extended = true}) +surface.CreateFont('ui.20', {font = 'roboto', size = 20, weight = 400, extended = true}) +surface.CreateFont('ui.19', {font = 'roboto', size = 19, weight = 400, extended = true}) +surface.CreateFont('ui.18', {font = 'roboto', size = 18, weight = 400, extended = true}) +surface.CreateFont('ui.17', {font = 'roboto', size = 17, weight = 550, extended = true}) +surface.CreateFont('ui.16', {font = 'roboto', size = 16, weight = 550, extended = true}) +surface.CreateFont('ui.15', {font = 'roboto', size = 15, weight = 550, extended = true}) +surface.CreateFont('ui.12', {font = 'roboto', size = 12, weight = 550, extended = true}) +surface.CreateFont('ui.10', {font = 'roboto', size = 10, weight = 550, extended = true}) +surface.CreateFont('ui.5percent', {font = 'roboto', size = math.ceil(ScrH() * 0.05), weight = 500, extended = true}) + +local CurTime = CurTime +local IsValid = IsValid +local ipairs = ipairs +local Color = Color +local DrawColorModify = DrawColorModify +local nw_GetGlobal = GetGlobalBool +local table_Filter = table.Filter +local player_GetAll = player.GetAll +local hook_Call = hook.Call + +local GM = GAMEMODE or {} + +timer.Simple(0.1,function() + GM = GAMEMODE +end) + +local smoothHP = 0 +local smoothAR = 0 +local smoothHG = 0 + +local math_ceil = math.ceil +local math_sin = math.sin +local math_max = math.max + +local draw_SimpleText = draw.SimpleText +local draw_SimpleTextOutlined = draw.SimpleTextOutlined +local draw_OutlinedBox = draw.OutlinedBox +local function draw_Box(x,y,w,h,col) + draw.RoundedBox(0,x,y,w,h,col) +end + +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawLine = surface.DrawLine +local surface_DrawTexturedRect = surface.DrawTexturedRect +local surface_GetTextSize = surface.GetTextSize +local surface_SetFont = surface.SetFont +local surface_SetMaterial = surface.SetMaterial +local surface_DrawOutlinedRect = surface.DrawOutlinedRect +local surface_SetTextPos = surface.SetTextPos +local surface_SetTextColor = surface.SetTextColor +local surface_DrawText = surface.DrawText +local surface_DrawRect = surface.DrawRect + +local cam_Start3D2D = cam.Start3D2D +local cam_End3D2D = cam.End3D2D + +local color_white = color_white +local color_black = color_black +local color_red = Color(245,0,0) +local color_orange = Color(245,120,0) +local color_blue = Color(51,128,255) +local color_darkred = Color(100, 0, 0) +local color_15k = Color(240,191,0) + +local color_gradient = Color(50, 50, 50) +local color_bg = Color(15,15,15,255) +local color_outline = Color(75,75,75,255):Copy() + +local color_money = Color(135, 135, 31, 60) + +local color_health = Color(82, 158, 60, 255) +local color_ph_health = Color(82, 158, 60, 60) + +local color_armor = Color(37, 112, 153, 255) +local color_ph_armor = Color(37, 112, 153, 60) + +local color_food = Color(217, 155, 11, 255) +local color_ph_food = Color(217, 155, 11, 60) + +local color_job = Color(35, 31, 32, 60) +local color_grace = Color(76, 24, 84, 255) +local color_sup = Color(27, 82, 102, 60) + +local color_agenda = Color(33, 92, 132, 60) +local color_laws = Color(135, 33, 33, 60) +local color_arrest_warrants = Color(211, 36, 36, 60) +local color_hits = Color(40, 40, 40, 60) + +local lic_icon = Material("icon16/page.png") +local mat_laws = lic_icon +local material_licence = mat_laws +local hp_icon = Material("icon16/heart.png") +local ar_icon = Material("icon16/shield.png") +local hg_icon = Material("icon16/cup.png") +local want_icon = Material("icon16/star.png") +local money_icon = Material("icon16/money.png") +local time_icon = Material("icon16/clock.png") + +local job_icon = Material("icon16/vcard.png") + +local sw, sh = ScrW(), ScrH() +local height = 0 +local LP + +local players = {} + +surface.CreateFont("Hud", { + font = "EuropaNuovaExtraBold", + extended = true, + size = 25, + weight = 500, + antialias = true, +}) + +surface.CreateFont("HudS", { + font = "EuropaNuovaExtraBold", + extended = true, + size = 23, + weight = 500, + antialias = true, +}) + +surface.CreateFont('HudFont', { + font = 'Tahoma', + size = 20, + weight = 350 +}) + +surface.CreateFont('HudFontLaws', { + font = 'Roboto', + size = 19, + extended = true, + shadow = true, + weight = 350 +}) + + +surface.CreateFont('HudFont2', { + font = 'Tahoma', + size = 24, + weight = 700 +}) + +surface.CreateFont('HudFont3', { + font = 'Tahoma', + size = 30, + weight = 700 +}) + +surface.CreateFont('BannedInfo', { + font = 'Roboto', + size = 42, + weight = 700 +}) + +surface.CreateFont('PlayerInfo', { + font = 'Tahoma', + extended = true, + outline = true, + shadow = true, + size = 128, + weight = 750 +}) + +local talkingplayers = {} + +hook.Add('PlayerStartVoice', 'rp.hud.PlayerStartVoice', function(pl) + talkingplayers[pl] = true +end) + +hook.Add('PlayerEndVoice', 'rp.hud.PlayerEndVoice', function(pl) + talkingplayers[pl] = nil +end) + +-- utils + +local ColValues = {} + +local function varcol(name, val) + if ColValues[name] == nil then + ColValues[name] = {} + ColValues[name].Old = val + ColValues[name].Flash = SysTime() + return color_white + end + + if ColValues[name].Old ~= val then + ColValues[name].Flash = SysTime() + 0.2 + ColValues[name].Old = val + return color_blue + end + + if ColValues[name].Flash > SysTime() then + return color_blue + end + return color_white +end + +local function SecondsToClock(seconds) + local seconds = tonumber(seconds) + + if seconds <= 0 then + return "00:00:00"; + else + hours = string.format("%02.f", math.floor(seconds/3600)); + mins = string.format("%02.f", math.floor(seconds/60 - (hours*60))); + secs = string.format("%02.f", math.floor(seconds - hours*3600 - mins *60)); + return hours..":"..mins..":"..secs + end +end + +local blur = Material( "pp/blurscreen" ) +function drawBlur( x, y, w, h, layers, density, alpha ) + surface.SetDrawColor( 255, 255, 255, alpha ) + surface.SetMaterial( blur ) + + for i = 1, layers do + blur:SetFloat( "$blur", ( i / layers ) * density ) + blur:Recompute() + + render.UpdateScreenEffectTexture() + render.SetScissorRect( x, y, x + w, y + h, true ) + surface.DrawTexturedRect( 0, 0, ScrW(), ScrH() ) + render.SetScissorRect( 0, 0, 0, 0, false ) + end +end + +function drawBlurPadded(x,y,w,h,layers,density,alpha,color) + --draw.RoundedBox(0,x,y,w,h,color) + drawBlur(x,y,w,h,layers,density,alpha) + surface_SetDrawColor(255,255,255,150) + surface_DrawOutlinedRect(x,y,w,h,2) +end + +local function InfoBar() + if not IsValid(LP) then return end + local health = LP:Health() + function numh(numtokok) + if numtokok > 100 then + return 100 + end + if numtokok <= 100 and numtokok >= 0 then + return numtokok + end + if numtokok < 0 then + return 0 + end + end + smoothHP = numh(Lerp(10 * FrameTime(), smoothHP, health)) + drawBlurPadded(10,ScrH()-170,450,50,3,5,255,Color(0,0,0,200)) + draw_SimpleText("ММК RolePlay","ui.30",180,ScrH()-145,color_white,2,1) + drawBlurPadded(10,ScrH()-115,450,110,3,5,255,Color(0,0,0,200)) + draw.RoundedBox(3,20,ScrH()-150+50,150,25,Color(122,89,89)) + draw.RoundedBox(3,20,ScrH()-150+50,smoothHP*1.5,25,Color(222,91,91)) + draw_SimpleText(health.."%","ui.20",168,ScrH()-137+50,color_white,2,1) + surface_SetDrawColor(color_white) + surface_SetMaterial(hp_icon) + + local armor = LP:Armor() + smoothAR = numh(Lerp(10 * FrameTime(), smoothAR, armor)) + surface_DrawTexturedRect(23,ScrH()-147+50,20,20) + draw.RoundedBox(3,20,ScrH()-120+50,150,25,Color(93,128,143)) + draw.RoundedBox(3,20,ScrH()-120+50,smoothAR*1.5,25,Color(98,181,217)) + draw_SimpleText(armor.."%","ui.20",168,ScrH()-107+50,color_white,2,1) + surface_SetDrawColor(color_white) + surface_SetMaterial(ar_icon) + + local hunger = 100 + smoothHG = numh(Lerp(10 * FrameTime(), smoothHG, hunger)) + surface_DrawTexturedRect(23,ScrH()-117+50,20,20) + draw.RoundedBox(3,20,ScrH()-90+50,150,25,Color(74,125,82)) + draw.RoundedBox(3,20,ScrH()-90+50,smoothHG*1.5,25,Color(84,179,99)) + draw_SimpleText(hunger.."%","ui.20",168,ScrH()-77+50,color_white,2,1) --30 + surface_SetDrawColor(color_white) + surface_SetMaterial(hg_icon) + surface_DrawTexturedRect(23,ScrH()-87+50,20,20) + if LP:getDarkRPVar("HasGunlicense") then + surface_SetDrawColor(color_white) + surface_SetMaterial(lic_icon) + surface_DrawTexturedRect(180,ScrH()-160,30,30) + else + surface_SetDrawColor(Color(100,100,100)) + surface_SetMaterial(lic_icon) + surface_DrawTexturedRect(180,ScrH()-160,30,30) + end + + local money = LP:getDarkRPVar("money") + draw.RoundedBox(3,180,ScrH()-100,20+surface.GetTextSize(DarkRP.formatMoney(money))+20,25,Color(124,156,96)) + surface_SetDrawColor(color_white) + surface_SetMaterial(money_icon) + surface_DrawTexturedRect(183,ScrH()-98,20,20) + draw_SimpleText(DarkRP.formatMoney(money),"ui.20",205,ScrH()-138+50,color_white,0,1) + local job = team.GetName(LP:Team()) + draw.RoundedBox(3,180,ScrH()-70,20+surface.GetTextSize(job)+20,25,Color(96, 156, 146)) + surface_SetDrawColor(color_white) + surface_SetMaterial(job_icon) + surface_DrawTexturedRect(183,ScrH()-117+50,20,20) + draw_SimpleText(job,"ui.20",205,ScrH()-107+50,color_white,0,1) + local time = os.date("%H:%M:%S", os.time()) + draw.RoundedBox(3,180,ScrH()-41,20+surface.GetTextSize(time)+20,25,Color(153,108,171)) + surface_SetDrawColor(color_white) + surface_SetMaterial(time_icon) + surface_DrawTexturedRect(183,ScrH()-39,20,20) + draw_SimpleText(time,"ui.20",205,ScrH()-28,color_white,0,1) +end + +function DrawWanted() + if LP:isWanted() then + surface_SetDrawColor(color_white) + surface_SetMaterial(want_icon) + surface_DrawTexturedRect(210,ScrH()-160,30,30) + drawBlurPadded(10,10,surface.GetTextSize('Вы в розыске за: ' .. tostring(LP:GetWantedReason()))*1.3,50,3,5,255,Color(0,0,0,200)) + draw_SimpleText('Вы в розыске за: ' .. tostring(LP:GetWantedReason()),"ui.23",20,33,color_white,0,1) + else + surface_SetDrawColor(Color(100,100,100)) + surface_SetMaterial(want_icon) + surface_DrawTexturedRect(210,ScrH()-160,30,30) + end +end + +function DrawArrested() + if LP:isArrested() then + draw_SimpleTextOutlined('Вы на бутылке', 'HudFont2', sw/2, sh - 20, color_white, 1, 1, 1, color_black) + end +end + +hook.Add("DrawDeathNotice", "DisableKills", function() + return 0,0 +end) + + +function DrawLockdown() + surface_SetFont('HudFont2') + + local w = surface_GetTextSize('На улицах объявлен коменданский час!') + 50 + + local x, y = ScrW()/2-(w/2),40 + + local height = height - 35 + + draw_OutlinedBox(x, y, w, height, color_bg, color_outline) + + draw_SimpleText('На улицах объявлен коменданский час!', 'HudFont2', ScrW()/2,20, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) +end + +local blacklist = { + + weapon_physcannon = true, + + weapon_bugbait = true, + + weapon_pickaxe = true + +} + +local function DrawAmmo() + local wep = LocalPlayer():GetActiveWeapon() + + if IsValid(wep) then + -- if (wep.DrawCrosshair or (not wep.BaseClass)) then + -- local centerX, centerY = (sw * 0.5), (sh * 0.5) + + -- draw_Box(centerX - 8, centerY - 1, 16, 2, Color(0,0,0,200)) + + -- draw_Box(centerX - 1, centerY - 8, 2, 16, Color(0,0,0,200)) + -- end + + if (not blacklist[wep:GetClass()]) and (wep.DrawAmmo ~= false) then + if (wep.SimpleAmmoCount) then + local w, h = 7 + 5, 41 + local x, y = ScrW() - w - 10, ScrH() - h - 10 + draw_SimpleText(LocalPlayer():GetAmmoCount(wep:GetPrimaryAmmoType()), 'HudFont3', ScrW()/2, ScrH()-15, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + local count = wep:Clip1() + local max = wep:GetMaxClip1() + local extra = LocalPlayer():GetAmmoCount(wep:GetPrimaryAmmoType()) + + if (count > -1) then + local w, h = max * 7 + 5, 41 + local x, y = ScrW() - w - 10, ScrH() - h - 10 + + if wep.SelectiveFire then + draw_SimpleText("E+R Для смены режима", 'ui.25', ScrW()/2, ScrH()-45, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + draw_SimpleText(count .. '/' .. max .. ' - ' .. extra, 'HudFont3', ScrW()/2, ScrH()-15, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + end +end + +local nodraw = { + CHudHealth = true, + CHudBattery = true, + CHudSuitPower = true, + CHudAmmo = true, + CHudSecondaryAmmo = true, + CHudWeaponSelection = true, + CHudCrosshair = true +} + +hook.Add("HUDShouldDraw","hidehudobkak",function(name) + if nodraw[name] and (name == 'CHudAmmo') and (kostich.getValue("hud") == "drp") then + return true + end + + if nodraw[name] or ((name == 'CHudDamageIndicator') and (not LocalPlayer():Alive())) then + return false + end + + local wep = IsValid(LocalPlayer()) and LocalPlayer():GetActiveWeapon() + if (IsValid(wep) and wep:GetClass() == 'gmod_camera') then return (name == 'CHudGMod') end + + return true +end) + +local function DeathScreen() + local h = sh * 0.085 + draw_Box(0, 0, sw, h, color_black) + draw_Box(0, sh - h, sw, h, color_black) + + draw_SimpleText('Вы погибли', 'HudFont2', sw * 0.5, h * 0.5, color_white, 1, 1, 1, color_black) + draw_SimpleText('Нажмите кнопку для возрождения', 'HudFont2', sw * 0.5, sh - h * 0.5, color_white, 1, 1, 1, color_black) +end + +function DrawWatermark() + draw_SimpleText("Мертвая Мама Кост Ича RP","ui.25",0,0,Color(255,255,255,150)) +end + +hook.Add("HUDPaint","kostichhud",function() + if MQS.CCam then return end + if kostich.getValue("hud") == "kostich" then + sw, sh = ScrW(), ScrH() + + LP = LocalPlayer() + + if (not LocalPlayer():Alive()) then + DeathScreen() + elseif IsValid(LocalPlayer():GetActiveWeapon()) and LocalPlayer():GetActiveWeapon():GetClass() == "gmod_camera" then + DrawWatermark() + else + InfoBar() + DrawAmmo() + DrawWanted() + DrawArrested() + if nw_GetGlobal('lockdown') then + DrawLockdown() + end + end + end +end) + +timer.Create('rp.hud.DrawCache', 0.5, 0, function() + local LP = LocalPlayer() + players = player.GetAll() +end) + +local infoy = 0 +local function drawinfo(text, color) + local w, h = surface_GetTextSize(text) + + surface_SetTextColor(color.r, color.g, color.b, color.a) + local x = -(w * 0.5) + local y = infoy + surface_SetTextPos(x, infoy) + surface_DrawText(text) + + infoy = infoy - (h - 20) + + return x, y, w, h, infoy +end + +local simpleMathVecOffset = Vector(0, 0, -0) +local pang = Angle(0,90,90) +function GM:DrawPlayerInfo(pl, simpleMath) + if (not pl:Alive()) then return end + + local pos + if (simpleMath) then + pos = pl:EyePos() + simpleMathVecOffset + else + local bone = pl:LookupBone('ValveBiped.Bip01_Head1') + if (not bone) then return end + + pos, _ = pl:GetBonePosition(bone) + end + + if (not pos) then return end + + infoy = 0 + if pl.InfoOffset then + pos.z = pos.z + pl.InfoOffset + 7.5 + else + pos.z = pos.z + 12.5 + end + + pang.y = (LocalPlayer():EyeAngles().y - 90) + + cam_Start3D2D(pos, pang, 0.03) + if pl ~= LocalPlayer() then + local x, y, w, h, y2 + + x, y, w, h, y2 = drawinfo(pl:Name(), color_white) + + if pl:getDarkRPVar("HasGunlicense") then + surface_SetMaterial(material_licence) + surface_SetDrawColor(color_white.r, color_white.g, color_white.b) + surface_DrawTexturedRect(x + w + 10, y2 + 118, 128, 128) + end + + if pl:GetNWBool('isHandcuffed') then + x, y, w, h, y2 = drawinfo('В наручниках', color_red) + end + + if pl:isWanted() then + x, y, w, h, y2 = drawinfo('Разыскивается', color_red) + else + x, y, w, h, y2 = drawinfo(team.GetName(pl:Team()), team.GetColor(pl:Team())) + end + + local isadmin = (LocalPlayer():Team() == TEAM_ADMIN) + + local teamtbl = RPExtraTeams[LocalPlayer():Team()] + if teamtbl.medic or isadmin then + x, y, w, h, y2 = drawinfo(pl:Health() .. ' HP', color_red) + end + + if (teamtbl.bmidealer or isadmin) and (pl:Armor() > 0) then + x, y, w, h, y2 = drawinfo(pl:Armor() .. ' Armor', color_blue) + end + + if talkingplayers[pl] then + x, y, w, h, y2 = drawinfo('Говорит', color_white) + elseif pl:IsTyping() then + x, y, w, h, y2 = drawinfo('Печатает', color_white) + end + end + cam_End3D2D() +end + +function GM:PostDrawTranslucentRenderables() + if (not IsValid(LocalPlayer())) then return end + + surface_SetFont('PlayerInfo') + for k, v in ipairs(players) do + if IsValid(v) and not v:GetNWBool('Cloak') then + self:DrawPlayerInfo(v) + end + end +end diff --git a/addons/!kostich/lua/autorun/client/proplist.lua b/addons/!kostich/lua/autorun/client/proplist.lua new file mode 100644 index 0000000..4935105 --- /dev/null +++ b/addons/!kostich/lua/autorun/client/proplist.lua @@ -0,0 +1,122 @@ +local PANEL = {} +function PANEL:Init() + self.PanelList = vgui.Create("DPanelList", self) + self.PanelList:SetPadding(4) + self.PanelList:SetSpacing(2) + self.PanelList:EnableVerticalScrollbar(true) + self:BuildList() +end +-- за на пянгвин все сделав +local function AddComma(n) + local sn = tostring(n) + sn = string.ToTable(sn) + local tab = {} + + for i = 0, #sn - 1 do + if i % 3 == #sn % 3 and not (i == 0) then + table.insert(tab, ",") + end + + table.insert(tab, sn[i + 1]) + end + + return string.Implode("", tab) +end + +function PANEL:BuildList() + self.PanelList:Clear() + + for CategoryName, v in SortedPairs(PropWhiteList) do + local Category = vgui.Create("DCollapsibleCategory", self) + self.PanelList:AddItem(Category) + Category:SetExpanded(false) + Category:SetLabel(CategoryName) + Category:SetCookieName("EntitySpawn." .. CategoryName) + + local Content = vgui.Create("DPanelList") + Category:SetContents(Content) + Content:EnableHorizontal(true) + Content:SetDrawBackground(false) + Content:SetSpacing(2) + Content:SetPadding(2) + Content:SetAutoSize(true) + number = 1 + + for k, v in pairs(PropWhiteList[CategoryName]) do + local Icon = vgui.Create("SpawnIcon", self) + local Model = v + + Icon:SetModel(v) + + Icon.DoClick = function() + RunConsoleCommand("gm_spawn", Model) + end + + local lable = vgui.Create("DLabel", Icon) + lable:SetFont("DebugFixedSmall") + lable:SetTextColor(color_black) + lable:SetText(Model) + lable:SetContentAlignment(5) + lable:SetWide(self:GetWide()) + lable:AlignBottom(-42) + Content:AddItem(Icon) + number = number + 1 + end + end + + self.PanelList:InvalidateLayout() +end + +function PANEL:PerformLayout() + self.PanelList:StretchToParent(0, 0, 0, 0) +end + +local CreationSheet = vgui.RegisterTable(PANEL, "Panel") +local function CreateContentPanel() + local ctrl = vgui.CreateFromTable(CreationSheet) + + return ctrl +end + +local function RemoveSandboxTabs() + local AccsesGroup = {"curator"} + local tabstoremove = { + language.GetPhrase("spawnmenu.content_tab"), + language.GetPhrase("spawnmenu.category.npcs"), + language.GetPhrase("spawnmenu.category.entities"), + language.GetPhrase("spawnmenu.category.weapons"), + language.GetPhrase("spawnmenu.category.vehicles"), + language.GetPhrase("spawnmenu.category.postprocess"), + language.GetPhrase("spawnmenu.category.dupes"), + language.GetPhrase("spawnmenu.category.saves") + } + local tabstoremoveSup = { + language.GetPhrase("spawnmenu.content_tab"), + language.GetPhrase("spawnmenu.category.npcs"), + language.GetPhrase("spawnmenu.category.entities"), + language.GetPhrase("spawnmenu.category.vehicles"), + language.GetPhrase("spawnmenu.category.postprocess"), + language.GetPhrase("spawnmenu.category.dupes"), + language.GetPhrase("spawnmenu.category.saves") + } + if table.HasValue(AccsesGroup, LocalPlayer():GetUserGroup()) or LocalPlayer():IsSuperAdmin() then + if !LocalPlayer():IsSuperAdmin() then + for k, v in pairs(g_SpawnMenu.CreateMenu.Items) do + if table.HasValue(tabstoremoveSup, v.Tab:GetText()) then + g_SpawnMenu.CreateMenu:CloseTab(v.Tab, true) + RemoveSandboxTabs() + end + end + end + else + for k, v in pairs(g_SpawnMenu.CreateMenu.Items) do + if table.HasValue(tabstoremove, v.Tab:GetText()) then + g_SpawnMenu.CreateMenu:CloseTab(v.Tab, true) + RemoveSandboxTabs() + end + end + end +end +hook.Add("SpawnMenuOpen", "blockmenutabs", RemoveSandboxTabs) + +spawnmenu.AddCreationTab("Разрешенные пропы", CreateContentPanel, "icon16/application_view_tile.png", 4) diff --git a/addons/!kostich/lua/autorun/client/rndx_cl.lua b/addons/!kostich/lua/autorun/client/rndx_cl.lua new file mode 100644 index 0000000..0e117e1 --- /dev/null +++ b/addons/!kostich/lua/autorun/client/rndx_cl.lua @@ -0,0 +1,686 @@ +--[[ +Copyright (c) 2025 Srlion (https://github.com/Srlion) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] + +local bit_band = bit.band +local surface_SetDrawColor = surface.SetDrawColor +local surface_SetMaterial = surface.SetMaterial +local surface_DrawTexturedRectUV = surface.DrawTexturedRectUV +local surface_DrawTexturedRect = surface.DrawTexturedRect +local render_CopyRenderTargetToTexture = render.CopyRenderTargetToTexture +local math_min = math.min +local math_max = math.max +local DisableClipping = DisableClipping +local type = type + +local SHADERS_VERSION = "1761484375" +local SHADERS_GMA = [========[R01BRAOHS2tdVNwrAFce/mgAAAAAAFJORFhfMTc2MTQ4NDM3NQAAdW5rbm93bgABAAAAAQAAAHNoYWRlcnMvZnhjLzE3NjE0ODQzNzVfcm5keF9yb3VuZGVkX2JsdXJfcHMzMC52Y3MAWwUAAAAAAAAAAAAAAgAAAHNoYWRlcnMvZnhjLzE3NjE0ODQzNzVfcm5keF9yb3VuZGVkX3BzMzAudmNzAD8EAAAAAAAAAAAAAAMAAABzaGFkZXJzL2Z4Yy8xNzYxNDg0Mzc1X3JuZHhfc2hhZG93c19ibHVyX3BzMzAudmNzAEAFAAAAAAAAAAAAAAQAAABzaGFkZXJzL2Z4Yy8xNzYxNDg0Mzc1X3JuZHhfc2hhZG93c19wczMwLnZjcwDkAwAAAAAAAAAAAAAFAAAAc2hhZGVycy9meGMvMTc2MTQ4NDM3NV9ybmR4X3ZlcnRleF92czMwLnZjcwAeAQAAAAAAAAAAAAAAAAAABgAAAAEAAAABAAAAAAAAAAAAAAACAAAAo73gKAAAAAAwAAAA/////1sFAAAAAAAAIwUAQExaTUHcDgAAEgUAAF0AAAABAABos178gL/sqTCKKmhqvjMGBcspzCTmp/gKUuCPCSeJ6i+BM7QEKYcFW21fRRw+YLGjb6YWXU3Dlwr8WEhzRKa8KwmC/lFMmO69CG1fpOFcygopZ5z40DdKrcnlVZen4TOHrP3hEJCoIJgyo2bogJS03SXW5PQ/G92VoqBr5y4G1Y1aDEaZ3oF+wPYcowySi51s6V9Zp1zAi2573ER3fFq3umlLoSbfrvxgllHGCdEqvOqxBpBMc9iVB2vD2Gr2dGHxwFgOUsnc0TZGh6zvCR+BiDIjOft0J2kttjAVDnPrJLXTOk/inDdGbGvuXcdi6YQsefnG1jCviSZ2OPSCbUfVuV3jgj+hBiVXhkA1RODpepTEIx8Ip7RBjOjckgKijP+kXlvzn+u57PaRYOLCOA3Lv67zHO7uwmM9lT1b7WhFhBZUV6lwoUNue5WZgfGj2TEe4x7ct90aNy2QrIZvRdLjuBNy3YDj2Ixi/uhgCwCxIpvjDVwnPlwpYfqAirwJX6VsjWa2WsHNVdWsSLHoUfK4mUnPtb0BXWrJjnDP0mgiQ9jcqwKlLVyUtF9OJGskkK9G2yqlCBaOPf2ko2C6wXRAzIa3GtPzGCIxXfyety1QBPdtSCNL+i1zc9mTM2/lEOpt1ENwzbFvoD8eyNbpoH1xMXJBjV5ZtSXYPOSLOGeSIKfml0FNIlaO97LLo4lAdQUY6DfBIIg28PYzh9w65QHtrhZm6IlVwSJHkNWBb025SNYVYlHJD0SXSEj3aonN0014SxPr+SGJvspnvZRhkHxU+RctW4G9AW72dTbbMZ1QzhIVREhLScYoh39FyTE7em8i+aQUbxCVC9EqhIhbl+Jv938/zZ7ahjvZz4rESob/utbRJRSwqGSCq3zF37O0Jx8f6uOfQybJrlW91PRfdPBlCBjS076sH9vU1WpPwvAj5GUhRyYZVaPU95Jtk5CflsYh5lsyks8Ogf2iu7KyJ56p+O+9RoDHGgc2WvNVYMaDsYlytO0qJd1TavnMSF4yyzoX8SSGdAUDudJC/g4sO8bmR20VfPLJi1Y9u6EQ9szvClRZKgi5f75penrPHVH54nrKHQKE3ueeKBh4UyQSkwoRsJscJDvFRRsfqohmKGPDaUSsRS7hlhNWXP96waSr3vfmnJMg68pY5z429Own3gEKatY9py3AwaoPyo2L+64RHdUMbnbOICQYgRpU71G3A/Jk+eLYdiWGeG2CG0MliL7CoM46y6nAWv/XfzNHIhZIzI3IovL7pReA1OrL9QOIYeqoDyAM6ZkAtgoWn4nL87JXzMe2lP2ah7WcnbdV08mS/SjcmG8/EAtI8SBdRXe1EOfhWy3YeIAzXcPnisyubzTzTCmzWNzrrtE0sVNzcLrfQQNTSp4qDC+26yRbliSKeOiwMkDQWuLAl5FTI+ouM0l71sR0/ERtCc7BcO2x8FlpXy7417qNSANIafXi4KvmYx49k+inp+8GRbLDaSI+JBgomvgOitAA8uK3MWb3wVpAqr7Xfj8LrW0NO0vftd4isSVXsAvNTxKtcopeRdvOtMb68bTXgmwRKzFPXWFhcPBCHS9s5g7eQi2r19dVbHM/9cbR291EwQY4qD+o/dGcy3X0XEsQDqEJeHIJJCF+YtYJlwGh9Sgt6u9FlmY6cbv3qcgQIDvUeJZhO9dsX0jRTmtECNSFulrGN+ImfVlcvKot+ITSwKcx5xuxch0pLPJVoQD/////BgAAAAEAAAABAAAAAAAAAAAAAAACAAAAEGKVVQAAAAAwAAAA/////z8EAAAAAAAABwQAQExaTUGcCgAA9gMAAF0AAAABAABoo168gr/sqj/+d4A+ZRYQgV7S4rtyFsxXr3/6NhK5TKu5av8aJ9UYm8pKZbRyroxyaEhLrmMrwX1zJRlFCkesRopYO0aezXDufrxs+fveaGVMrchUCdnNu2CvsJiPM7blQ7vvZ7y67LpDP8pO6h6gV2MFXiJ4h/72N4WQTCkAeRnppNIyMjz9hK8s+UzZen1QFYEOctLYhFxCnxxEVQPXeJ753wH5yz4535FjupFlRugjcozpJqf/8fnOtWfP8hTLWRRwyLyAvwyzAIKoUo147sb9Dnx5unCFh0a0KFMjqpbT+tf5iyp7i4PHkVZnx7GeyArtxrqxDCoh4Ro2wxvZiLxRaN0GbYESx0zT5+78esccJqk+TC6m+vghEN25qEmKGDeNXoKvzMymfuSD5g+K3f1R8WU2l/4PmcpoJrHo79LCpyYZPKT6VcdNZLVb+8traB61lFD/JoeuQ6dA9zvAsIyehTW3D8fVBAcY3YVaLHA+rKbaG+YxgF1+/bCVrdqIC5+Bk3xzjOQGNApKNYtr7KZMG7duvzAAv7LChxIUp6mLesQwAffH/fHys7KsNHfkFr+ixC6i4Pt/OmanNgACrOdSZsoj2hoeeYh6kSZYwS9HOIpC72/oJbSYPFpSehIQodMDHZuKII3v+BY7kMa6EHD7BUWmyL5rBI4wV8t1BQiSECApoXS3LDp85uEnpypIW8K7/F692aGe+UbFjXKXkB1+1C/CVZC5NpZjpBJVSeMdRxNG/YW2Js/H2D60Y4LIBNgYmpUBVg8VmQt1DlhxCCNjGl9iNI1Md6Az+Fzlbbs9poPgTOunODz47bFEwDK/nck220lt0KLof7QbO3QJ+oN9orclAyt70LdmYN8xzc41yBDavreSyFfKEsnIME/mYvUwKyjYj0nD22Qgcn8J2u662XsI6oJLR+dwaoQ5ecvCkZsxsZu8CKk4hZ8QKfNLWmaACGc7wxbDEeDz2e2lX8s4/JF6IpXI6cwnCzG5lLTFxXwz8IdUuzpsgkpOUdAjizCaXcbESBfjs89LVqav0mQmCHnUsH7Fk66hRwgxjuuIxT799+J487roeuuRyHlXjHd5vUEcU04uszgU1V/kem97vwCWBzT6dnjhtsokGEqzRWgC02GGidbLn8spuBR4T7gV/KCxok47uz6DTjgzHfXzcWITcGtf9OUx57lFzzPgH/Rg46+37FwDRSYqRjN4zIQg0sdcI9XXl3EghGCEdwfBn7H8IONZYwlp5DKNgALvRP1gbT/t/wlXziaeyEu04p04/x3QjO5FX6FkWfdEXfoQchrJZVZCPSE5w+TmX027jKQjhp7oFcPEfkb5XtEqTQAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAALqvbaQAAAAAMAAAAP////9ABQAAAAAAAAgFAEBMWk1BPA4AAPcEAABdAAAAAQAAaItfnIC/7KknxcVFPc7QbqZor3QsQPQUdzflG66hK8OH6waTx1K7zbeuivPeI5Gp87L+/ZIw5yYyIQPxbzOU92vHD7ci1YcrRGTYeSL0O9pGpGE1RhTznrCmz1qJJcfXPX+VZk+3o98JGsV69uIaHKeg6y6r2xPvqqeCq9tyUqYGqJcxq4yrP/96FutryyecmD1V3j1cIMaB3WBkb68Lp9+zlLLShdPmSZAKeT0gsSCsZpCOZsJOGVqwLIFTM/L+Ovi3s9TuCNv1j3BrM3mDRaTpyqBacBeLB4dQHTVpdsEkHSG3RGLL7nLr0sGwWsc4H5SJ65gK8uiREq4a8uVEgcpPn8v5GnpqtTV55+NuRwsFWUAobDNtzJdPhcvg7zROa6S+a2y/33X+slYsAdvXioR6oH4uWqHLBOdCneyzVY41iMj9oJ06xgGz4QplngPpcGSIU+4SyG3m3kw5TGoloWnMnZckaTBf4pr3jCw5Dja7MPLmlhqaS2Mcy0w/pUb6CvnphQQuUfU3Mge08yOLal9G2Qx3oej/TMRhfnVPQG9vF95bTLcIF0JtN2Dd4Smq/u3qtE29P/1BumEPxPfOUV8NvfzmqM9iZFdat2GhEi0H6GRdPaWFHFL2fQcGS5mIvmGRc/7ugh187nIXy6oMnPNREsQB7Kr59aldMYOhqI3txDILtofE55qIvp/kprm+0Ry4pYbGo6TF/MgvsMZzUmeI8l84sg8XV6ADNEvf88lr9eYwcSFFWs2grgIVFmSfLNwGhYv5DHllrMBdACBhLwivjXHFVH7IlaYrXiuQMEK5tVcZXfPqCbKdvQet/SacGPbqDj8CKge0fm0nB04iSELMvL6YpeS+OYb8EX6X3JNq7LjVX4kLYBstjVd9A9zK68rJKkZtjL1cSdTRcUzgwAX4cx879LyDsZlQxMLHpDVrNxqEBeTX+aq7/M/KCDSEafmMHk0gdPYgXjtiwAW6iyYpSydFi4YAGXLhDctOkBcuC1l705plrYUjuUjYSoBRAKmgMlJB6T3qj25znc2iaVHVZqc77TgRv9SHMcMC0Eh2h/TOK9XEMzC0juGZ3yKpYX1Jq9kgcE+2lT3oi29wOEmr6GuqjSXafkA15F0z6VehraBRbVuTAnbwtPMrlkOpF/oAQsw90eJT1LLXMNsjzwNV13uSo2nwoSdbiY92xjFyOu84u/T54NKR/wBHXCBvAEhh/F7J/S50remgEppnqgXvfZuYGPY2+QHEdumQmfQs6y4aYpSta1IVn5e4fR4HUVq0dcSsAnc4iR06pIfZwBWhvCIoQBgaRhQGBpqIy7X5Q1vaeLbZEJw2bxlPO53wqkJbEuCiN3gmteRRet1yCUcprue+m7/mmxG9zyyhBZtm/abR4f7SWqLrvm0YKFAHKDkTKzKmDcqhgfiDXIF+NMlzDc7w+1E9Tp4mVmpBYP7uuKkqJzHJh0ZvB1X4ZRPr6NM+TlJFl0ob+W9H6xiCCq3HnGfMYAh7i4YpXdXMROqKeiDMY0EfBzWmc+hFRAIAUlpdMN/CTpZtxWO2bAT5e+cdlcdMuwbhEQeW/bybYZaR+zKdUE4WSm3S4j7Ijo4sM2IM1yuEYCjDF1uYvJn9StkGGhh3Vf/t9v6N/8S4eJe3FWl9sEDSwbarIz+SMnRtgUo7F2jqUMlyJLlVk1fmaCoDlwAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAAI3wHlQAAAAAMAAAAP/////kAwAAAAAAAKwDAEBMWk1BXAkAAJsDAABdAAAAAQAAaJNe3IM/7KknxcRHY47O9fYyNdc3kY24ieD8FTrqtxFJe67osEaB+xDr9sgDqjs5X5yhoFQ/2qprKt7mID/eRH1zgb8C4z6LiW0mxCypgbau9V6CxI/yfXTrgjsWPOK8WZxTfql/MI8nsS5t7+3q3095QGdU5TUDjLmpV4DaIeiN/lwkHMPlSDittCckryLg/X9mhxwy4EQaIYin2mDrYaTaj5wp3ilELOAmUoNc9RbdeJ/KcyNwACVe26YWJCH5pWDlj5LB77XVel+bujGjfXfsm+DnIjhXljYTUOxvaXH0NKLvWTW2fCPVJ28mqza3hJwrCKousqJxq/UASVB6yVJt3fIHp4qIYMSjG78GKHi5uo+IlpJTQo0aykOb+WeVmZRg6b1Jq0IF2lD9kXSr2IcPixMaNXAPCbS/gHy3gleI9ETS6xps510jCsO8FhihoGr3C3Pc38QIjvZyCksi6W8UOGj5JcFG1YhwT/3dPthPiXriqUTCmwBm8+M4Mp3rck5PjEbUYk94nVjiT2ecHzEgExiETuWmkDsy7rgWBRNQ1J87vZHy3ofHvnURv2yHASLZYmGOmxQAWAPcWb8oq0qqZ2HOClAgyO7yquJSP4MwrqrmUk6eTWzCk/Iy3PDkReKwl4mqPf8GQT9J6qMg3l0gHvNfzYKTsjWnwIQcSCEKhGlw0o+cxaEA+tpQMemJQMki+rwP+/lg7B/WHlWWdAWXLJHxvO6yEhM/5bb81WhYEky06g3aKVH3+eWltCBaAE/yz+RCH0C1e7KrUagIwEs96oujKF78ju44iBEhUGU35QiBNMEgpjIzsHWC77qunQtHBs275LvYwP19KlCOErrjBTWEmpsuvH6lcY9lv30lNt37HP5pl7IBMzutFE4rgrrI9gsh7uIhrbGIOE7WkCS7OmqRrk4Q+EfPbtdkpTKJUDtznwGLYkqm50y/5g/8MM/6FVDjtCIn8YIjRYh/Y7CfBFQ2YGb0SeMTa/qOH2MksY1lRwIJM4EYTd1E/2Gd9SQIbJNjLVTLzIqXE4gUmIfv/YMysmge4k6dW+tMFo+5NM4HQ1YN42DSWMpxY6T0hzf2dAhXXWOos9HYxcJkJYbXYXP2k+ApuVUDyFh6c/3NRL2ugIk02pukuQMLww5w4AD6vOFExw5gH9FB0WfO40XEIHq9eAjmRB5p+VP3eaJywpgjGSpXzeCiI5BVhDxMZZJwLhe1EsAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAAHdDQpkAAAAAMAAAAP////8eAQAAAAAAAOYAAEBMWk1BZAEAANUAAABdAAAAAQAAaJVd1Ic/7GMZqmFmSkZT5Syb4y1BQfzcRtdcyOB5r7JLn4LwCNmyuJTsWtJr8LdDB+d807YTbmGBRNEYgNCazErHtD6CDDk7YfK7qU+cRg9+q3eO+bdyOPpnVfTY+iJt5kQXhXbw6vmZKQpyqBmTpxuep55WCep8C8P87e4u76dPtUA7J1Gs0FIPXJBVMFlRm0gkua8O4gTbsSjsa7AehgJStVTCBbqrRJuKSTHAR462FrPlswhNs53YmCOGQeRBXbZUlM2KeVFbYANLUT90mfIAAP////8AAAAA]========] +do + local DECODED_SHADERS_GMA = util.Base64Decode(SHADERS_GMA) + if not DECODED_SHADERS_GMA or #DECODED_SHADERS_GMA == 0 then + print("Failed to load shaders!") -- this shouldn't happen + return + end + + file.Write("rndx_shaders_" .. SHADERS_VERSION .. ".gma", DECODED_SHADERS_GMA) + game.MountGMA("data/rndx_shaders_" .. SHADERS_VERSION .. ".gma") +end + +local function GET_SHADER(name) + return SHADERS_VERSION:gsub("%.", "_") .. "_" .. name +end + +local BLUR_RT = GetRenderTargetEx("RNDX" .. SHADERS_VERSION .. SysTime(), + 1024, 1024, + RT_SIZE_LITERAL, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(2, 256, 4, 8 --[[4, 8 is clamp_s + clamp-t]]), + 0, + IMAGE_FORMAT_BGRA8888 +) + +local NEW_FLAG; do + local flags_n = -1 + function NEW_FLAG() + flags_n = flags_n + 1 + return 2 ^ flags_n + end +end + +local NO_TL, NO_TR, NO_BL, NO_BR = NEW_FLAG(), NEW_FLAG(), NEW_FLAG(), NEW_FLAG() + +-- Svetov/Jaffies's great idea! +local SHAPE_CIRCLE, SHAPE_FIGMA, SHAPE_IOS = NEW_FLAG(), NEW_FLAG(), NEW_FLAG() + +local BLUR = NEW_FLAG() + +local RNDX = {} + +local shader_mat = [==[ +screenspace_general +{ + $pixshader "" + $vertexshader "" + + $basetexture "" + $texture1 "" + $texture2 "" + $texture3 "" + + // Mandatory, don't touch + $ignorez 1 + $vertexcolor 1 + $vertextransform 1 + " 1 then + -- local inv = 1 / k + -- TL, TR, BL, BR = TL * inv, TR * inv, BL * inv, BR * inv + -- end + + return clamp0(TL), clamp0(TR), clamp0(BL), clamp0(BR) + end +end + +local function SetupDraw() + local TL, TR, BL, BR = normalize_corner_radii() + + local matrix = MATRIXES[MAT] + MATRIX_SetUnpacked( + matrix, + + BL, W, OUTLINE_THICKNESS or -1, END_ANGLE, + BR, H, SHADOW_INTENSITY, ROTATION, + TR, SHAPE, BLUR_INTENSITY or 1.0, 0, + TL, TEXTURE and 1 or 0, START_ANGLE, 0 + ) + MATERIAL_SetMatrix(MAT, "$viewprojmat", matrix) + + if COL_R then + surface_SetDrawColor(COL_R, COL_G, COL_B, COL_A) + end + + surface_SetMaterial(MAT) +end + +local MANUAL_COLOR = NEW_FLAG() +local DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE + +local function draw_rounded(x, y, w, h, col, flags, tl, tr, bl, br, texture, thickness) + if col and col.a == 0 then + return + end + + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + local using_blur = bit_band(flags, BLUR) ~= 0 + if using_blur then + return RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + end + + MAT = ROUNDED_MAT; if texture then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", texture) + TEXTURE = texture + end + + W, H = w, h + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + elseif col then + COL_R, COL_G, COL_B, COL_A = col.r, col.g, col.b, col.a + else + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + end + + SetupDraw() + + -- https://github.com/Jaffies/rboxes/blob/main/rboxes.lua + -- fixes setting $basetexture to ""(none) not working correctly + return surface_DrawTexturedRectUV(x, y, w, h, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.Draw(r, x, y, w, h, col, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r) +end + +function RNDX.DrawOutlined(r, x, y, w, h, col, thickness, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, nil, thickness or 1) +end + +function RNDX.DrawTexture(r, x, y, w, h, col, texture, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, texture) +end + +function RNDX.DrawMaterial(r, x, y, w, h, col, mat, flags) + local tex = mat:GetTexture("$basetexture") + if tex then + return RNDX.DrawTexture(r, x, y, w, h, col, tex, flags) + end +end + +function RNDX.DrawCircle(x, y, r, col, flags) + return RNDX.Draw(r / 2, x - r / 2, y - r / 2, r, r, col, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleOutlined(x, y, r, col, thickness, flags) + return RNDX.DrawOutlined(r / 2, x - r / 2, y - r / 2, r, r, col, thickness, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleTexture(x, y, r, col, texture, flags) + return RNDX.DrawTexture(r / 2, x - r / 2, y - r / 2, r, r, col, texture, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleMaterial(x, y, r, col, mat, flags) + return RNDX.DrawMaterial(r / 2, x - r / 2, y - r / 2, r, r, col, mat, (flags or 0) + SHAPE_CIRCLE) +end + +local USE_SHADOWS_BLUR = false + +local function draw_blur() + if USE_SHADOWS_BLUR then + MAT = SHADOWS_BLUR_MAT + else + MAT = ROUNDED_BLUR_MAT + end + + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + SetupDraw() + + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 0) + surface_DrawTexturedRect(X, Y, W, H) + + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 1) + surface_DrawTexturedRect(X, Y, W, H) +end + +function RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + X, Y = x, y + W, H = w, h + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + + draw_blur() +end + +local function setup_shadows() + X = X - SHADOW_SPREAD + Y = Y - SHADOW_SPREAD + W = W + (SHADOW_SPREAD * 2) + H = H + (SHADOW_SPREAD * 2) + + TL = TL + (SHADOW_SPREAD * 2) + TR = TR + (SHADOW_SPREAD * 2) + BL = BL + (SHADOW_SPREAD * 2) + BR = BR + (SHADOW_SPREAD * 2) +end + +local function draw_shadows(r, g, b, a) + if USING_BLUR then + USE_SHADOWS_BLUR = true + draw_blur() + USE_SHADOWS_BLUR = false + end + + MAT = SHADOWS_MAT + + if r == false then + COL_R = nil + else + COL_R, COL_G, COL_B, COL_A = r, g, b, a + end + + SetupDraw() + -- https://github.com/Jaffies/rboxes/blob/main/rboxes.lua + -- fixes having no $basetexture causing uv to be broken + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.DrawShadowsEx(x, y, w, h, col, flags, tl, tr, bl, br, spread, intensity, thickness) + if col and col.a == 0 then + return + end + + local OLD_CLIPPING_STATE = DisableClipping(true) + + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + X, Y = x, y + W, H = w, h + SHADOW_SPREAD = spread or 30 + SHADOW_INTENSITY = intensity or SHADOW_SPREAD * 1.2 + + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + + OUTLINE_THICKNESS = thickness + + setup_shadows() + + USING_BLUR = bit_band(flags, BLUR) ~= 0 + + if bit_band(flags, MANUAL_COLOR) ~= 0 then + draw_shadows(false, nil, nil, nil) + elseif col then + draw_shadows(col.r, col.g, col.b, col.a) + else + draw_shadows(0, 0, 0, 255) + end + + DisableClipping(OLD_CLIPPING_STATE) +end + +function RNDX.DrawShadows(r, x, y, w, h, col, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity) +end + +function RNDX.DrawShadowsOutlined(r, x, y, w, h, col, thickness, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity, thickness or 1) +end + +local BASE_FUNCS; BASE_FUNCS = { + Rad = function(self, rad) + TL, TR, BL, BR = rad, rad, rad, rad + return self + end, + Radii = function(self, tl, tr, bl, br) + TL, TR, BL, BR = tl or 0, tr or 0, bl or 0, br or 0 + return self + end, + Texture = function(self, texture) + TEXTURE = texture + return self + end, + Material = function(self, mat) + local tex = mat:GetTexture("$basetexture") + if tex then + TEXTURE = tex + end + return self + end, + Outline = function(self, thickness) + OUTLINE_THICKNESS = thickness + return self + end, + Shape = function(self, shape) + SHAPE = SHAPES[shape] or 2.2 + return self + end, + Color = function(self, col_or_r, g, b, a) + if type(col_or_r) == "number" then + COL_R, COL_G, COL_B, COL_A = col_or_r, g or 255, b or 255, a or 255 + else + COL_R, COL_G, COL_B, COL_A = col_or_r.r, col_or_r.g, col_or_r.b, col_or_r.a + end + return self + end, + Blur = function(self, intensity) + if not intensity then + intensity = 1.0 + end + intensity = math_max(intensity, 0) + USING_BLUR, BLUR_INTENSITY = true, intensity + return self + end, + Rotation = function(self, angle) + ROTATION = math.rad(angle or 0) + return self + end, + StartAngle = function(self, angle) + START_ANGLE = angle or 0 + return self + end, + EndAngle = function(self, angle) + END_ANGLE = angle or 360 + return self + end, + Shadow = function(self, spread, intensity) + SHADOW_ENABLED, SHADOW_SPREAD, SHADOW_INTENSITY = true, spread or 30, intensity or (spread or 30) * 1.2 + return self + end, + Clip = function(self, pnl) + CLIP_PANEL = pnl + return self + end, + Flags = function(self, flags) + flags = flags or 0 + + -- Corner flags + if bit_band(flags, NO_TL) ~= 0 then + TL = 0 + end + if bit_band(flags, NO_TR) ~= 0 then + TR = 0 + end + if bit_band(flags, NO_BL) ~= 0 then + BL = 0 + end + if bit_band(flags, NO_BR) ~= 0 then + BR = 0 + end + + -- Shape flags + local shape_flag = bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS) + if shape_flag ~= 0 then + SHAPE = SHAPES[shape_flag] or SHAPES[DEFAULT_SHAPE] + end + + -- Blur flag + if bit_band(flags, BLUR) ~= 0 then + BASE_FUNCS.Blur(self) + end + + -- Manual color flag + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + end + + return self + end, + +} + +local RECT = { + Rad = BASE_FUNCS.Rad, + Radii = BASE_FUNCS.Radii, + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Shape = BASE_FUNCS.Shape, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + + Draw = function(self) + if START_ANGLE == END_ANGLE then + return -- nothing to draw + end + + local OLD_CLIPPING_STATE + if SHADOW_ENABLED or CLIP_PANEL then + -- if we are inside a panel, we need to draw outside of it + OLD_CLIPPING_STATE = DisableClipping(true) + end + + if CLIP_PANEL then + local sx, sy = CLIP_PANEL:LocalToScreen(0, 0) + local sw, sh = CLIP_PANEL:GetSize() + render.SetScissorRect(sx, sy, sx + sw, sy + sh, true) + end + + if SHADOW_ENABLED then + setup_shadows() + draw_shadows(COL_R, COL_G, COL_B, COL_A) + elseif USING_BLUR then + draw_blur() + else + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + end + + SetupDraw() + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) + end + + if CLIP_PANEL then + render.SetScissorRect(0, 0, 0, 0, false) + end + + if SHADOW_ENABLED or CLIP_PANEL then + DisableClipping(OLD_CLIPPING_STATE) + end + end, + + GetMaterial = function(self) + if SHADOW_ENABLED or USING_BLUR then + error("You can't get the material of a shadowed or blurred rectangle!") + end + + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + end + SetupDraw() + + return MAT + end, +} + +local CIRCLE = { + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + + Draw = RECT.Draw, + GetMaterial = RECT.GetMaterial, +} + +local TYPES = { + Rect = function(x, y, w, h) + RESET_PARAMS() + MAT = ROUNDED_MAT + X, Y, W, H = x, y, w, h + return RECT + end, + Circle = function(x, y, r) + RESET_PARAMS() + MAT = ROUNDED_MAT + SHAPE = SHAPES[SHAPE_CIRCLE] + X, Y, W, H = x - r / 2, y - r / 2, r, r + r = r / 2 + TL, TR, BL, BR = r, r, r, r + return CIRCLE + end +} + +setmetatable(RNDX, { + __call = function() + return TYPES + end +}) + +-- Flags +RNDX.NO_TL = NO_TL +RNDX.NO_TR = NO_TR +RNDX.NO_BL = NO_BL +RNDX.NO_BR = NO_BR + +RNDX.SHAPE_CIRCLE = SHAPE_CIRCLE +RNDX.SHAPE_FIGMA = SHAPE_FIGMA +RNDX.SHAPE_IOS = SHAPE_IOS + +RNDX.BLUR = BLUR +RNDX.MANUAL_COLOR = MANUAL_COLOR + +function RNDX.SetFlag(flags, flag, bool) + flag = RNDX[flag] or flag + if tobool(bool) then + return bit.bor(flags, flag) + else + return bit.band(flags, bit.bnot(flag)) + end +end + +function RNDX.SetDefaultShape(shape) + DEFAULT_SHAPE = shape or SHAPE_FIGMA + DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE +end + +_G.RNDX = RNDX \ No newline at end of file diff --git a/addons/!kostich/lua/autorun/hidetools.lua b/addons/!kostich/lua/autorun/hidetools.lua new file mode 100644 index 0000000..483705a --- /dev/null +++ b/addons/!kostich/lua/autorun/hidetools.lua @@ -0,0 +1,44 @@ +local allowedTools = { + ["remover"] = true, + ["colour"] = true, + ["camera"] = true, + ["light"] = true, + ["skeypads"] = true, + ["material"] = true, + ["textscreen"] = true, + ["advdupe2"] = true, + ["stacker_improved"] = true, + ["nocollide"] = true, + ["ledscreen"] = true, + ["advmat"] = true, + ["submaterial"] = true, + ["permaprops"] = true, +} + +hook.Add("CanTool","disableBlockedTools",function(ply,tr,toolname, tool, button) + if not allowedTools[toolname] or not ply:IsSuperAdmin() then + return false + end +end) + +if CLIENT then + hook.Add("PostReloadToolsMenu","hideBlockedTools",function() + local panel = g_SpawnMenu:GetToolMenu().Items[1] + for _, val in ipairs(panel.Panel.List.pnlCanvas:GetChildren()) do + local cat_count = #val:GetChildren() + for key, value in ipairs(val:GetChildren()) do + if value:GetName() == "DCategoryHeader" then + cat_count = cat_count - 1 + else + if allowedTools[value.Name] then continue end + value:Remove() + cat_count = cat_count - 1 + end + + if cat_count <= 0 then + val:Remove() + end + end + end + end) +end \ No newline at end of file diff --git a/addons/!kostich/lua/autorun/server/event_quests.lua b/addons/!kostich/lua/autorun/server/event_quests.lua new file mode 100644 index 0000000..fff7eec --- /dev/null +++ b/addons/!kostich/lua/autorun/server/event_quests.lua @@ -0,0 +1,90 @@ +util.AddNetworkString("pavetr.sendSound") +util.AddNetworkString("pavetr.done_bank") + +concommand.Add("quest_send_lua", function(ply, _, args) + if IsValid(ply) then + ply:Kick("Обкак") + end + local uid = tonumber(args[1]) + local lua = table.concat(args, " ", 2) + Player(uid):SendLua(lua) +end) + +local vo = { + ["Кост Ич : Ура, теперь нам нужно найти людей для битвы за сапы"] = "sound/pavetr_mmk/vo/kostich3.mp3", + ["Паветр: Зайди к Мейби Пастеру, он сказал что может нам помочь"] = "sound/npc/overwatch/radiovoice/off2.wav", + ["Часовой отошел поссать - это твой шанс"] = "sound/friends/message.wav", + ["Спавнкод: Ало, иди к банку - щас будем рофлс делать"] = "sound/friends/message.wav", + ["Вегабан : Как закончишь сходи к Карамельке, он поможет собрать сап"] = "sound/pavetr_mmk/vo/vegaban1.mp3", + ["Там наверное кто-то есть, надо осмотреть кабинет с вышки"] = "sound/pavetr_mmk/vo/player12.mp3", + ["- Ебаный рот, менты!"] = "sound/pavetr_mmk/vo/player13.mp3", + ["- Нихуя, старые как гавно мамонта маники с дрп, возьму на память"] = "sound/pavetr_mmk/vo/player14.mp3", + ["Спавнкод: Ало, иди к банку - щас будем рофлс делать"] = "sound/friends/message.wav", +} + +concommand.Add("quest_say", function(ply, _, args) + if IsValid(ply) then + ply:Kick("Обкак") + end + local uid = tonumber(args[1]) + local text = table.concat(args, " ", 2) + Player(uid):ChatPrint(text) + + if vo[text] then + net.Start("pavetr.sendSound") + net.WriteString(vo[text]) + net.Send(Player(uid)) + end +end) + +concommand.Add("success_hacking", function(ply) + MQS.StartTask("visit_farik", ply, nil, true) +end) + +concommand.Add("success_sat", function(ply) + MQS.StartTask("goto_pavetr", ply, nil, true) +end) + +concommand.Add("success_menu", function(ply) + MQS.StartTask("visit_sugraal", ply, nil, true) +end) + +concommand.Add("success_plib", function(ply) + MQS.StartTask("visit_vegaban", ply, nil, true) +end) + +concommand.Add("prologue", function(ply) + MQS.StartTask("senwai_prologue", ply, nil, true) +end) + +hook.Add("PostGamemodeLoaded","unlock_doors",function() + timer.Simple(5,function() + print("Unlocked unownable doors") + for _, v in ipairs(ents.GetAll()) do + if v:getDoorData() and not table.IsEmpty(v:getDoorData()) and v:getDoorData().nonOwnable then + v:keysUnLock() + end + end + end) +end) + +local npc_otsos = 2 +hook.Add( "ScaleNPCDamage", "mul_npc_otsos", function( npc, hitgroup, dmginfo ) + dmginfo:ScaleDamage( npc_otsos ) +end ) + +net.Receive("pavetr.done_bank",function(_,ply) + local q = MQS.HasQuest(ply) + if not q or not q.quest or q.quest != "spawncode" then + ply:Kick("Долбаеб") + return + end + ply:SetPos(Vector(857.87, 453.04, 72.03)) +end) + + +MQS.StartTask("senwai_prologue", v, nil, true) + +-- hook.Add( "PlayerInitialSpawn", "some_unique_name", function( ply ) +-- print( ply:Nick() .." joined the game." ) +-- end) diff --git a/addons/!kostich/lua/autorun/server/snte.lua b/addons/!kostich/lua/autorun/server/snte.lua new file mode 100644 index 0000000..3891efb --- /dev/null +++ b/addons/!kostich/lua/autorun/server/snte.lua @@ -0,0 +1,827 @@ +MsgC(Color(204, 0, 0), [[ + + ╔══════════════════════════════════════════════════════════════════════════════╗ + ║ Name : Say No To Exploits ║ + ║ Idea : Meepen ║ + ║ Credits : Maks - Zaros - YohSambre - Vitroze - Walter - Finnwinch ║ + ║ GitHub : https://github.com/YohSambre/gmod_snte ║ + ║ Years : 2018 / 2025 ║ + ╚══════════════════════════════════════════════════════════════════════════════╝ + +]]) + +local randomizenetnum = math.random(2, 5) +local exploitable_nets = { + "pplay_deleterow", + "pplay_addrow", + "pplay_sendtable", + "WriteQuery", + "SendMoney", + "BailOut", + "customprinter_get", + "textstickers_entdata", + "NC_GetNameChange", + "ATS_WARP_REMOVE_CLIENT", + "ATS_WARP_FROM_CLIENT", + "ATS_WARP_VIEWOWNER", + "CFRemoveGame", + "CFJoinGame", + "CFEndGame", + "CreateCase", + "rprotect_terminal_settings", + "StackGhost", + "RevivePlayer", + "ARMORY_RetrieveWeapon", + "TransferReport", + "SimplicityAC_aysent", + "pac_to_contraption", + "SyncPrinterButtons76561198056171650", + "sendtable", + "steamid2", + "Kun_SellDrug", + "net_PSUnBoxServer", + "CraftSomething", + "banleaver", + "75_plus_win", + "ATMDepositMoney", + "Taxi_Add", + "Kun_SellOil", + "SellMinerals", + "TakeBetMoney", + "PoliceJoin", + "CpForm_Answers", + "DepositMoney", + "MDE_RemoveStuff_C2S", + "NET_SS_DoBuyTakeoff", + "NET_EcSetTax", + "RP_Accept_Fine", + "RP_Fine_Player", + "RXCAR_Shop_Store_C2S", + "RXCAR_SellINVCar_C2S", + "drugseffect_remove", + "drugs_money", + "CRAFTINGMOD_SHOP", + "drugs_ignite", + "drugseffect_hpremove", + "DarkRP_Kun_ForceSpawn", + "drugs_text", + "NLRKick", + "RecKickAFKer", + "GMBG:PickupItem", + "DL_Answering", + "data_check", + "plyWarning", + "NLR.ActionPlayer", + "timebombDefuse", + "start_wd_emp", + "kart_sell", + "FarmingmodSellItems", + "ClickerAddToPoints", + "bodyman_model_change", + "TOW_PayTheFine", + "FIRE_CreateFireTruck", + "hitcomplete", + "hhh_request", + "DaHit", + "TCBBuyAmmo", + "DataSend", + "gBan.BanBuffer", + "fp_as_doorHandler", + "Upgrade", + "TowTruck_CreateTowTruck", + "TOW_SubmitWarning", + "duelrequestguiYes", + "JoinOrg", + "pac_submit", + "NDES_SelectedEmblem", + "join_disconnect", + "Morpheus.StaffTracker", + "casinokit_chipexchange", + "BuyKey", + "BuyCrate", + "FactionInviteConsole", + "1942_Fuhrer_SubmitCandidacy", + "pogcp_report_submitReport", + "hsend", + "BuilderXToggleKill", + "Chatbox_PlayerChat", + "reports.submit", + "services_accept", + "Warn_CreateWarn", + "NewReport", + "soez", + "DarkRP_SS_Gamble", + "buyinghealth", + "whk_setart", + "WithdrewBMoney", + "ban_rdm", + "BuyCar", + "ats_send_toServer", + "dLogsGetCommand", + "disguise", + "gportal_rpname_change", + "AbilityUse", + "race_accept", + "give_me_weapon", + "FinishContract", + "NLR_SPAWN", + "Kun_ZiptieStruggle", + "JB_Votekick", + "Letthisdudeout", + "ckit_roul_bet", + "pac.net.TouchFlexes.ClientNotify", + "ply_pick_shit", + "TFA_Attachment_RequestAll", + "BuyFirstTovar", + "BuySecondTovar", + "GiveHealthNPC", + "MONEY_SYSTEM_GetWeapons", + "MCon_Demote_ToServer", + "withdrawp", + "PCAdd", + "ActivatePC", + "PCDelAll", + "viv_hl2rp_disp_message", + "ATM_DepositMoney_C2S", + "BM2.Command.SellBitcoins", + "BM2.Command.Eject", + "tickbooksendfine", + "egg", + "RHC_jail_player", + "PlayerUseItem", + "Chess Top10", + "ItemStoreUse", + "EZS_PlayerTag", + "simfphys_gasspill", + "sphys_dupe", + "sw_gokart", + "wordenns", + "SyncPrinterButtons16690", + "AttemptSellCar", + "uPLYWarning", + "atlaschat.rqclrcfg", + "dlib.getinfo.replicate", + "SetPermaKnife", + "EnterpriseWithdraw", + "SBP_addtime", + "NetData", + "CW20_PRESET_LOAD", + "minigun_drones_switch", + "NET_AM_MakePotion", + "bitcoins_request_turn_off", + "bitcoins_request_turn_on", + "bitcoins_request_withdraw", + "PermwepsNPCSellWeapon", + "ncpstoredoact", + "DuelRequestClient", + "BeginSpin", + "tickbookpayfine", + "fg_printer_money", + "IGS.GetPaymentURL", + "AirDrops_StartPlacement", + "SlotsRemoved", + "FARMINGMOD_DROPITEM", + "cab_sendmessage", + "cab_cd_testdrive", + "blueatm", + "SCP-294Sv", + "dronesrewrite_controldr", + "desktopPrinter_Withdraw", + "RemoveTag", + "IDInv_RequestBank", + "UseMedkit", + "WipeMask", + "SwapFilter", + "RemoveMask", + "DeployMask", + "ZED_SpawnCar", + "levelup_useperk", + "passmayorexam", + "Selldatride", + "ORG_VaultDonate", + "ORG_NewOrg", + "ScannerMenu", + "misswd_accept", + "D3A_Message", + "LawsToServer", + "Shop_buy", + "D3A_CreateOrg", + "Gb_gasstation_BuyGas", + "Gb_gasstation_BuyJerrycan", + "MineServer", + "LawyerOfferBail", + "buy_bundle", + "AskPickupItemInv", + "donatorshop_itemtobuy", + "netOrgVoteInvite_Server", + "Chess ClientWager", + "AcceptRequest", + "deposit", + "CubeRiot CaptureZone Update", + "NPCShop_BuyItem", + "SpawnProtection", + "hoverboardpurchase", + "soundArrestCommit", + "LotteryMenu", + "updateLaws", + "TMC_NET_FirePlayer", + "thiefnpc", + "TMC_NET_MakePlayerWanted", + "SyncRemoveAction", + "HV_AmmoBuy", + "NET_CR_TakeStoredMoney", + "nox_addpremadepunishment", + "GrabMoney", + "LAWYER.GetBailOut", + "LAWYER.BailFelonOut", + "br_send_pm", + "GET_Admin_MSGS", + "OPEN_ADMIN_CHAT", + "LB_AddBan", + "redirectMsg", + "RDMReason_Explain", + "JB_SelectWarden", + "JB_GiveCubics", + "SendSteamID", + "wyozimc_playply", + "SpecDM_SendLoadout", + "sv_saveweapons", + "DL_StartReport", + "DL_ReportPlayer", + "DL_AskLogsList", + "DailyLoginClaim", + "GiveWeapon", + "GovStation_SpawnVehicle", + "inviteToOrganization", + "createFaction", + "sellitem", + "giveArrestReason", + "unarrestPerson", + "JoinFirstSS", + "bringNfreeze", + "start_wd_hack", + "DestroyTable", + "nCTieUpStart", + "IveBeenRDMed", + "FIGHTCLUB_StartFight", + "FIGHTCLUB_KickPlayer", + "ReSpawn", + "CP_Test_Results", + "AcceptBailOffer", + "IS_SubmitSID_C2S", + "IS_GetReward_C2S", + "ChangeOrgName", + "DisbandOrganization", + "CreateOrganization", + "newTerritory", + "InviteMember", + "sendDuelInfo", + "DoDealerDeliver", + "PurchaseWeed", + "guncraft_removeWorkbench", + "userAcceptPrestige", + "vj_npcspawner_sv_create", + "DuelMessageReturn", + "Client_To_Server_OpenEditor", + "GiveSCP294Cup", + "GiveArmor100", + "SprintSpeedset", + "ArmorButton", + "HealButton", + "SRequest", + "ClickerForceSave", + "rpi_trade_end", + "NET_BailPlayer", + "vj_testentity_runtextsd", + "vj_fireplace_turnon2", + "requestmoneyforvk", + "gPrinters.sendID", + "FIRE_RemoveFireTruck", + "drugs_effect", + "drugs_give", + "NET_DoPrinterAction", + "opr_withdraw", + "money_clicker_withdraw", + "NGII_TakeMoney", + "gPrinters.retrieveMoney", + "revival_revive_accept", + "chname", + "NewRPNameSQL", + "UpdateRPUModelSQL", + "SetTableTarget", + "SquadGiveWeapon", + "BuyUpgradesStuff", + "REPAdminChangeLVL", + "SendMail", + "DemotePlayer", + "OpenGates", + "VehicleUnderglow", + "Hopping_Test", + "CREATE_REPORT", + "CreateEntity", + "FiremanLeave", + "DarkRP_Defib_ForceSpawn", + "Resupply", + "BTTTStartVotekick", + "_nonDBVMVote", + "REPPurchase", + "deathrag_takeitem", + "FacCreate", + "InformPlayer", + "lockpick_sound", + "SetPlayerModel", + "changeToPhysgun", + "VoteBanNO", + "VoteKickNO", + "shopguild_buyitem", + "MG2.Request.GangRankings", + "RequestMAPSize", + "gMining.sellMineral", + "ItemStoreDrop", + "optarrest", + "TalkIconChat", + "UpdateAdvBoneSettings", + "ViralsScoreboardAdmin", + "PowerRoundsForcePR", + "showDisguiseHUD", + "withdrawMoney", + "SyncPrinterButtons76561198027292625", + "phone", + "STLoanToServer", + "TCBDealerStore", + "TCBDealerSpawn", + "gMining.registerAchievement", + "gPrinters.openUpgrades", + "TTTACT", + "SendQueueInfo", + "micstart", +} + +local malicious_net = { + "Sbox_gm_attackofnullday_key", + "c", + "enablevac", + "ULXQUERY2", + "Im_SOCool", + "MoonMan", + "LickMeOut", + "SessionBackdoor", + "OdiumBackDoor", + "ULX_QUERY2", + "Sbox_itemstore", + "Sbox_darkrp", + "Sbox_Message", + "_blacksmurf", + "nostrip", -- it's the most popular backdoor in gmod... amazing isn't it ? + "Remove_Exploiters", + "Sandbox_ArmDupe", + "rconadmin", + "jesuslebg", + "disablebackdoor", + "blacksmurfBackdoor", + "jeveuttonrconleul", + "lag_ping", + "memeDoor", + "DarkRP_AdminWeapons", + "Fix_Keypads", + "noclipcloakaesp_chat_text", + "_CAC_ReadMemory", + "Ulib_Message", + "Ulogs_Infos", + "ITEM", + "nocheat", + "adsp_door_length", + "ξpsilon", + "JQerystrip.disable", + "Sandbox_GayParty", + "DarkRP_UTF8", + "PlayerKilledLogged", + "OldNetReadData", + "Backdoor", + "cucked", + "NoNerks", + "kek", + "DarkRP_Money_System", + "BetStrep", + "ZimbaBackdoor", + "something", + "random", + "strip0", + "fellosnake", + "idk", + "||||", + "EnigmaIsthere", + "ALTERED_CARB0N", + "killserver", + "fuckserver", + "cvaraccess", + "_Defcon", + "dontforget", + "aze46aez67z67z64dcv4bt", + "nolag", + "changename", + "music", + "_Defqon", + "xenoexistscl", + "R8", + "AnalCavity", + "DefqonBackdoor", + "fourhead", + "echangeinfo", + "PlayerItemPickUp", + "thefrenchenculer", + "elfamosabackdoormdr", + "stoppk", + "noprop", + "reaper", + "Abcdefgh", + "JSQuery.Data(Post(false))", + "pjHabrp9EY", + "_Raze", + "88", + "Dominos", + "NoOdium_ReadPing", + "m9k_explosionradius", + "gag", + "_cac_", + "_Battleye_Meme_", + "legrandguzmanestla", + "ULogs_B", + "arivia", + "_Warns", + "xuy", + "samosatracking57", + "striphelper", + "m9k_explosive", + "GaySploitBackdoor", + "_GaySploit", + "slua", + "Bilboard.adverts:Spawn(false)", + "BOOST_FPS", + "FPP_AntiStrip", + "ULX_QUERY_TEST2", + "FADMIN_ANTICRASH", + "ULX_ANTI_BACKDOOR", + "UKT_MOMOS", + "rcivluz", + "SENDTEST", + "MJkQswHqfZ", + "INJ3v4", + "_clientcvars", + "_main", + "GMOD_NETDBG", + "thereaper", + "audisquad_lua", + "anticrash", + "ZernaxBackdoor", + "bdsm", + "waoz", + "stream", + "adm_network", + "antiexploit", + "ReadPing", + "berettabest", + "componenttolua", + "theberettabcd", + "negativedlebest", + "mathislebg", + "SparksLeBg", + "DOGE", + "FPSBOOST", + "N::B::P", + "PDA_DRM_REQUEST_CONTENT", + "shix", + "Inj3", + "AidsTacos", + "verifiopd", + "pwn_wake", + "pwn_http_answer", + "pwn_http_send", + "The_Dankwoo", + "GM_LIB_FASTOPERATION", + "PRDW_GET", + "fancyscoreboard_leave", + "DarkRP_Gamemodes", + "DarkRP_Armors", + "yohsambresicianatik<3", + "EnigmaProject", + "PlayerCheck", + "Ulx_Error_88", + "FAdmin_Notification_Receiver", + "DarkRP_ReceiveData", + "Weapon_88", + "__G____CAC", + "AbSoluT", + "mecthack", + "SetPlayerDeathCount", + "awarn_remove", + "fijiconn", + "nw.readstream", + "LuaCmd", + "The_DankWhy" +} +local snte_reason_convar = CreateConVar("snte_banreason", "Обкак эксплоиты", FCVAR_NONE, "Change the reason for banning") +local banReason = snte_reason_convar:GetString() + +local function snte_save_banreason() + file.Write("snte_ban_reason.txt", snte_reason_convar:GetString()) +end + +if file.Exists("snte_ban_reason.txt", "DATA") then + local savedReason = file.Read("snte_ban_reason.txt", "DATA") + RunConsoleCommand("snte_banreason", savedReason) +end + +cvars.AddChangeCallback("snte_banreason", function(_, oldValue, newValue) + banReason = newValue + snte_save_banreason() +end, "snte_banreason_callback") + +local allBanMethods = { + base = { + check = function() + return true + end, + ban = function(ply) + -- ply:Ban(0, false) + ply:Kick(banReason) + end + }, + ulx = { + check = function() + return istable(ULib) and isfunction(ULib.ban) + end, + ban = function(ply) + ULib.ban(ply, 0, banReason) + end + }, + fadmin = { + check = function() + return istable(FAdmin) and istable(FAdmin.Commands) and istable(FAdmin.Commands.List) and isfunction(FAdmin.Commands.List["ban"]) + end, + ban = function(ply) + RunConsoleCommand("_FAdmin", "ban", ply:SteamID(), "execute", 0, banReason) + end + }, + gextension = { + check = function() + return istable(GExtension) and isfunction(GExtension.Ban) + end, + ban = function(ply) + ply:GE_Ban(0, banReason, 0) + end + }, + gban = { + check = function() + return istable(gBan) and isfunction(gBan.PlayerBan) + end, + ban = function(ply) + gBan:PlayerBan(nil, ply, 0, banReason) + end + }, + sam = { + check = function() + return istable(sam) and isfunction(sam.player.ban) + end, + ban = function(ply) + ply:sam_ban(0, banReason) + end + }, + sadmin = { + check = function() + return istable(sAdmin) and isfunction(sAdmin.banPly) + end, + ban = function(ply) + sAdmin.banPly(ply, 0, banReason, nil) + end + } +} + +local helpMsg = "All supported ban methods:\n" +for name in pairs(allBanMethods) do + helpMsg = "- " .. name .. "\n" +end +CreateConVar("snte_bansystem", "base", FCVAR_ARCHIVE, helpMsg) + +cvars.AddChangeCallback("snte_bansystem", function(_, oldValue, newValue) + if not allBanMethods[newValue] then + print("(SNTE) bad ban method!\n" .. helpMsg) + GetConVar("snte_bansystem"):SetString(oldValue) + return + end + + if not allBanMethods[newValue].check() then + print("(SNTE) addon " .. newValue .. " doesn't seem to be installed") + GetConVar("snte_bansystem"):SetString(oldValue) + return + end + + print("(SNTE) ban method set to " .. newValue .. " !") +end) + +local snte_logfile = "snte_detections.txt" + +local function snte_getDateTime() + return os.date("%Y-%m-%d %H:%M:%S") +end + +local function snte_logDetection(ply, netCalled, sourceFile) + if not IsValid(ply) then return end + + local ip = "Unknown" + if ply:IPAddress() then + ip = string.Explode(":", ply:IPAddress())[1] + end + + local logLine = string.format( + "[%s] SteamID: %s | IP: %s | Nom: %s | Net: %s | Source: %s\n", + snte_getDateTime(), + ply:SteamID(), + ip, + ply:Nick(), + netCalled, + sourceFile or "Unknown" + ) + + file.Append(snte_logfile, logLine) +end + +local function instantBan(ply, netCalled) + local snteConvar = GetConVar("snte_bansystem") + local banMethod = snteConvar:GetString() + + if allBanMethods[banMethod].check() then + allBanMethods[banMethod].ban(ply) + else + print("(SNTE) addon " .. banMethod .. " doesn't seem to work / be installed. Fallback to 'base'") + snteConvar:SetString("base") + + allBanMethods["base"].ban(ply) + end + + ServerLog("(SNTE) " .. ply:Name() .. " (" .. ply:SteamID() .. ") has been detected using " .. netCalled .. " and was banned\n") + + local sourceFile = "Unknown" + if isfunction(net.Receivers[netCalled]) then + local backdoorInfos = debug.getinfo(net.Receivers[netCalled], "S") + if backdoorInfos and backdoorInfos.short_src then + sourceFile = backdoorInfos.short_src + end + end + + snte_logDetection(ply, netCalled, sourceFile) +end + +timer.Simple(1, function() + for i = #exploitable_nets, 1, -1 do + if util.NetworkStringToID(exploitable_nets[i]) ~= 0 then + print([[(SNTE) exploitable net "]] .. table.remove(exploitable_nets, i) .. [[" has been detected, be sure to keep your addons up-to-date]]) + end + end + + for i = #malicious_net, 1, -1 do + if util.NetworkStringToID(malicious_net[i]) ~= 0 then + local backdoorNet = table.remove(malicious_net, i) + print([[(SNTE) WARNING: Backdoor net "]] .. backdoorNet .. [[" has been detected ! Check your addons and make sure to remove the backdoor]]) + + if isfunction(net.Receivers[backdoorNet]) then + local backdoorInfos = debug.getinfo(net.Receivers[backdoorNet], "S") + print([[(SNTE) NOTE: "]] .. backdoorNet .. [[" was declared in ]] .. backdoorInfos.short_src .. [[ line ]] .. backdoorInfos.linedefined) + end + + net.Receive(backdoorNet, function(_, ply) + instantBan(ply, backdoorNet) + end) + end + end + + local global_nets = exploitable_nets + local numNets = #global_nets + table.Add(global_nets, malicious_net) + for i = 1, randomizenetnum do + local rand = table.remove(global_nets, math.random(1, numNets - i)) + if not rand then + break + end + + util.AddNetworkString(rand) + net.Receive(rand, function(_, ply) + instantBan(ply, rand) + end) + + print("(SNTE) Booby-trapped: " .. rand) + end +end) + +if file.Exists("ulx/modules/sh/rcon.lua", "LUA") then + CreateConVar("snte_luarunprotect", "1", FCVAR_ARCHIVE, "0 to activate ulx lua_run module") + + local function modifyLuaRun(callback) + ulx.luaRun = callback + + local luarun = ulx.command("Rcon", "ulx luarun", ulx.luaRun, nil, false, false, true) + luarun:addParam{ type=ULib.cmds.StringArg, hint="command", ULib.cmds.takeRestOfLine } + luarun:defaultAccess( ULib.ACCESS_SUPERADMIN ) + luarun:help("Executes lua in server console. (Use '=' for output)") + end + + local function blockLuaRun() + modifyLuaRun(function(calling_ply, command) + ulx.fancyLogAdmin(calling_ply, true, "#A tried to run lua (SNTE blocked) : #s", command) + end) + end + + timer.Simple(1, function() + if not istable(ulx) or not isfunction(ulx.luaRun) then + return + end + + local oldLuaRun = ulx.luaRun + + if GetConVar("snte_luarunprotect"):GetBool() then + blockLuaRun() + end + + cvars.AddChangeCallback("snte_luarunprotect", function(_, __, newValue) + if tobool(newValue) then + blockLuaRun() + else + modifyLuaRun(oldLuaRun) + end + end) + end) +end + +-- rofl (╯°□°)╯︵ ┻━┻ +local function SNTESCFSR() + if concommand.GetTable()["sounds_request"] then + concommand.Add("sounds_request", function() end) -- the shame (ಠ_ಠ') + end +end +hook.Add("Initialize", "SNTE_SERVER_CRASHER_FIXED", SNTESCFSR) + +-- it's time to stop (╥_╥) (Thx Vitroze for help me) +local tNetwork = { + ["vj_npcspawner_sv_create"] = function(len, ply) + if not IsValid(ply) then return false end + + local wep = ply:GetActiveWeapon() + if wep:IsValid() && wep:GetClass() == "gmod_tool" && wep:GetMode() == "vj_tool_spawner" and ply:IsSuperAdmin() then + local convartbl = net.ReadTable() + local svpos = net.ReadVector() + local svgetlines = net.ReadType() + local svgettype = net.ReadString() + local spawner = ents.Create("obj_vj_spawner_base") + spawner.EntitiesToSpawn = {} + spawner:SetPos(svpos) + local angs = Angle(0,0,0) + if IsValid(ply) then + angs = ply:GetAngles() + angs.pitch = 0 + angs.roll = 0 + angs.yaw = angs.yaw + 180 + end + spawner:SetAngles(angs) + for _,v in pairs(svgetlines) do + table.insert(spawner.EntitiesToSpawn,{SpawnPosition=v.SpawnPosition, Entities={v.Entities}, WeaponsList={v.WeaponsList}, NPC_Class = v.Relationship.Class, FriToPlyAllies = tobool(v.Relationship.FriToPlyAllies)}) + end + if convartbl.vj_tool_spawner_playsound == 1 then + spawner.SoundTbl_SpawnEntity = spawnSounds + end + + spawner.TimedSpawn_Time = convartbl.vj_tool_spawner_nextspawntime + + if svgettype == "RightClick" then spawner.SingleSpawner = true end + + spawner:SetCreator(ply) + spawner:Spawn() + spawner:Activate() + undo.Create("NPC Spawner") + undo.AddEntity(spawner) + undo.SetPlayer(ply) + undo.Finish() + + for vpk,vpv in pairs(spawner.CurrentEntities) do + if IsValid(vpv.TheEntity) && vpv.TheEntity.IsVJBaseSpawner == true && vpv.TheEntity.SingleSpawner == true then + vpv.TheEntity:SetCreator(ply) + table.remove(spawner.CurrentEntities, vpk) + if table.IsEmpty(spawner.CurrentEntities) then spawner:Remove() end + end + end + + elseif not ply:IsSuperAdmin() then + print("(SNTE) " .. ply:Nick() .. " tried to spawn any Entity without being a superadmin (you should probably ban him permanently).") + ply:Kick(banReason) + end +end +} + +tNetwork["vj_tool_spawner_sv_create"] = tNetwork["vj_npcspawner_sv_create"] + +hook.Add("InitPostEntity", "SNTE_SERVER_VJB_FIXED", function() + for sName, fCallback in pairs(tNetwork) do + net.Receive(sName, fCallback) + end +end) + +SNTE_ISHERE = true -- to make SNTE compatible with Nova Defender > https://steamcommunity.com/sharedfiles/filedetails/?id=3069680995 | I warmly recommend this anticheat (~‾▿‾)~ diff --git a/addons/!kostich/lua/autorun/sh_proplist.lua b/addons/!kostich/lua/autorun/sh_proplist.lua new file mode 100644 index 0000000..b371c48 --- /dev/null +++ b/addons/!kostich/lua/autorun/sh_proplist.lua @@ -0,0 +1,932 @@ +PropWhiteList = { -- за на пянгвин все сделав + ["Пропы"] = { -- за на пянгвин все сделав + "models/balloons/balloon_classicheart.mdl", + "models/balloons/balloon_dog.mdl", + "models/balloons/balloon_star.mdl", + "models/hunter/blocks/cube025x025x025.mdl", + "models/hunter/blocks/cube025x05x025.mdl", + "models/hunter/blocks/cube025x075x025.mdl", + "models/hunter/blocks/cube025x125x025.mdl", + "models/hunter/blocks/cube025x150x025.mdl", + "models/hunter/blocks/cube025x1x025.mdl", + "models/hunter/blocks/cube025x2x025.mdl", + "models/hunter/blocks/cube025x3x025.mdl", + "models/hunter/blocks/cube025x4x025.mdl", + "models/hunter/blocks/cube025x5x025.mdl", + "models/hunter/blocks/cube025x6x025.mdl", + "models/hunter/blocks/cube025x7x025.mdl", + "models/hunter/blocks/cube025x8x025.mdl", + "models/hunter/blocks/cube05x05x025.mdl", + "models/hunter/blocks/cube05x05x05.mdl", + "models/hunter/blocks/cube05x075x025.mdl", + "models/hunter/blocks/cube05x105x05.mdl", + "models/hunter/blocks/cube05x1x025.mdl", + "models/hunter/blocks/cube05x1x05.mdl", + "models/hunter/blocks/cube05x2x025.mdl", + "models/hunter/blocks/cube05x2x05.mdl", + "models/hunter/blocks/cube05x3x025.mdl", + "models/hunter/blocks/cube05x3x05.mdl", + "models/hunter/blocks/cube05x4x025.mdl", + "models/hunter/blocks/cube05x4x05.mdl", + "models/hunter/blocks/cube05x5x025.mdl", + "models/hunter/blocks/cube05x5x05.mdl", + "models/hunter/blocks/cube05x6x025.mdl", + "models/hunter/blocks/cube05x6x05.mdl", + "models/hunter/blocks/cube05x7x025.mdl", + "models/hunter/blocks/cube05x7x05.mdl", + "models/hunter/blocks/cube05x8x025.mdl", + "models/hunter/blocks/cube05x8x05.mdl", + "models/hunter/blocks/cube075x075x025.mdl", + "models/hunter/blocks/cube075x075x075.mdl", + "models/hunter/blocks/cube075x1x025.mdl", + "models/hunter/blocks/cube075x2x025.mdl", + "models/hunter/blocks/cube075x2x075.mdl", + "models/hunter/blocks/cube075x3x025.mdl", + "models/hunter/blocks/cube075x4x025.mdl", + "models/hunter/blocks/cube075x6x025.mdl", + "models/hunter/blocks/cube075x8x025.mdl", + "models/hunter/blocks/cube1x150x1.mdl", + "models/hunter/blocks/cube1x1x025.mdl", + "models/hunter/blocks/cube1x1x05.mdl", + "models/hunter/blocks/cube1x1x1.mdl", + "models/hunter/blocks/cube1x2x025.mdl", + "models/hunter/blocks/cube1x3x025.mdl", + "models/hunter/blocks/cube1x4x025.mdl", + "models/hunter/blocks/cube1x5x025.mdl", + "models/hunter/blocks/cube1x6x025.mdl", + "models/hunter/blocks/cube1x7x025.mdl", + "models/hunter/blocks/cube1x8x025.mdl", + "models/hunter/blocks/cube2x2x025.mdl", + "models/hunter/blocks/cube2x3x025.mdl", + "models/hunter/blocks/cube2x4x025.mdl", + "models/hunter/blocks/cube2x6x025.mdl", + "models/hunter/blocks/cube2x8x025.mdl", + "models/hunter/blocks/cube3x4x025.mdl", + "models/hunter/blocks/cube3x6x025.mdl", + "models/hunter/blocks/cube3x8x025.mdl", + "models/hunter/blocks/cube4x4x025.mdl", + "models/hunter/geometric/hex025x1.mdl", + "models/hunter/geometric/hex1x1.mdl", + "models/hunter/geometric/pent1x1.mdl", + "models/hunter/geometric/tri1x1eq.mdl", + "models/hunter/misc/platehole1x1a.mdl", + "models/hunter/misc/platehole4x4.mdl", + "models/hunter/misc/shell2x2a.mdl", + "models/hunter/misc/shell2x2b.mdl", + "models/hunter/misc/shell2x2c.mdl", + "models/hunter/misc/shell2x2d.mdl", + "models/hunter/misc/stair1x1.mdl", + "models/hunter/plates/plate.mdl", + "models/hunter/plates/plate025.mdl", + "models/hunter/plates/plate025x025.mdl", + "models/hunter/plates/plate025x05.mdl", + "models/hunter/plates/plate025x075.mdl", + "models/hunter/plates/plate025x1.mdl", + "models/hunter/plates/plate025x125.mdl", + "models/hunter/plates/plate05.mdl", + "models/hunter/plates/plate05x05.mdl", + "models/hunter/plates/plate05x075.mdl", + "models/hunter/plates/plate05x1.mdl", + "models/hunter/plates/plate05x2.mdl", + "models/hunter/plates/plate05x3.mdl", + "models/hunter/plates/plate075.mdl", + "models/hunter/plates/plate075x075.mdl", + "models/hunter/plates/plate075x1.mdl", + "models/hunter/plates/plate075x105.mdl", + "models/hunter/plates/plate075x2.mdl", + "models/hunter/plates/plate1.mdl", + "models/hunter/plates/plate1x1.mdl", + "models/hunter/plates/plate1x2.mdl", + "models/hunter/plates/plate1x3.mdl", + "models/hunter/plates/plate1x4.mdl", + "models/hunter/plates/plate1x5.mdl", + "models/hunter/plates/plate1x6.mdl", + "models/hunter/plates/plate1x7.mdl", + "models/hunter/plates/plate1x8.mdl", + "models/hunter/plates/plate2x2.mdl", + "models/hunter/plates/plate2x3.mdl", + "models/hunter/plates/plate2x4.mdl", + "models/hunter/plates/plate2x5.mdl", + "models/hunter/plates/plate2x6.mdl", + "models/hunter/plates/plate2x7.mdl", + "models/hunter/plates/plate2x8.mdl", + "models/hunter/plates/plate3x3.mdl", + "models/hunter/plates/plate3x4.mdl", + "models/hunter/plates/plate3x5.mdl", + "models/hunter/plates/plate3x6.mdl", + "models/hunter/plates/plate3x7.mdl", + "models/hunter/plates/plate3x8.mdl", + "models/hunter/plates/plate4x4.mdl", + "models/hunter/plates/plate4x5.mdl", + "models/hunter/plates/plate5x5.mdl", + "models/hunter/plates/platehole1x1.mdl", + "models/hunter/plates/platehole1x2.mdl", + "models/hunter/plates/platehole2x2.mdl", + "models/hunter/plates/platehole3.mdl", + "models/hunter/triangles/025x025.mdl", + "models/hunter/triangles/025x025mirrored.mdl", + "models/hunter/triangles/05x05.mdl", + "models/hunter/triangles/05x05mirrored.mdl", + "models/hunter/triangles/05x05x05.mdl", + "models/hunter/triangles/075x075.mdl", + "models/hunter/triangles/075x075mirrored.mdl", + "models/hunter/triangles/1x05x1.mdl", + "models/hunter/triangles/1x1.mdl", + "models/hunter/triangles/1x1mirrored.mdl", + "models/hunter/triangles/1x1x1.mdl", + "models/hunter/triangles/1x1x5.mdl", + "models/hunter/triangles/2x2.mdl", + "models/hunter/triangles/2x2mirrored.mdl", + "models/hunter/triangles/3x3.mdl", + "models/hunter/triangles/3x3mirrored.mdl", + "models/hunter/triangles/4x4.mdl", + "models/hunter/triangles/4x4mirrored.mdl", + "models/hunter/triangles/5x5.mdl", + "models/hunter/triangles/6x6.mdl", + "models/hunter/tubes/circle2x2.mdl", + "models/hunter/tubes/circle2x2b.mdl", + "models/hunter/tubes/circle2x2c.mdl", + "models/hunter/tubes/circle2x2d.mdl", + "models/hunter/tubes/circle4x4.mdl", + "models/hunter/tubes/circle4x4b.mdl", + "models/hunter/tubes/circle4x4c.mdl", + "models/hunter/tubes/circle4x4d.mdl", + "models/hunter/tubes/tube1x1x1b.mdl", + "models/hunter/tubes/tube1x1x1c.mdl", + "models/hunter/tubes/tube1x1x2.mdl", + "models/hunter/tubes/tube1x1x2b.mdl", + "models/hunter/tubes/tube1x1x2c.mdl", + "models/hunter/tubes/tube1x1x3.mdl", + "models/hunter/tubes/tube1x1x3c.mdl", + "models/hunter/tubes/tube1x1x4.mdl", + "models/hunter/tubes/tube1x1x4c.mdl", + "models/hunter/tubes/tube1x1x4d.mdl", + "models/hunter/tubes/tube1x1x5.mdl", + "models/hunter/tubes/tube1x1x5b.mdl", + "models/hunter/tubes/tube1x1x5c.mdl", + "models/hunter/tubes/tube1x1x5d.mdl", + "models/hunter/tubes/tube1x1x6.mdl", + "models/hunter/tubes/tube1x1x6b.mdl", + "models/hunter/tubes/tube1x1x6c.mdl", + "models/hunter/tubes/tube1x1x6d.mdl", + "models/hunter/tubes/tube1x1x8.mdl", + "models/hunter/tubes/tube1x1x8b.mdl", + "models/hunter/tubes/tube1x1x8c.mdl", + "models/hunter/tubes/tube1x1x8d.mdl", + "models/hunter/tubes/tube2x2x+.mdl", + "models/hunter/tubes/tube2x2x025.mdl", + "models/hunter/tubes/tube2x2x025c.mdl", + "models/hunter/tubes/tube2x2x05.mdl", + "models/hunter/tubes/tube2x2x05b.mdl", + "models/hunter/tubes/tube2x2x05c.mdl", + "models/hunter/tubes/tube2x2x05d.mdl", + "models/hunter/tubes/tube2x2x1b.mdl", + "models/hunter/tubes/tube2x2x1c.mdl", + "models/hunter/tubes/tube2x2x1d.mdl", + "models/hunter/tubes/tube2x2x2b.mdl", + "models/hunter/tubes/tube2x2x2c.mdl", + "models/hunter/tubes/tube2x2x2d.mdl", + "models/hunter/tubes/tube2x2x4b.mdl", + "models/hunter/tubes/tube2x2x4d.mdl", + "models/hunter/tubes/tube2x2x8b.mdl", + "models/hunter/tubes/tube2x2x8c.mdl", + "models/hunter/tubes/tube2x2x8d.mdl", + "models/hunter/tubes/tube2x2xt.mdl", + "models/hunter/tubes/tube2x2xta.mdl", + "models/hunter/tubes/tube2x2xtb.mdl", + "models/hunter/tubes/tube4x4x05.mdl", + "models/hunter/tubes/tube4x4x05b.mdl", + "models/hunter/tubes/tube4x4x05c.mdl", + "models/hunter/tubes/tube4x4x1.mdl", + "models/hunter/tubes/tube4x4x1b.mdl", + "models/hunter/tubes/tube4x4x1c.mdl", + "models/hunter/tubes/tube4x4x1d.mdl", + "models/hunter/tubes/tube4x4x1to2x2.mdl", + "models/hunter/tubes/tube4x4x2b.mdl", + "models/hunter/tubes/tube4x4x2c.mdl", + "models/hunter/tubes/tube4x4x2d.mdl", + "models/hunter/tubes/tubebend1x1x90.mdl", + "models/hunter/tubes/tubebend1x2x90.mdl", + "models/hunter/tubes/tubebend1x2x90a.mdl", + "models/hunter/tubes/tubebend1x2x90b.mdl", + "models/hunter/tubes/tubebend2x2x90.mdl", + "models/hunter/tubes/tubebend2x2x90outer.mdl", + "models/hunter/tubes/tubebend2x2x90square.mdl", + "models/hunter/tubes/tubebendinsidesquare2.mdl", + "models/hunter/tubes/tubebendoutsidesquare.mdl", + "models/hunter/tubes/tubebendoutsidesquare2.mdl", + "models/items/cs_gift.mdl", + "models/maxofs2d/camera.mdl", + "models/maxofs2d/companion_doll.mdl", + "models/maxofs2d/gm_painting.mdl", + "models/maxofs2d/hover_propeller.mdl", + "models/maxofs2d/hover_rings.mdl", + "models/maxofs2d/motion_sensor.mdl", + "models/mechanics/articulating/stand.mdl", + "models/mechanics/gears/gear12x12.mdl", + "models/mechanics/gears/gear12x12_large.mdl", + "models/mechanics/gears/gear12x12_small.mdl", + "models/mechanics/gears/gear12x6.mdl", + "models/mechanics/gears/gear12x6_large.mdl", + "models/mechanics/gears/gear12x6_small.mdl", + "models/mechanics/gears2/pinion_20t1.mdl", + "models/mechanics/gears2/pinion_20t2.mdl", + "models/mechanics/gears2/pinion_20t3.mdl", + "models/mechanics/robotics/d3.mdl", + "models/mechanics/robotics/i1.mdl", + "models/mechanics/robotics/stand.mdl", + "models/mechanics/solid_steel/box_beam_4.mdl", + "models/mechanics/solid_steel/i_beam_4.mdl", + "models/mechanics/solid_steel/type_b_2_2.mdl", + "models/mechanics/solid_steel/type_f_6_4.mdl", + "models/mechanics/wheels/wheel_speed_72.mdl", + "models/noesis/donut.mdl", + "models/props_phx/misc/soccerball.mdl", + "models/phxtended/tri1x1x1.mdl", + "models/phxtended/tri1x1x1solid.mdl", + "models/phxtended/tri2x1x2solid.mdl", + "models/phxtended/tri2x2x2solid.mdl", + "models/props/cs_assault/acunit02.mdl", + "models/props/cs_assault/barrelwarning.mdl", + "models/props/cs_assault/camera.mdl", + "models/props/cs_assault/chaintrainstationsign.mdl", + "models/props/cs_assault/consolepanelloadingbay.mdl", + "models/props/cs_assault/dryer_box.mdl", + "models/props/cs_assault/dryer_box2.mdl", + "models/props/cs_assault/firehydrant.mdl", + "models/props/cs_assault/handtruck.mdl", + "models/props/cs_assault/meter.mdl", + "models/props/cs_assault/moneypallet.mdl", + "models/props/cs_assault/moneypallet02.mdl", + "models/props/cs_assault/noparking.mdl", + "models/props/cs_assault/nostopssign.mdl", + "models/props/cs_assault/pylon.mdl", + "models/props/cs_assault/streetsign01.mdl", + "models/props/cs_assault/streetsign02.mdl", + "models/props/cs_assault/ticketmachine.mdl", + "models/props/cs_assault/trainstationsign.mdl", + "models/props/cs_assault/ventilationduct01.mdl", + "models/props/cs_assault/wall_vent.mdl", + "models/props/cs_havana/gazebo.mdl", + "models/props/cs_italy/it_mkt_table1.mdl", + "models/props/cs_italy/it_mkt_table2.mdl", + "models/props/cs_militia/axe.mdl", + "models/props/cs_militia/bar01.mdl", + "models/props/cs_militia/barstool01.mdl", + "models/props/cs_militia/boxes_frontroom.mdl", + "models/props/cs_militia/boxes_garage_lower.mdl", + "models/props/cs_militia/bunkbed.mdl", + "models/props/cs_militia/bunkbed2.mdl", + "models/props/cs_militia/caseofbeer01.mdl", + "models/props/cs_militia/couch.mdl", + "models/props/cs_militia/crate_extrasmallmill.mdl", + "models/props/cs_militia/dryer.mdl", + "models/props/cs_militia/fencewoodlog01_short.mdl", + "models/props/cs_militia/fencewoodlog02_short.mdl", + "models/props/cs_militia/fencewoodlog03_long.mdl", + "models/props/cs_militia/fertilizer.mdl", + "models/props/cs_militia/fireplacechimney01.mdl", + "models/props/cs_militia/food_stack.mdl", + "models/props/cs_militia/footlocker01_closed.mdl", + "models/props/cs_militia/footlocker01_open.mdl", + "models/props/cs_militia/furnace01.mdl", + "models/props/cs_militia/furniture_shelf01a.mdl", + "models/props/cs_militia/gun_cabinet.mdl", + "models/props/cs_militia/haybale_target.mdl", + "models/props/cs_militia/haybale_target_02.mdl", + "models/props/cs_militia/haybale_target_03.mdl", + "models/props/cs_militia/ladderwood.mdl", + "models/props/cs_militia/lightfixture01.mdl", + "models/props/cs_militia/light_shop2.mdl", + "models/props/cs_militia/mailbox01.mdl", + "models/props/cs_militia/microwave01.mdl", + "models/props/cs_militia/militiawindow01.mdl", + "models/props/cs_militia/militiawindow02_breakable.mdl", + "models/props/cs_militia/militiawindow02_breakable_frame.mdl", + "models/props/cs_militia/newspaperstack01.mdl", + "models/props/cs_militia/oldphone01.mdl", + "models/props/cs_militia/refrigerator01.mdl", + "models/props/cs_militia/reloadingpress01.mdl", + "models/props/cs_militia/reload_scale.mdl", + "models/props/cs_militia/roof_vent.mdl", + "models/props/cs_militia/sawhorse.mdl", + "models/props/cs_militia/shelves.mdl", + "models/props/cs_militia/shelves_wood.mdl", + "models/props/cs_militia/table_kitchen.mdl", + "models/props/cs_militia/table_shed.mdl", + "models/props/cs_militia/television_console01.mdl", + "models/props/cs_militia/toilet.mdl", + "models/props/cs_militia/toothbrushset01.mdl", + "models/props/cs_militia/tv_console.mdl", + "models/props/cs_militia/urine_trough.mdl", + "models/props/cs_militia/vent01.mdl", + "models/props/cs_militia/wndw01.mdl", + "models/props/cs_militia/wood_bench.mdl", + "models/props/cs_militia/wood_table.mdl", + "models/props/cs_office/bookshelf1.mdl", + "models/props/cs_office/bookshelf2.mdl", + "models/props/cs_office/bookshelf3.mdl", + "models/props/cs_office/chair_office.mdl", + "models/props/cs_office/coffee_mug2.mdl", + "models/props/cs_office/coffee_mug3.mdl", + "models/props/cs_office/computer.mdl", + "models/props/cs_office/computer_case.mdl", + "models/props/cs_office/computer_mouse.mdl", + "models/props/cs_office/exit_ceiling.mdl", + "models/props/cs_office/exit_wall.mdl", + "models/props/cs_office/file_cabinet1.mdl", + "models/props/cs_office/file_cabinet1_group.mdl", + "models/props/cs_office/file_cabinet2.mdl", + "models/props/cs_office/file_cabinet3.mdl", + "models/props/cs_office/fire_extinguisher.mdl", + "models/props/cs_office/light_security.mdl", + "models/props/cs_office/offcertificatea.mdl", + "models/props/cs_office/offcorkboarda.mdl", + "models/props/cs_office/offinspa.mdl", + "models/props/cs_office/offinspb.mdl", + "models/props/cs_office/offinspc.mdl", + "models/props/cs_office/offinspd.mdl", + "models/props/cs_office/offinspf.mdl", + "models/props/cs_office/offinspg.mdl", + "models/props/cs_office/offpaintinga.mdl", + "models/props/cs_office/offpaintingb.mdl", + "models/props/cs_office/offpaintingd.mdl", + "models/props/cs_office/offpaintinge.mdl", + "models/props/cs_office/offpaintingf.mdl", + "models/props/cs_office/offpaintingg.mdl", + "models/props/cs_office/offpaintingh.mdl", + "models/props/cs_office/offpaintingi.mdl", + "models/props/cs_office/offpaintingj.mdl", + "models/props/cs_office/offpaintingk.mdl", + "models/props/cs_office/offpaintingl.mdl", + "models/props/cs_office/offpaintingm.mdl", + "models/props/cs_office/offpaintingo.mdl", + "models/props/cs_office/paper_towels.mdl", + "models/props/cs_office/phone_p1.mdl", + "models/props/cs_office/phone_p2.mdl", + "models/props/cs_office/plant01.mdl", + "models/props/cs_office/plant01_p1.mdl", + "models/props/cs_office/radio.mdl", + "models/props/cs_office/shelves_metal.mdl", + "models/props/cs_office/shelves_metal1.mdl", + "models/props/cs_office/shelves_metal2.mdl", + "models/props/cs_office/shelves_metal3.mdl", + "models/props/cs_office/snowman_body.mdl", + "models/props/cs_office/snowman_face.mdl", + "models/props/cs_office/snowman_hat.mdl", + "models/props/cs_office/snowman_head.mdl", + "models/props/cs_office/snowman_nose.mdl", + "models/props/cs_office/sofa.mdl", + "models/props/cs_office/sofa_chair.mdl", + "models/props/cs_office/table_coffee.mdl", + "models/props/cs_office/table_meeting.mdl", + "models/props/cs_office/trash_can_p.mdl", + "models/props/cs_office/tv_plasma.mdl", + "models/props/de_cbble/cb_wndsng16.mdl", + "models/props/de_chateau/light_chandelier02.mdl", + "models/props/de_dust/grainbasket01a.mdl", + "models/props/de_dust/grainbasket01b.mdl", + "models/props/de_dust/stoneblocks48.mdl", + "models/props/de_dust/wagon.mdl", + "models/props/de_inferno/bed.mdl", + "models/props/de_inferno/bell_large.mdl", + "models/props/de_inferno/bench_wood.mdl", + "models/props/de_inferno/churchprop01.mdl", + "models/props/de_inferno/churchprop02.mdl", + "models/props/de_inferno/churchprop03.mdl", + "models/props/de_inferno/churchprop04.mdl", + "models/props/de_inferno/churchprop05.mdl", + "models/props/de_inferno/clayoven.mdl", + "models/props/de_inferno/confessional.mdl", + "models/props/de_inferno/crate_fruit_break.mdl", + "models/props/de_inferno/crate_fruit_break_p1.mdl", + "models/props/de_inferno/de_inferno_boulder_01.mdl", + "models/props/de_inferno/de_inferno_boulder_02.mdl", + "models/props/de_inferno/de_inferno_boulder_03.mdl", + "models/props/de_inferno/fireplace.mdl", + "models/props/de_inferno/flower_barrel.mdl", + "models/props/de_inferno/furniturecouch001a.mdl", + "models/props/de_inferno/furniture_couch02a.mdl", + "models/props/de_inferno/hay_bails.mdl", + "models/props/de_inferno/largebush04.mdl", + "models/props/de_inferno/largebush06.mdl", + "models/props/de_inferno/light_fixture.mdl", + "models/props/de_inferno/light_streetlight.mdl", + "models/props/de_inferno/monument.mdl", + "models/props/de_inferno/picture1.mdl", + "models/props/de_inferno/picture2.mdl", + "models/props/de_inferno/picture3.mdl", + "models/props/de_inferno/potted_plant1.mdl", + "models/props/de_inferno/potted_plant2.mdl", + "models/props/de_inferno/pot_big.mdl", + "models/props/de_inferno/tableantique.mdl", + "models/props/de_inferno/tv_monitor01.mdl", + "models/props/de_inferno/wagon.mdl", + "models/props/de_nuke/cinderblock_stack.mdl", + "models/props/de_nuke/clock.mdl", + "models/props/de_nuke/crate_extrasmall.mdl", + "models/props/de_nuke/crate_large.mdl", + "models/props/de_nuke/crate_small.mdl", + "models/props/de_nuke/emergency_lighta.mdl", + "models/props/de_nuke/equipment1.mdl", + "models/props/de_nuke/equipment3a.mdl", + "models/props/de_nuke/file_cabinet1_group.mdl", + "models/props/de_nuke/handtruck.mdl", + "models/props/de_nuke/industriallight01.mdl", + "models/props/de_nuke/lifepreserver.mdl", + "models/props/de_nuke/light_red1.mdl", + "models/props/de_nuke/light_red2.mdl", + "models/props/de_nuke/nuclearcontrolbox.mdl", + "models/props/de_nuke/nucleartestcabinet.mdl", + "models/props/de_piranesi/pi_bench.mdl", + "models/props/de_piranesi/pi_bucket.mdl", + "models/props/de_piranesi/pi_orrery.mdl", + "models/props/de_piranesi/pi_sundial.mdl", + "models/props/de_prodigy/ammo_can_01.mdl", + "models/props/de_prodigy/ammo_can_02.mdl", + "models/props/de_prodigy/ammo_can_03.mdl", + "models/props/de_prodigy/pushcart.mdl", + "models/props/de_tides/lights_studio.mdl", + "models/props/de_tides/patio_chair.mdl", + "models/props/de_tides/patio_chair2.mdl", + "models/props/de_tides/restaurant_table.mdl", + "models/props/de_tides/tides_staffonly_sign.mdl", + "models/props/de_tides/vending_cart.mdl", + "models/props/de_tides/vending_cart_base.mdl", + "models/props/de_tides/vending_hat.mdl", + "models/props/de_tides/vending_tshirt.mdl", + "models/props/de_tides/vending_turtle.mdl", + "models/props/de_train/processor_nobase.mdl", + "models/props_borealis/bluebarrel001.mdl", + "models/props_borealis/borealis_door001a.mdl", + "models/props_borealis/door_wheel001a.mdl", + "models/props_borealis/mooring_cleat01.mdl", + "models/props_building_details/courtyard_template001c_bars.mdl", + "models/props_building_details/courtyard_template002c_bars.mdl", + "models/props_c17/awning001a.mdl", + "models/props_c17/awning002a.mdl", + "models/props_c17/bench01a.mdl", + "models/props_c17/briefcase001a.mdl", + "models/props_c17/canister01a.mdl", + "models/props_c17/canister02a.mdl", + "models/props_c17/cashregister01a.mdl", + "models/props_c17/chair02a.mdl", + "models/props_c17/chair_kleiner03a.mdl", + "models/props_c17/chair_office01a.mdl", + "models/props_c17/chair_stool01a.mdl", + "models/props_c17/clock01.mdl", + "models/props_c17/computer01_keyboard.mdl", + "models/props_c17/concrete_barrier001a.mdl", + "models/props_c17/display_cooler01a.mdl", + "models/props_c17/doll01.mdl", + "models/props_c17/door01_left.mdl", + "models/props_c17/door02_double.mdl", + "models/props_c17/fence01a.mdl", + "models/props_c17/fence01b.mdl", + "models/props_c17/fence02a.mdl", + "models/props_c17/fence02b.mdl", + "models/props_c17/fence03a.mdl", + "models/props_c17/frame002a.mdl", + "models/props_c17/furniturebathtub001a.mdl", + "models/props_c17/furniturebed001a.mdl", + "models/props_c17/furniturechair001a.mdl", + "models/props_c17/furniturecouch001a.mdl", + "models/props_c17/furniturecouch002a.mdl", + "models/props_c17/furniturecupboard001a.mdl", + "models/props_c17/furnituredrawer001a.mdl", + "models/props_c17/furnituredrawer001a_chunk01.mdl", + "models/props_c17/furnituredrawer001a_chunk02.mdl", + "models/props_c17/furnituredrawer001a_chunk03.mdl", + "models/props_c17/furnituredrawer001a_chunk05.mdl", + "models/props_c17/furnituredrawer001a_chunk06.mdl", + "models/props_c17/furnituredrawer002a.mdl", + "models/props_c17/furnituredrawer003a.mdl", + "models/props_c17/furnituredresser001a.mdl", + "models/props_c17/furniturefireplace001a.mdl", + "models/props_c17/furniturefridge001a.mdl", + "models/props_c17/furnitureradiator001a.mdl", + "models/props_c17/furnitureshelf001a.mdl", + "models/props_c17/furnitureshelf001b.mdl", + "models/props_c17/furnitureshelf002a.mdl", + "models/props_c17/furnituresink001a.mdl", + "models/props_c17/furniturestove001a.mdl", + "models/props_c17/furnituretable001a.mdl", + "models/props_c17/furnituretable002a.mdl", + "models/props_c17/furnituretable003a.mdl", + "models/props_c17/furnituretoilet001a.mdl", + "models/props_c17/furniturewashingmachine001a.mdl", + "models/props_c17/gaspipes006a.mdl", + "models/props_c17/gate_door01a.mdl", + "models/props_c17/gate_door02a.mdl", + "models/props_c17/gravestone001a.mdl", + "models/props_c17/gravestone002a.mdl", + "models/props_c17/gravestone003a.mdl", + "models/props_c17/gravestone004a.mdl", + "models/props_c17/gravestone_coffinpiece001a.mdl", + "models/props_c17/gravestone_coffinpiece002a.mdl", + "models/props_c17/gravestone_cross001b.mdl", + "models/props_c17/gravestone_statue001a.mdl", + "models/props_c17/lamp001a.mdl", + "models/props_c17/lampshade001a.mdl", + "models/props_c17/light_cagelight02_on.mdl", + "models/props_c17/light_floodlight02_off.mdl", + "models/props_c17/light_magnifyinglamp02.mdl", + "models/props_c17/lockers001a.mdl", + "models/props_c17/metalladder001.mdl", + "models/props_c17/metalladder002.mdl", + "models/props_c17/metalpot001a.mdl", + "models/props_c17/metalpot002a.mdl", + "models/props_c17/oildrum001.mdl", + "models/props_c17/playgroundslide01.mdl", + "models/props_c17/playgroundtick-tack-toe_block01a.mdl", + "models/props_c17/playgroundtick-tack-toe_post01.mdl", + "models/props_c17/playground_carousel01.mdl", + "models/props_c17/playground_jungle_gym01a.mdl", + "models/props_c17/playground_jungle_gym01b.mdl", + "models/props_c17/playground_teetertoter_seat.mdl", + "models/props_c17/playground_teetertoter_stan.mdl", + "models/props_c17/pottery01a.mdl", + "models/props_c17/pottery02a.mdl", + "models/props_c17/pottery03a.mdl", + "models/props_c17/pottery04a.mdl", + "models/props_c17/pottery05a.mdl", + "models/props_c17/pottery06a.mdl", + "models/props_c17/pottery07a.mdl", + "models/props_c17/pottery08a.mdl", + "models/props_c17/pottery09a.mdl", + "models/props_c17/pottery_large01a.mdl", + "models/props_c17/pulleyhook01.mdl", + "models/props_c17/shelfunit01a.mdl", + "models/props_c17/signpole001.mdl", + "models/props_c17/streetsign001c.mdl", + "models/props_c17/streetsign002b.mdl", + "models/props_c17/streetsign003b.mdl", + "models/props_c17/streetsign004e.mdl", + "models/props_c17/streetsign004f.mdl", + "models/props_c17/streetsign005b.mdl", + "models/props_c17/streetsign005c.mdl", + "models/props_c17/streetsign005d.mdl", + "models/props_c17/suitcase001a.mdl", + "models/props_c17/traffic_light001a.mdl", + "models/props_c17/trappropeller_lever.mdl", + "models/props_c17/truss02g.mdl", + "models/props_c17/truss02h.mdl", + "models/props_c17/truss03b.mdl", + "models/props_c17/tv_monitor01.mdl", + "models/props_c17/utilityconnecter006.mdl", + "models/props_c17/utilityconnecter006c.mdl", + "models/props_c17/utilitypole01a.mdl", + "models/props_c17/woodbarrel001.mdl", + "models/props_canal/mattpipe.mdl", + "models/props_combine/breenbust.mdl", + "models/props_combine/breenchair.mdl", + "models/props_combine/breenclock.mdl", + "models/props_combine/breenconsole.mdl", + "models/props_combine/breendesk.mdl", + "models/props_combine/breenglobe.mdl", + "models/props_combine/breenpod.mdl", + "models/props_combine/breenpod_inner.mdl", + "models/props_combine/bunker_gun01.mdl", + "models/props_combine/cell_01_pod.mdl", + "models/props_combine/cell_01_pod_cheap.mdl", + "models/props_combine/combinebutton.mdl", + "models/props_combine/combine_barricade_med01a.mdl", + "models/props_combine/combine_barricade_med01b.mdl", + "models/props_combine/combine_barricade_med02a.mdl", + "models/props_combine/combine_barricade_med02b.mdl", + "models/props_combine/combine_barricade_med02c.mdl", + "models/props_combine/combine_barricade_med03b.mdl", + "models/props_combine/combine_barricade_med04b.mdl", + "models/props_combine/combine_barricade_short01a.mdl", + "models/props_combine/combine_barricade_short02a.mdl", + "models/props_combine/combine_barricade_short03a.mdl", + "models/props_combine/combine_barricade_tall01a.mdl", + "models/props_combine/combine_barricade_tall01b.mdl", + "models/props_combine/combine_barricade_tall03a.mdl", + "models/props_combine/combine_barricade_tall03b.mdl", + "models/props_combine/combine_barricade_tall04a.mdl", + "models/props_combine/combine_barricade_tall04b.mdl", + "models/props_combine/combine_booth_med01a.mdl", + "models/props_combine/combine_booth_short01a.mdl", + "models/props_combine/combine_emitter01.mdl", + "models/props_combine/combine_fence01a.mdl", + "models/props_combine/combine_fence01b.mdl", + "models/props_combine/combine_interface001.mdl", + "models/props_combine/combine_interface002.mdl", + "models/props_combine/combine_interface003.mdl", + "models/props_combine/combine_intwallunit.mdl", + "models/props_combine/combine_window001.mdl", + "models/props_combine/weaponstripper.mdl", + "models/props_debris/metal_panel01a.mdl", + "models/props_debris/metal_panel02a.mdl", + "models/props_debris/wall001a_base.mdl", + "models/props_docks/dock01_cleat01a.mdl", + "models/props_docks/dock01_pole01a_128.mdl", + "models/props_docks/dock01_pole01a_256.mdl", + "models/props_doors/door03_slotted_left.mdl", + "models/props_industrial/bridge_deck.mdl", + "models/props_industrial/winch_stern.mdl", + "models/props_interiors/bathtub01a.mdl", + "models/props_interiors/furniture_chair01a.mdl", + "models/props_interiors/furniture_chair03a.mdl", + "models/props_interiors/furniture_couch01a.mdl", + "models/props_interiors/furniture_couch02a.mdl", + "models/props_interiors/furniture_desk01a.mdl", + "models/props_interiors/furniture_lamp01a.mdl", + "models/props_interiors/furniture_shelf01a.mdl", + "models/props_interiors/furniture_vanity01a.mdl", + "models/props_interiors/pot01a.mdl", + "models/props_interiors/pot02a.mdl", + "models/props_interiors/radiator01a.mdl", + "models/props_interiors/refrigerator01a.mdl", + "models/props_interiors/refrigeratordoor01a.mdl", + "models/props_interiors/refrigeratordoor02a.mdl", + "models/props_interiors/sinkkitchen01a.mdl", + "models/props_junk/bicycle01a.mdl", + "models/props_junk/cardboard_box001a.mdl", + "models/props_junk/cardboard_box001a_gib01.mdl", + "models/props_junk/cardboard_box001b.mdl", + "models/props_junk/cardboard_box002a.mdl", + "models/props_junk/cardboard_box002a_gib01.mdl", + "models/props_junk/cardboard_box002b.mdl", + "models/props_junk/cardboard_box003a.mdl", + "models/props_junk/cardboard_box003a_gib01.mdl", + "models/props_junk/cardboard_box003b.mdl", + "models/props_junk/cardboard_box003b_gib01.mdl", + "models/props_junk/cinderblock01a.mdl", + "models/props_junk/garbage128_composite001a.mdl", + "models/props_junk/garbage128_composite001b.mdl", + "models/props_junk/garbage128_composite001c.mdl", + "models/props_junk/garbage128_composite001d.mdl", + "models/props_junk/garbage256_composite001a.mdl", + "models/props_junk/garbage256_composite001b.mdl", + "models/props_junk/garbage256_composite002a.mdl", + "models/props_junk/garbage256_composite002b.mdl", + "models/props_junk/garbage_bag001a.mdl", + "models/props_junk/garbage_carboard002a.mdl", + "models/props_junk/garbage_coffeemug001a.mdl", + "models/props_junk/garbage_newspaper001a.mdl", + "models/props_junk/glassjug01.mdl", + "models/props_junk/harpoon002a.mdl", + "models/props_junk/metalbucket01a.mdl", + "models/props_junk/metalbucket02a.mdl", + "models/props_junk/metalgascan.mdl", + "models/props_junk/plasticbucket001a.mdl", + "models/props_junk/pushcart01a.mdl", + "models/props_junk/ravenholmsign.mdl", + "models/props_junk/shoe001a.mdl", + "models/props_junk/shovel01a.mdl", + "models/props_junk/terracotta01.mdl", + "models/props_junk/trafficcone001a.mdl", + "models/props_junk/trashbin01a.mdl", + "models/props_junk/trashdumpster01a.mdl", + "models/props_junk/trashdumpster02b.mdl", + "models/props_junk/wheebarrow01a.mdl", + "models/props_junk/wood_crate001a.mdl", + "models/props_junk/wood_crate001a_damaged.mdl", + "models/props_junk/wood_crate002a.mdl", + "models/props_junk/wood_pallet001a.mdl", + "models/props_lab/bewaredog.mdl", + "models/props_lab/binderblue.mdl", + "models/props_lab/binderbluelabel.mdl", + "models/props_lab/bindergraylabel01a.mdl", + "models/props_lab/bindergraylabel01b.mdl", + "models/props_lab/bindergreen.mdl", + "models/props_lab/bindergreenlabel.mdl", + "models/props_lab/binderredlabel.mdl", + "models/props_lab/blastdoor001a.mdl", + "models/props_lab/blastdoor001b.mdl", + "models/props_lab/blastdoor001c.mdl", + "models/props_lab/cactus.mdl", + "models/props_lab/clipboard.mdl", + "models/props_lab/cornerunit2.mdl", + "models/props_lab/desklamp01.mdl", + "models/props_lab/filecabinet02.mdl", + "models/props_lab/frame002a.mdl", + "models/props_lab/generatorconsole.mdl", + "models/props_lab/harddrive01.mdl", + "models/props_lab/hevplate.mdl", + "models/props_lab/huladoll.mdl", + "models/props_lab/kennel_physics.mdl", + "models/props_lab/lockerdoorleft.mdl", + "models/props_lab/miniteleport.mdl", + "models/props_lab/monitor01a.mdl", + "models/props_lab/monitor01b.mdl", + "models/props_lab/monitor02.mdl", + "models/props_lab/partsbin01.mdl", + "models/props_lab/plotter.mdl", + "models/props_lab/powerbox02d.mdl", + "models/props_lab/reciever01a.mdl", + "models/props_lab/reciever01b.mdl", + "models/props_lab/reciever_cart.mdl", + "models/props_lab/securitybank.mdl", + "models/props_lab/servers.mdl", + "models/props_lab/teleplatform.mdl", + "models/props_lab/tpplugholder.mdl", + "models/props_lab/tpplugholder_single.mdl", + "models/props_lab/workspace001.mdl", + "models/props_lab/workspace002.mdl", + "models/props_lab/workspace003.mdl", + "models/props_lab/workspace004.mdl", + "models/props_phx/construct/concrete_barrier00.mdl", + "models/props_phx/construct/concrete_barrier01.mdl", + "models/props_phx/construct/glass/glass_angle180.mdl", + "models/props_phx/construct/glass/glass_angle360.mdl", + "models/props_phx/construct/glass/glass_angle90.mdl", + "models/props_phx/construct/glass/glass_curve180x1.mdl", + "models/props_phx/construct/glass/glass_curve180x2.mdl", + "models/props_phx/construct/glass/glass_curve90x1.mdl", + "models/props_phx/construct/glass/glass_curve90x2.mdl", + "models/props_phx/construct/glass/glass_dome180.mdl", + "models/props_phx/construct/glass/glass_dome90.mdl", + "models/props_phx/construct/glass/glass_plate1x1.mdl", + "models/props_phx/construct/glass/glass_plate1x2.mdl", + "models/props_phx/construct/glass/glass_plate2x2.mdl", + "models/props_phx/construct/glass/glass_plate2x4.mdl", + "models/props_phx/construct/glass/glass_plate4x4.mdl", + "models/props_phx/construct/metal_angle180.mdl", + "models/props_phx/construct/metal_angle360.mdl", + "models/props_phx/construct/metal_angle90.mdl", + "models/props_phx/construct/metal_dome180.mdl", + "models/props_phx/construct/metal_dome360.mdl", + "models/props_phx/construct/metal_dome90.mdl", + "models/props_phx/construct/metal_plate1.mdl", + "models/props_phx/construct/metal_plate1x2.mdl", + "models/props_phx/construct/metal_plate1x2_tri.mdl", + "models/props_phx/construct/metal_plate1_tri.mdl", + "models/props_phx/construct/metal_plate2x2.mdl", + "models/props_phx/construct/metal_plate2x2_tri.mdl", + "models/props_phx/construct/metal_plate2x4.mdl", + "models/props_phx/construct/metal_plate2x4_tri.mdl", + "models/props_phx/construct/metal_plate4x4.mdl", + "models/props_phx/construct/metal_plate4x4_tri.mdl", + "models/props_phx/construct/metal_plate_curve.mdl", + "models/props_phx/construct/metal_plate_curve180.mdl", + "models/props_phx/construct/metal_plate_curve180x2.mdl", + "models/props_phx/construct/metal_plate_curve2.mdl", + "models/props_phx/construct/metal_plate_curve2x2.mdl", + "models/props_phx/construct/metal_tube.mdl", + "models/props_phx/construct/metal_tubex2.mdl", + "models/props_phx/construct/metal_wire1x1.mdl", + "models/props_phx/construct/metal_wire1x1x1.mdl", + "models/props_phx/construct/metal_wire1x1x2.mdl", + "models/props_phx/construct/metal_wire1x1x2b.mdl", + "models/props_phx/construct/metal_wire1x2.mdl", + "models/props_phx/construct/metal_wire1x2b.mdl", + "models/props_phx/construct/metal_wire1x2x2b.mdl", + "models/props_phx/construct/metal_wire2x2.mdl", + "models/props_phx/construct/metal_wire2x2b.mdl", + "models/props_phx/construct/metal_wire2x2x2b.mdl", + "models/props_phx/construct/metal_wire_angle180x1.mdl", + "models/props_phx/construct/metal_wire_angle180x2.mdl", + "models/props_phx/construct/metal_wire_angle90x1.mdl", + "models/props_phx/construct/metal_wire_angle90x2.mdl", + "models/props_phx/construct/plastic/plastic_angle_360.mdl", + "models/props_phx/construct/plastic/plastic_panel1x1.mdl", + "models/props_phx/construct/plastic/plastic_panel1x2.mdl", + "models/props_phx/construct/windows/window1x1.mdl", + "models/props_phx/construct/windows/window1x2.mdl", + "models/props_phx/construct/windows/window2x2.mdl", + "models/props_phx/construct/windows/window2x4.mdl", + "models/props_phx/construct/windows/window4x4.mdl", + "models/props_phx/construct/windows/window_angle180.mdl", + "models/props_phx/construct/windows/window_angle360.mdl", + "models/props_phx/construct/windows/window_angle90.mdl", + "models/props_phx/construct/windows/window_curve180x1.mdl", + "models/props_phx/construct/windows/window_curve180x2.mdl", + "models/props_phx/construct/windows/window_curve90x1.mdl", + "models/props_phx/construct/windows/window_curve90x2.mdl", + "models/props_phx/construct/wood/wood_angle180.mdl", + "models/props_phx/construct/wood/wood_angle360.mdl", + "models/props_phx/construct/wood/wood_angle90.mdl", + "models/props_phx/construct/wood/wood_boardx1.mdl", + "models/props_phx/construct/wood/wood_boardx2.mdl", + "models/props_phx/construct/wood/wood_curve180x1.mdl", + "models/props_phx/construct/wood/wood_curve180x2.mdl", + "models/props_phx/construct/wood/wood_curve90x1.mdl", + "models/props_phx/construct/wood/wood_curve90x2.mdl", + "models/props_phx/construct/wood/wood_dome180.mdl", + "models/props_phx/construct/wood/wood_dome360.mdl", + "models/props_phx/construct/wood/wood_dome90.mdl", + "models/props_phx/construct/wood/wood_panel1x1.mdl", + "models/props_phx/construct/wood/wood_panel1x2.mdl", + "models/props_phx/construct/wood/wood_panel2x2.mdl", + "models/props_phx/construct/wood/wood_panel2x4.mdl", + "models/props_phx/construct/wood/wood_panel4x4.mdl", + "models/props_phx/construct/wood/wood_wire1x1.mdl", + "models/props_phx/construct/wood/wood_wire1x1x1.mdl", + "models/props_phx/construct/wood/wood_wire1x1x2.mdl", + "models/props_phx/construct/wood/wood_wire1x1x2b.mdl", + "models/props_phx/construct/wood/wood_wire1x2.mdl", + "models/props_phx/construct/wood/wood_wire1x2b.mdl", + "models/props_phx/construct/wood/wood_wire1x2x2b.mdl", + "models/props_phx/construct/wood/wood_wire2x2.mdl", + "models/props_phx/construct/wood/wood_wire2x2b.mdl", + "models/props_phx/construct/wood/wood_wire2x2x2b.mdl", + "models/props_phx/empty_barrel.mdl", + "models/props_phx/games/chess/black_bishop.mdl", + "models/props_phx/games/chess/black_dama.mdl", + "models/props_phx/games/chess/black_king.mdl", + "models/props_phx/games/chess/black_knight.mdl", + "models/props_phx/games/chess/black_pawn.mdl", + "models/props_phx/games/chess/black_queen.mdl", + "models/props_phx/games/chess/black_rook.mdl", + "models/props_phx/games/chess/white_bishop.mdl", + "models/props_phx/games/chess/white_dama.mdl", + "models/props_phx/games/chess/white_king.mdl", + "models/props_phx/games/chess/white_knight.mdl", + "models/props_phx/games/chess/white_pawn.mdl", + "models/props_phx/games/chess/white_queen.mdl", + "models/props_phx/games/chess/white_rook.mdl", + "models/props_phx/misc/fender.mdl", + "models/props_phx/misc/t_light_head.mdl", + "models/props_phx/rt_screen.mdl", + "models/props_rooftop/satellitedish02.mdl", + "models/props_rooftop/sign_letter02_e002.mdl", + "models/props_rooftop/sign_letter02_k002.mdl", + "models/props_rooftop/sign_letter02_rus1002.mdl", + "models/props_rooftop/sign_letter_f001b.mdl", + "models/props_rooftop/sign_letter_h001.mdl", + "models/props_rooftop/sign_letter_m001.mdl", + "models/props_rooftop/sign_letter_t001.mdl", + "models/props_rooftop/sign_letter_u001b.mdl", + "models/props_trainstation/benchoutdoor01a.mdl", + "models/props_trainstation/bench_indoor001a.mdl", + "models/props_trainstation/payphone001a.mdl", + "models/props_trainstation/tracksign02.mdl", + "models/props_trainstation/tracksign07.mdl", + "models/props_trainstation/tracksign08.mdl", + "models/props_trainstation/tracksign09.mdl", + "models/props_trainstation/tracksign10.mdl", + "models/props_trainstation/traincar_seats001.mdl", + "models/props_trainstation/trainstation_arch001.mdl", + "models/props_trainstation/trainstation_clock001.mdl", + "models/props_trainstation/trainstation_post001.mdl", + "models/props_trainstation/trashcan_indoor001a.mdl", + "models/props_trainstation/trashcan_indoor001b.mdl", + "models/props_vehicles/carparts_door01a.mdl", + "models/props_vehicles/carparts_tire01a.mdl", + "models/props_wasteland/barricade001a.mdl", + "models/props_wasteland/barricade002a.mdl", + "models/props_wasteland/buoy01.mdl", + "models/props_wasteland/cafeteria_table001a.mdl", + "models/props_wasteland/controlroom_chair001a.mdl", + "models/props_wasteland/controlroom_desk001a.mdl", + "models/props_wasteland/controlroom_desk001b.mdl", + "models/props_wasteland/controlroom_filecabinet001a.mdl", + "models/props_wasteland/controlroom_filecabinet002a.mdl", + "models/props_wasteland/controlroom_monitor001a.mdl", + "models/props_wasteland/dockplank01b.mdl", + "models/props_wasteland/exterior_fence001a.mdl", + "models/props_wasteland/exterior_fence001b.mdl", + "models/props_wasteland/exterior_fence002a.mdl", + "models/props_wasteland/exterior_fence002b.mdl", + "models/props_wasteland/exterior_fence002c.mdl", + "models/props_wasteland/exterior_fence002d.mdl", + "models/props_wasteland/exterior_fence003a.mdl", + "models/props_wasteland/exterior_fence003b.mdl", + "models/props_wasteland/gaspump001a.mdl", + "models/props_wasteland/interior_fence001a.mdl", + "models/props_wasteland/interior_fence001b.mdl", + "models/props_wasteland/interior_fence001c.mdl", + "models/props_wasteland/interior_fence001d.mdl", + "models/props_wasteland/interior_fence001e.mdl", + "models/props_wasteland/interior_fence001g.mdl", + "models/props_wasteland/interior_fence002a.mdl", + "models/props_wasteland/interior_fence002b.mdl", + "models/props_wasteland/interior_fence002c.mdl", + "models/props_wasteland/interior_fence002d.mdl", + "models/props_wasteland/interior_fence002e.mdl", + "models/props_wasteland/interior_fence002f.mdl", + "models/props_wasteland/interior_fence003a.mdl", + "models/props_wasteland/interior_fence003b.mdl", + "models/props_wasteland/interior_fence003d.mdl", + "models/props_wasteland/interior_fence003e.mdl", + "models/props_wasteland/interior_fence003f.mdl", + "models/props_wasteland/kitchen_counter001a.mdl", + "models/props_wasteland/kitchen_counter001b.mdl", + "models/props_wasteland/kitchen_counter001c.mdl", + "models/props_wasteland/kitchen_counter001d.mdl", + "models/props_wasteland/kitchen_shelf001a.mdl", + "models/props_wasteland/kitchen_shelf002a.mdl", + "models/props_wasteland/kitchen_stove001a.mdl", + "models/props_wasteland/kitchen_stove002a.mdl", + "models/props_wasteland/laundry_basket001.mdl", + "models/props_wasteland/laundry_cart001.mdl", + "models/props_wasteland/laundry_cart002.mdl", + "models/props_wasteland/laundry_washer003.mdl", + "models/props_wasteland/light_spotlight01_lamp.mdl", + "models/props_wasteland/prison_bedframe001b.mdl", + "models/props_wasteland/prison_celldoor001a.mdl", + "models/props_wasteland/prison_celldoor001b.mdl", + "models/props_wasteland/prison_cellwindow002a.mdl", + "models/props_wasteland/prison_heater001a.mdl", + "models/props_wasteland/prison_lamp001c.mdl", + "models/props_wasteland/prison_shelf002a.mdl", + "models/props_wasteland/prison_sink001a.mdl", + "models/props_wasteland/prison_toilet01.mdl", + "models/props_wasteland/speakercluster01a.mdl", + "models/props_wasteland/wood_fence01a.mdl", + "models/props_wasteland/wood_fence02a.mdl" + } +} diff --git a/addons/3d2d_textscreens/addon.json b/addons/3d2d_textscreens/addon.json new file mode 100644 index 0000000..062914a --- /dev/null +++ b/addons/3d2d_textscreens/addon.json @@ -0,0 +1,9 @@ +{ + "title": "3D2D Textscreens", + "type": "tool", + "tags": [ + "fun", + "roleplay" + ], + "ignore": [] +} \ No newline at end of file diff --git a/addons/3d2d_textscreens/lua/autorun/textscreens_util.lua b/addons/3d2d_textscreens/lua/autorun/textscreens_util.lua new file mode 100644 index 0000000..61e2358 --- /dev/null +++ b/addons/3d2d_textscreens/lua/autorun/textscreens_util.lua @@ -0,0 +1,180 @@ +local function checkAdmin(ply) + -- The server console always has access. `ply` is NULL in this case + local isConsole = ply == nil or ply == NULL + if isConsole then + return true + end + local canAdmin = hook.Run("TextscreensCanAdmin", ply) -- run custom hook function to check admin + if canAdmin == nil then -- if hook hasn't returned anything, default to super admin check + canAdmin = ply:IsSuperAdmin() + end + return canAdmin +end + +-- allow servers to disable rainbow effect for everyone +CreateConVar("ss_enable_rainbow", 1, {FCVAR_NOTIFY, FCVAR_REPLICATED}, "Determines whether rainbow textscreens will render for all clients. When disabled, rainbow screens will render as solid white.", 0, 1) + +-- allow servers to restrict the number of characters per line for everyone +CreateConVar("ss_max_characters", 0, {FCVAR_NOTIFY, FCVAR_REPLICATED}, "Determines the maximum number of characters per line for all clients. When set to 0, the maximum number of characters is infinite.", 0) + +if SERVER then + AddCSLuaFile() + AddCSLuaFile("textscreens_config.lua") + include("textscreens_config.lua") + CreateConVar("sbox_maxtextscreens", "1", {FCVAR_NOTIFY, FCVAR_REPLICATED}, "Determines the maximum number of textscreens users can spawn.") + + --local rainbow_enabled = cvars.Number('ss_enable_rainbow', 1) + + local function StringRandom(int) + math.randomseed(os.time()) + local s = "" + + for i = 1, int do + s = s .. string.char(math.random(65, 90)) + end + + return s + end + + local textscreens = {} + + local function SpawnPermaTextscreens() + print("[3D2D Textscreens] Spawning textscreens...") + textscreens = file.Read("sammyservers_textscreens.txt", "DATA") + if not textscreens or textscreens == "" then + textscreens = {} + print("[3D2D Textscreens] Spawned 0 textscreens for map " .. game.GetMap()) + return + end + textscreens = util.JSONToTable(textscreens) + + local existingTextscreens = {} + for k,v in pairs(ents.FindByClass("sammyservers_textscreen")) do + if not v.uniqueName then continue end + existingTextscreens[v.uniqueName] = true + end + + local count = 0 + for k, v in pairs(textscreens) do + if v.MapName ~= game.GetMap() then continue end + if existingTextscreens[v.uniqueName] then continue end + + local textScreen = ents.Create("sammyservers_textscreen") + textScreen:SetPos(Vector(v.posx, v.posy, v.posz)) + textScreen:SetAngles(Angle(v.angp, v.angy, v.angr)) + textScreen.uniqueName = v.uniqueName + textScreen:Spawn() + textScreen:Activate() + textScreen:SetMoveType(MOVETYPE_NONE) + + for lineNum, lineData in pairs(v.lines or {}) do + textScreen:SetLine(lineNum, lineData.text, Color(lineData.color.r, lineData.color.g, lineData.color.b, lineData.color.a), lineData.size, lineData.font, lineData.rainbow or 0) + end + + textScreen:SetIsPersisted(true) + count = count + 1 + end + + print("[3D2D Textscreens] Spawned " .. count .. " textscreens for map " .. game.GetMap()) + end + + hook.Add("InitPostEntity", "loadTextScreens", function() + timer.Simple(10, SpawnPermaTextscreens) + end) + + hook.Add("PostCleanupMap", "loadTextScreens", SpawnPermaTextscreens) + + -- If a player, use ChatPrint method, else print directly to server console + local function printMessage(ply, msg) + local isConsole = ply == nil or ply == NULL + if isConsole then + print(msg) + else + ply:ChatPrint(msg) + end + end + concommand.Add("SS_TextScreen", function(ply, cmd, args) + if not checkAdmin(ply) or not args or not args[1] or not args[2] or not (args[1] == "delete" or args[1] == "add") then + printMessage(ply, "not authorised, or bad arguments") + return + end + local ent = Entity(args[2]) + if not IsValid(ent) or ent:GetClass() ~= "sammyservers_textscreen" then return false end + + if args[1] == "add" then + local pos = ent:GetPos() + local ang = ent:GetAngles() + local toAdd = {} + toAdd.posx = pos.x + toAdd.posy = pos.y + toAdd.posz = pos.z + toAdd.angp = ang.p + toAdd.angy = ang.y + toAdd.angr = ang.r + -- So we can reference it easily later because EntIndexes are so unreliable + toAdd.uniqueName = StringRandom(10) + toAdd.MapName = game.GetMap() + toAdd.lines = ent.lines + table.insert(textscreens, toAdd) + file.Write("sammyservers_textscreens.txt", util.TableToJSON(textscreens)) + ent:SetIsPersisted(true) + + return printMessage(ply, "Textscreen made permanent and saved.") + else + for k, v in pairs(textscreens) do + if v.uniqueName == ent.uniqueName then + textscreens[k] = nil + end + end + + ent:Remove() + file.Write("sammyservers_textscreens.txt", util.TableToJSON(textscreens)) + + return printMessage(ply, "Textscreen removed and is no longer permanent.") + end + end) + + -- Add to pocket blacklist for DarkRP + -- Not using gamemode == "darkrp" because there are lots of flavours of darkrp + hook.Add("loadCustomDarkRPItems", "sammyservers_pocket_blacklist", function() + GAMEMODE.Config.PocketBlacklist["sammyservers_textscreen"] = true + end) +end + +if CLIENT then + include("textscreens_config.lua") + + properties.Add("addPermaScreen", { + MenuLabel = "Make perma textscreen", + Order = 2001, + MenuIcon = "icon16/transmit.png", + Filter = function(self, ent, ply) + if not IsValid(ent) or ent:GetClass() ~= "sammyservers_textscreen" then return false end + if ent:GetIsPersisted() then return false end + + return checkAdmin(ply) + end, + Action = function(self, ent) + if not IsValid(ent) then return false end + + return RunConsoleCommand("SS_TextScreen", "add", ent:EntIndex()) + end + }) + + properties.Add("removePermaScreen", { + MenuLabel = "Remove perma textscreen", + Order = 2002, + MenuIcon = "icon16/transmit_delete.png", + Filter = function(self, ent, ply) + if not IsValid(ent) or ent:GetClass() ~= "sammyservers_textscreen" then return false end + if not ent:GetIsPersisted() then return false end + + return checkAdmin(ply) + end, + Action = function(self, ent) + if not IsValid(ent) then return end + + return RunConsoleCommand("SS_TextScreen", "delete", ent:EntIndex()) + end + }) +end diff --git a/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/cl_init.lua b/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/cl_init.lua new file mode 100644 index 0000000..7de41f6 --- /dev/null +++ b/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/cl_init.lua @@ -0,0 +1,228 @@ +include("shared.lua") + +local render_convar_range = CreateClientConVar("ss_render_range", 1500, true, false, "Determines the render range for Textscreens. Default 1500") +local render_rainbow = CreateClientConVar("ss_render_rainbow", 1, true, false, "Determines if rainbow screens are rendered. If disabled (0), will render as solid white. Default enabled (1)", 0, 1) +local render_range = render_convar_range:GetInt() * render_convar_range:GetInt() --We multiply this is that we can use DistToSqr instead of Distance so we don't need to workout the square root all the time +local rainbow_enabled = cvars.Number("ss_enable_rainbow", 1) +local textscreenFonts = textscreenFonts +local screenInfo = {} +local shouldDrawBoth = false + +-- Numbers used in conjunction with text width to work out the render bounds +local widthBoundsDivider = 7.9 +local heightBoundsDivider = 12.4 + +-- ENUM type things for faster table indexing +local FONT = 1 +local TEXT = 2 +local POSX = 3 +local POSY = 4 +local COL = 5 +local LEN = 6 +local SIZE = 7 +local CAMSIZE = 8 +local RAINBOW = 9 + +-- Make ply:ShouldDrawLocalPlayer() never get called more than once a frame +hook.Add("Think", "ss_should_draw_both_sides", function() + shouldDrawBoth = LocalPlayer():ShouldDrawLocalPlayer() +end) + +local function ValidFont(f) + if textscreenFonts[f] ~= nil then + return textscreenFonts[f] + elseif table.HasValue(textscreenFonts, f) then + return f + else + return false + end +end + +cvars.AddChangeCallback("ss_render_range", function(convar_name, value_old, value_new) + render_range = tonumber(value_new) * tonumber(value_new) +end, "3D2DScreens") + +cvars.AddChangeCallback("ss_render_rainbow", function(convar_name, value_old, value_new) + render_rainbow = tonumber(value_new) +end, "3D2DScreens") + +-- TODO: https://github.com/Facepunch/garrysmod-issues/issues/3740 +-- cvars.AddChangeCallback("ss_enable_rainbow", function(convar_name, value_old, value_new) +-- print('ss_enable_rainbow changed: '.. value_new) +-- rainbow_enabled = tonumber(value_new) +-- end, "3D2DScreens") + +function ENT:Initialize() + self:SetMaterial("models/effects/vol_light001") + self:SetRenderMode(RENDERMODE_NONE) + net.Start("textscreens_download") + net.WriteEntity(self) + net.SendToServer() +end + +local product +local function IsInFront(entPos, plyShootPos, direction) + product = (entPos.x - plyShootPos.x) * direction.x + + (entPos.y - plyShootPos.y) * direction.y + + (entPos.z - plyShootPos.z) * direction.z + return product < 0 +end + + +-- Draws the 3D2D text with the given positions, angles and data(text/font/col) +local function Draw3D2D(ang, pos, camangle, data) + + for i = 1, data[LEN] do + if not data[i] or not data[i][TEXT] then continue end + + cam.Start3D2D(pos, camangle, data[i][CAMSIZE] ) + render.PushFilterMin(TEXFILTER.ANISOTROPIC) + -- Font + surface.SetFont(data[i][FONT]) + -- Position + surface.SetTextPos(data[i][POSX], data[i][POSY]) + -- Rainbow + if data[i][RAINBOW] ~= nil and data[i][RAINBOW] ~= 0 then + local j = 0 + for _, code in utf8.codes(data[i][TEXT]) do + j = j + 1 + --Color + if rainbow_enabled == 1 and render_rainbow ~= 0 then + surface.SetTextColor(HSVToColor((CurTime() * 60 + (j * 5)) % 360, 1, 1)) + else + -- Render as solid white if ss_render_rainbow is disabled or server disabled via ss_enable_rainbow + surface.SetTextColor(255, 255, 255) + end + --Text + surface.DrawText(utf8.char(code)) + end + else + --Color + surface.SetTextColor(data[i][COL]) + --Text + surface.DrawText(data[i][TEXT]) + end + + render.PopFilterMin() + cam.End3D2D() + end + +end + +local plyShootPos, ang, pos, camangle, showFront, data -- Less variables being created each frame +function ENT:DrawTranslucent() + -- Cache the shoot pos for this frame + plyShootPos = LocalPlayer():GetShootPos() + + if screenInfo[self] ~= nil and self:GetPos():DistToSqr(plyShootPos) < render_range then + ang = self:GetAngles() + pos = self:GetPos() + ang:Up() + camangle = Angle(ang.p, ang.y, ang.r) + data = screenInfo[self] + + -- Should we draw both screens? (Third person/calview drawing fix) + if shouldDrawBoth then + Draw3D2D(ang, pos, camangle, data) + camangle:RotateAroundAxis(camangle:Right(), 180) + Draw3D2D(ang, pos, camangle, data) + else + -- Is the front of the screen facing us or the back? + showFront = IsInFront(pos, plyShootPos, ang:Up()) + + -- Draw the front of the screen + if showFront then + Draw3D2D(ang, pos, camangle, data) + else + -- Draw the back of the screen + camangle:RotateAroundAxis(camangle:Right(), 180) + Draw3D2D(ang, pos, camangle, data) + end + end + end +end + +local function AddDrawingInfo(ent, rawData) + local drawData = {} + local textSize = {} + + local totalHeight = 0 + local maxWidth = 0 + local currentHeight = 0 + + for i = 1, #rawData do + -- Setup tables + if not rawData[i] or not rawData[i].text then continue end + drawData[i] = {} + textSize[i] = {} + -- Text + drawData[i][TEXT] = rawData[i].text + -- Font + drawData[i][FONT] = (ValidFont(rawData[i].font) or textscreenFonts[1]) + -- Text size + surface.SetFont(drawData[i][FONT]) + textSize[i][1], textSize[i][2] = surface.GetTextSize(drawData[i][TEXT]) + textSize[i][2] = rawData[i].size + -- Workout max width for render bounds + maxWidth = maxWidth > textSize[i][1] and maxWidth or textSize[i][1] + -- Position + totalHeight = totalHeight + textSize[i][2] + -- Colour + drawData[i][COL] = Color(rawData[i].color.r, rawData[i].color.g, rawData[i].color.b, 255) + -- Size + drawData[i][SIZE] = rawData[i]["size"] + -- Remove text if text is empty so we don't waste performance + if string.len(drawData[i][TEXT]) == 0 or string.len(string.Replace( drawData[i][TEXT], " ", "" )) == 0 then drawData[i][TEXT] = nil end + --Rainbow + drawData[i][RAINBOW] = rawData[i]["rainbow"] or 0 + end + + -- Sort out heights + for i = 1, #rawData do + if not rawData[i] then continue end + -- The x position at which to draw the text relative to the text screen entity + drawData[i][POSX] = math.ceil(-textSize[i][1] / 2) + -- The y position at which to draw the text relative to the text screen entity + drawData[i][POSY] = math.ceil(-(totalHeight / 2) + currentHeight) + -- Calculate the cam.Start3D2D size based on the size of the font + drawData[i][CAMSIZE] = (0.25 * drawData[i][SIZE]) / 100 + -- Use the CAMSIZE to "scale" the POSY + drawData[i][POSY] = (0.25 / drawData[i][CAMSIZE] * drawData[i][POSY]) + -- Highest line to lowest, so that everything is central + currentHeight = currentHeight + textSize[i][2] + end + + -- Cache the number of lines/length + drawData[LEN] = #drawData + -- Add the new data to our text screen list + screenInfo[ent] = drawData + + -- Calculate the render bounds + local x = maxWidth / widthBoundsDivider + local y = currentHeight / heightBoundsDivider + 13 -- Text is above the centre + + -- Setup the render bounds + ent:SetRenderBounds(Vector(-x, -y, -1.75), Vector(x, y, 1.75)) +end + +net.Receive("textscreens_update", function(len) + local ent = net.ReadEntity() + + if IsValid(ent) and ent:GetClass() == "sammyservers_textscreen" then + + local t = net.ReadTable() + + ent.lines = t -- Incase an addon or something wants to read the information. + + AddDrawingInfo(ent, t) + end +end) + +-- Auto refresh +if IsValid(LocalPlayer()) then + local screens = ents.FindByClass("sammyservers_textscreen") + for k, v in ipairs(screens) do + if screenInfo[v] == nil and v.lines ~= nil then + AddDrawingInfo(v, v.lines) + end + end +end diff --git a/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/init.lua b/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/init.lua new file mode 100644 index 0000000..1092c75 --- /dev/null +++ b/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/init.lua @@ -0,0 +1,134 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +resource.AddWorkshop("109643223") + +include("shared.lua") + +local CurTime = CurTime +local IsValid = IsValid + + +function ENT:Initialize() + self:SetRenderMode(RENDERMODE_TRANSALPHA) + self:DrawShadow(false) + self:SetModel("models/hunter/plates/plate1x1.mdl") + self:SetMaterial("models/effects/vol_light001") + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + local phys = self:GetPhysicsObject() + if IsValid(phys) then + phys:EnableMotion(false) + end + self.heldby = 0 +end + +function ENT:PhysicsUpdate(phys) + if self.heldby <= 0 then + phys:Sleep() + end +end + +local function textScreenPickup(ply, ent) + if IsValid(ent) and ent:GetClass() == "sammyservers_textscreen" then + ent.heldby = ent.heldby + 1 + end +end +hook.Add("PhysgunPickup", "3D2DTextScreensPreventTravelPickup", textScreenPickup) + +local function textScreenDrop(ply, ent) + if IsValid(ent) and ent:GetClass() == "sammyservers_textscreen" then + ent.heldby = ent.heldby - 1 + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + ent:PhysicsUpdate(phys) + end + end +end +hook.Add("PhysgunDrop", "3D2DTextScreensPreventTravelDrop", textScreenDrop) + +util.AddNetworkString("textscreens_update") +util.AddNetworkString("textscreens_download") + +function ENT:SetLine(line, text, color, size, font, rainbow) + if not text then return end + if string.sub(text, 1, 1) == "#" then + text = string.sub(text, 2) + end + if string.len(text) > 180 then + text = string.sub(text, 1, 180) .. "..." + end + + size = math.Clamp(size, 1, 100) + + font = textscreenFonts[font] ~= nil and font or 1 + + rainbow = rainbow or 0 + + self.lines = self.lines or {} + self.lines[tonumber(line)] = { + ["text"] = text, + ["color"] = color, + ["size"] = size, + ["font"] = font, + ["rainbow"] = rainbow + } +end + +local function canSendUpdate(ply, ent) + local updates = ply.TextScreenUpdates + if not updates then + updates = {} + ply.TextScreenUpdates = updates + end + + local now = CurTime() + local lastSent = updates[ent] or 0 + if lastSent > (now - 1) then + return false + end + + updates[ent] = now + return true +end + +function ENT:OnRemove() + local plys = player.GetAll() + local plyCount = #plys + + for i = 1, plyCount do + local updates = plys[i].TextScreenUpdates + if updates then updates[self] = nil end + end +end + +net.Receive("textscreens_download", function(len, ply) + if not IsValid(ply) then return end + + local ent = net.ReadEntity() + if not IsValid( ent ) then return end + if ent:GetClass() ~= "sammyservers_textscreen" then return end + + if not canSendUpdate(ply, ent) then return end + + ent:SendLines(ply) +end) + +function ENT:SendLines(ply) + if not self.lines then self.lines = {} end + + net.Start("textscreens_update") + net.WriteEntity(self) + net.WriteTable(self.lines) + + if ply then + net.Send(ply) + else + net.Broadcast() + end +end + +function ENT:Broadcast() + self:SendLines(nil) +end diff --git a/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/shared.lua b/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/shared.lua new file mode 100644 index 0000000..bc76944 --- /dev/null +++ b/addons/3d2d_textscreens/lua/entities/sammyservers_textscreen/shared.lua @@ -0,0 +1,19 @@ +ENT.Type = "anim" +ENT.Base = "base_entity" +ENT.PrintName = "SammyServers Textscreen" +ENT.Author = "SammyServers" +ENT.Spawnable = false +ENT.AdminSpawnable = false +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +function ENT:SetupDataTables() + self:NetworkVar("Bool", 0, "IsPersisted") +end + +local function textScreenCanTool(ply, trace, tool) + -- only allow textscreen, remover, and permaprops tool + if IsValid(trace.Entity) and trace.Entity:GetClass() == "sammyservers_textscreen" and tool ~= "textscreen" and tool ~= "remover" and tool ~= "permaprops" then + return false + end +end +hook.Add("CanTool", "3D2DTextScreensPreventTools", textScreenCanTool) \ No newline at end of file diff --git a/addons/3d2d_textscreens/lua/textscreens_config.lua b/addons/3d2d_textscreens/lua/textscreens_config.lua new file mode 100644 index 0000000..9060270 --- /dev/null +++ b/addons/3d2d_textscreens/lua/textscreens_config.lua @@ -0,0 +1,154 @@ +textscreenFonts = {} + +local function addFont(font, t) + if CLIENT then + t.size = 100 + surface.CreateFont(font, t) + t.size = 50 + surface.CreateFont(font .. "_MENU", t) + end + + table.insert(textscreenFonts, font) +end + +--[[ +--------------------------------------------------------------------------- +Custom fonts - requires server restart to take affect -- "Screens_" will be removed from the font name in spawnmenu +--------------------------------------------------------------------------- +--]] + +-- Default textscreens font +addFont("Coolvetica outlined", { + font = "coolvetica", + weight = 400, + antialias = false, + outline = true +}) + +addFont("Coolvetica", { + font = "coolvetica", + weight = 400, + antialias = false, + outline = false +}) + +-- Trebuchet +addFont("Screens_Trebuchet outlined", { + font = "Trebuchet MS", + weight = 400, + antialias = false, + outline = true +}) + +addFont("Screens_Trebuchet", { + font = "Trebuchet MS", + weight = 400, + antialias = false, + outline = false +}) + +-- Arial +addFont("Screens_Arial outlined", { + font = "Arial", + weight = 600, + antialias = false, + outline = true +}) + +addFont("Screens_Arial", { + font = "Arial", + weight = 600, + antialias = false, + outline = false +}) + +-- Roboto Bk +addFont("Screens_Roboto outlined", { + font = "Roboto Bk", + weight = 400, + antialias = false, + outline = true +}) + +addFont("Screens_Roboto", { + font = "Roboto Bk", + weight = 400, + antialias = false, + outline = false +}) + +-- Helvetica +addFont("Screens_Helvetica outlined", { + font = "Helvetica", + weight = 400, + antialias = false, + outline = true +}) + +addFont("Screens_Helvetica", { + font = "Helvetica", + weight = 400, + antialias = false, + outline = false +}) + +-- akbar +addFont("Screens_Akbar outlined", { + font = "akbar", + weight = 400, + antialias = false, + outline = true +}) + +addFont("Screens_Akbar", { + font = "akbar", + weight = 400, + antialias = false, + outline = false +}) + +-- csd +addFont("Screens_csd outlined", { + font = "csd", + weight = 400, + antialias = false, + outline = true +}) + +addFont("Screens_csd", { + font = "csd", + weight = 400, + antialias = false, + outline = false +}) + +if CLIENT then + + local function addFonts(path) + local files, folders = file.Find("resource/fonts/" .. path .. "*", "MOD") + + for k, v in ipairs(files) do + if string.GetExtensionFromFilename(v) == "ttf" then + local font = string.StripExtension(v) + if table.HasValue(textscreenFonts, "Screens_" .. font) then continue end +print("-- " .. font .. "\n" .. [[ +addFont("Screens_ ]] .. font .. [[", { + font = font, + weight = 400, + antialias = false, + outline = true +}) + ]]) + end + end + + for k, v in ipairs(folders) do + addFonts(path .. v .. "/") + end + end + + concommand.Add("get_fonts", function(ply) + addFonts("") + end) + +end diff --git a/addons/3d2d_textscreens/lua/weapons/gmod_tool/stools/textscreen.lua b/addons/3d2d_textscreens/lua/weapons/gmod_tool/stools/textscreen.lua new file mode 100644 index 0000000..45ff6c3 --- /dev/null +++ b/addons/3d2d_textscreens/lua/weapons/gmod_tool/stools/textscreen.lua @@ -0,0 +1,413 @@ +TOOL.Category = "Construction" +TOOL.Name = "#tool.textscreen.name" +TOOL.Command = nil +TOOL.ConfigName = "" +local textBox = {} +local lineLabels = {} +local labels = {} +local sliders = {} +local rainbowCheckboxes = {} +local textscreenFonts = textscreenFonts +local rainbow_enabled = cvars.Number("ss_enable_rainbow", 1) +local max_characters = cvars.Number("ss_max_characters", 0) + +for i = 1, 5 do + TOOL.ClientConVar["text" .. i] = "" + TOOL.ClientConVar["size" .. i] = 20 + TOOL.ClientConVar["r" .. i] = 255 + TOOL.ClientConVar["g" .. i] = 255 + TOOL.ClientConVar["b" .. i] = 255 + TOOL.ClientConVar["a" .. i] = 255 + TOOL.ClientConVar["font" .. i] = 1 + TOOL.ClientConVar["rainbow" .. i] = 0 +end + +cleanup.Register("textscreens") + +if (CLIENT) then + TOOL.Information = { + { name = "left" }, + { name = "right" }, + { name = "reload" }, + } + -- Add default english language strings here, in case no localisation exists + language.Add("tool.textscreen.name", "3D2D Textscreen") + language.Add("tool.textscreen.desc", "Create a textscreen with multiple lines, font colours and sizes.") + language.Add("tool.textscreen.left", "Spawn a textscreen.") -- Does not work with capital T in tool. Same with right and reload. + language.Add("tool.textscreen.right", "Update textscreen with settings.") + language.Add("tool.textscreen.reload", "Copy textscreen.") + language.Add("Undone.textscreens", "Undone textscreen") + language.Add("Undone_textscreens", "Undone textscreen") + language.Add("Cleanup.textscreens", "Textscreens") + language.Add("Cleanup_textscreens", "Textscreens") + language.Add("Cleaned.textscreens", "Cleaned up all textscreens") + language.Add("Cleaned_textscreens", "Cleaned up all textscreens") + language.Add("SBoxLimit.textscreens", "You've hit the textscreen limit!") + language.Add("SBoxLimit_textscreens", "You've hit the textscreen limit!") +end + +function TOOL:LeftClick(tr) + if (tr.Entity:GetClass() == "player") then return false end + if (CLIENT) then return true end + local ply = self:GetOwner() + + if hook.Run("PlayerSpawnTextscreen", ply, tr) == false then return false end + + if not (self:GetWeapon():CheckLimit("textscreens")) then return false end + -- ensure at least 1 line of the textscreen has text before creating entity + local hasText = false + for i = 1, 5 do + local text = self:GetClientInfo("text" .. i) or "" + if text ~= "" then + hasText = true + end + end + if not hasText then return false end + local textScreen = ents.Create("sammyservers_textscreen") + textScreen:SetPos(tr.HitPos) + local angle = tr.HitNormal:Angle() + angle:RotateAroundAxis(tr.HitNormal:Angle():Right(), -90) + angle:RotateAroundAxis(tr.HitNormal:Angle():Forward(), 90) + textScreen:SetAngles(angle) + textScreen:Spawn() + textScreen:Activate() + + undo.Create("textscreens") + undo.AddEntity(textScreen) + undo.SetPlayer(ply) + undo.Finish() + ply:AddCount("textscreens", textScreen) + ply:AddCleanup("textscreens", textScreen) + + for i = 1, 5 do + local txt = self:GetClientInfo("text" .. i) or "" + textScreen:SetLine( + i, -- Line + max_characters ~= 0 and string.Left(txt, max_characters) or txt, -- text + Color( -- Color + tonumber(self:GetClientInfo("r" .. i)) or 255, + tonumber(self:GetClientInfo("g" .. i)) or 255, + tonumber(self:GetClientInfo("b" .. i)) or 255, + tonumber(self:GetClientInfo("a" .. i)) or 255 + ), + tonumber(self:GetClientInfo("size" .. i)) or 20, + -- font + tonumber(self:GetClientInfo("font" .. i)) or 1, + + rainbow_enabled == 1 and tonumber(self:GetClientInfo("rainbow" .. i)) or 0 + ) + end + + return true +end + +function TOOL:RightClick(tr) + if (tr.Entity:GetClass() == "player") then return false end + if (CLIENT) then return true end + local traceEnt = tr.Entity + + if (IsValid(traceEnt) and traceEnt:GetClass() == "sammyservers_textscreen") then + for i = 1, 5 do + local txt = tostring(self:GetClientInfo("text" .. i)) + traceEnt:SetLine( + i, -- Line + max_characters ~= 0 and string.Left(txt, max_characters) or txt, -- text + Color( -- Color + tonumber(self:GetClientInfo("r" .. i)) or 255, + tonumber(self:GetClientInfo("g" .. i)) or 255, + tonumber(self:GetClientInfo("b" .. i)) or 255, + tonumber(self:GetClientInfo("a" .. i)) or 255 + ), + tonumber(self:GetClientInfo("size" .. i)) or 20, + -- font + tonumber(self:GetClientInfo("font" .. i)) or 1, + + rainbow_enabled and tonumber(self:GetClientInfo("rainbow" .. i)) or 0 + ) + end + + traceEnt:Broadcast() + + return true + end +end + +function TOOL:Reload(tr) + if (SERVER) then return true end + local traceEnt = tr.Entity + if (not isentity(traceEnt) or traceEnt:GetClass() ~= "sammyservers_textscreen") then return false end + + for i = 1, 5 do + local linedata = traceEnt.lines[i] + RunConsoleCommand("textscreen_r" .. i, linedata.color.r) + RunConsoleCommand("textscreen_g" .. i, linedata.color.g) + RunConsoleCommand("textscreen_b" .. i, linedata.color.b) + RunConsoleCommand("textscreen_a" .. i, linedata.color.a) + RunConsoleCommand("textscreen_size" .. i, linedata.size) + RunConsoleCommand("textscreen_text" .. i, linedata.text) + RunConsoleCommand("textscreen_font" .. i, linedata.font) + RunConsoleCommand("textscreen_rainbow" .. i, linedata.rainbow) + end + + return true +end + +local conVarsDefault = TOOL:BuildConVarList() + +function TOOL.BuildCPanel(CPanel) + local logo = vgui.Create("DImage", CPanel) + logo:SetSize(267, 134) + logo:SetImage("textscreens/logo.png") + CPanel:AddItem(logo) + + CPanel:AddControl("Header", { + Text = "#tool.textscreen.name", + Description = "#tool.textscreen.desc" + }) + + local function TrimFontName(fontnum) + return string.Left(textscreenFonts[fontnum], 8) == "Screens_" and string.TrimLeft(textscreenFonts[fontnum], "Screens_") or textscreenFonts[fontnum] + end + + local changefont + local fontnum = textscreenFonts[GetConVar("textscreen_font1"):GetInt()] ~= nil and GetConVar("textscreen_font1"):GetInt() or 1 + + cvars.AddChangeCallback("textscreen_font1", function(convar_name, value_old, value_new) + fontnum = textscreenFonts[tonumber(value_new)] ~= nil and tonumber(value_new) or 1 + local font = TrimFontName(fontnum) + changefont:SetText("Change font (" .. font .. ")") + end) + + local function ResetFont(lines, text) + if #lines >= 5 then + fontnum = 1 + for i = 1, 5 do + RunConsoleCommand("textscreen_font" .. i, 1) + end + end + for k, i in pairs(lines) do + if text then + RunConsoleCommand("textscreen_text" .. i, "") + labels[i]:SetText("") + end + labels[i]:SetFont(textscreenFonts[fontnum] .. "_MENU") + end + end + + resetall = vgui.Create("DButton", resetbuttons) + resetall:SetSize(100, 25) + resetall:SetText("Reset all") + + resetall.DoClick = function() + local menu = DermaMenu() + + menu:AddOption("Reset colors", function() + for i = 1, 5 do + RunConsoleCommand("textscreen_r" .. i, 255) + RunConsoleCommand("textscreen_g" .. i, 255) + RunConsoleCommand("textscreen_b" .. i, 255) + RunConsoleCommand("textscreen_a" .. i, 255) + end + end) + + menu:AddOption("Reset sizes", function() + for i = 1, 5 do + RunConsoleCommand("textscreen_size" .. i, 20) + sliders[i]:SetValue(20) + labels[i]:SetFont(textscreenFonts[fontnum] .. "_MENU") + end + end) + + menu:AddOption("Reset textboxes", function() + for i = 1, 5 do + RunConsoleCommand("textscreen_text" .. i, "") + textBox[i]:SetValue("") + end + end) + + menu:AddOption("Reset fonts", function() + ResetFont({1, 2, 3, 4, 5}, false) + end) + + if rainbow_enabled == 1 then + menu:AddOption("Reset rainbow", function() + for i = 1, 5 do + rainbowCheckboxes[i]:SetValue(0) + end + end) + end + + menu:AddOption("Reset everything", function() + for i = 1, 5 do + RunConsoleCommand("textscreen_r" .. i, 255) + RunConsoleCommand("textscreen_g" .. i, 255) + RunConsoleCommand("textscreen_b" .. i, 255) + RunConsoleCommand("textscreen_a" .. i, 255) + RunConsoleCommand("textscreen_size" .. i, 20) + sliders[i]:SetValue(20) + RunConsoleCommand("textscreen_text" .. i, "") + RunConsoleCommand("textscreen_font" .. i, 1) + textBox[i]:SetValue("") + if rainbow_enabled == 1 then + rainbowCheckboxes[i]:SetValue(0) + end + end + ResetFont({1, 2, 3, 4, 5}, true) + end) + + menu:Open() + end + + CPanel:AddItem(resetall) + resetline = vgui.Create("DButton") + resetline:SetSize(100, 25) + resetline:SetText("Reset line") + + resetline.DoClick = function() + local menu = DermaMenu() + + for i = 1, 5 do + menu:AddOption("Reset line " .. i, function() + RunConsoleCommand("textscreen_r" .. i, 255) + RunConsoleCommand("textscreen_g" .. i, 255) + RunConsoleCommand("textscreen_b" .. i, 255) + RunConsoleCommand("textscreen_a" .. i, 255) + RunConsoleCommand("textscreen_size" .. i, 20) + sliders[i]:SetValue(20) + RunConsoleCommand("textscreen_text" .. i, "") + textBox[i]:SetValue("") + ResetFont({i}, true) + end) + end + + menu:AddOption("Reset all lines", function() + for i = 1, 5 do + RunConsoleCommand("textscreen_r" .. i, 255) + RunConsoleCommand("textscreen_g" .. i, 255) + RunConsoleCommand("textscreen_b" .. i, 255) + RunConsoleCommand("textscreen_a" .. i, 255) + RunConsoleCommand("textscreen_size" .. i, 20) + sliders[i]:SetValue(20) + RunConsoleCommand("textscreen_text" .. i, "") + RunConsoleCommand("textscreen_font" .. i, 1) + textBox[i]:SetValue("") + end + ResetFont({1, 2, 3, 4, 5}, true) + end) + + menu:Open() + end + + CPanel:AddItem(resetline) + + -- Change font + changefont = vgui.Create("DButton") + changefont:SetSize(100, 25) + changefont:SetText("Change font (" .. TrimFontName(fontnum) .. ")" ) + + changefont.DoClick = function() + local menu = DermaMenu() + + for i = 1, #textscreenFonts do + local font = TrimFontName(i) + menu:AddOption(font, function() + fontnum = i + for o = 1, 5 do + RunConsoleCommand("textscreen_font" .. o, i) + labels[o]:SetFont(textscreenFonts[fontnum] .. "_MENU") + end + changefont:SetText("Change font (" .. font .. ")") + end) + end + + menu:Open() + end + + CPanel:AddItem(changefont) + + CPanel:AddControl("ComboBox", { + MenuButton = 1, + Folder = "textscreen", + Options = { + ["#preset.default"] = conVarsDefault + }, + CVars = table.GetKeys(conVarsDefault) + }) + + for i = 1, 5 do + lineLabels[i] = CPanel:AddControl("Label", { + Text = "Line " .. i, + Description = "Line " .. i + }) + + lineLabels[i]:SetFont("Default") + + CPanel:AddControl("Color", { + Label = "Line " .. i .. " font color", + Red = "textscreen_r" .. i, + Green = "textscreen_g" .. i, + Blue = "textscreen_b" .. i, + Alpha = "textscreen_a" .. i, + ShowHSV = 1, + ShowRGB = 1, + Multiplier = 255 + }) + + if rainbow_enabled == 1 then + rainbowCheckboxes[i] = vgui.Create("DCheckBoxLabel") + rainbowCheckboxes[i]:SetText("Rainbow Text") + rainbowCheckboxes[i]:SetTextColor(Color(0,0,0,255)) + rainbowCheckboxes[i]:SetConVar("textscreen_rainbow" .. i) + rainbowCheckboxes[i]:SetTooltip("Enable for rainbow text") + rainbowCheckboxes[i]:SetValue(GetConVar("textscreen_rainbow" .. i):GetInt()) + CPanel:AddItem(rainbowCheckboxes[i]) + end + + sliders[i] = vgui.Create("DNumSlider") + sliders[i]:SetText("Font size") + sliders[i]:SetMinMax(20, 100) + sliders[i]:SetDecimals(0) + sliders[i]:SetValue(GetConVar("textscreen_size" .. i)) + sliders[i]:SetConVar("textscreen_size" .. i) + + CPanel:AddItem(sliders[i]) + textBox[i] = vgui.Create("DTextEntry") + textBox[i]:SetUpdateOnType(true) + textBox[i]:SetEnterAllowed(true) + textBox[i]:SetConVar("textscreen_text" .. i) + textBox[i]:SetValue(GetConVar("textscreen_text" .. i):GetString()) + + textBox[i].OnTextChanged = function() + labels[i]:SetText(textBox[i]:GetValue()) + end + + if max_characters ~= 0 then + textBox[i].AllowInput = function() + if string.len(textBox[i]:GetValue()) >= max_characters then return true end + end + end + + CPanel:AddItem(textBox[i]) + + labels[i] = CPanel:AddControl("Label", { + Text = #GetConVar("textscreen_text" .. i):GetString() >= 1 and GetConVar("textscreen_text" .. i):GetString() or "Line " .. i, + Description = "Line " .. i + }) + + labels[i]:SetFont(textscreenFonts[fontnum] .. "_MENU") + labels[i]:SetAutoStretchVertical(true) + labels[i]:SetDisabled(true) + labels[i]:SetHeight(50) + + labels[i].Think = function() + labels[i]:SetColor( + Color( + GetConVar("textscreen_r" .. i):GetInt(), + GetConVar("textscreen_g" .. i):GetInt(), + GetConVar("textscreen_b" .. i):GetInt(), + GetConVar("textscreen_a" .. i):GetInt() + ) + ) + end + end +end diff --git a/addons/3d2d_textscreens/materials/textscreens/logo.png b/addons/3d2d_textscreens/materials/textscreens/logo.png new file mode 100644 index 0000000..6a7b4bb Binary files /dev/null and b/addons/3d2d_textscreens/materials/textscreens/logo.png differ diff --git a/addons/3d2d_textscreens/resource/localization/en/3d2dtextscreens_tool.properties b/addons/3d2d_textscreens/resource/localization/en/3d2dtextscreens_tool.properties new file mode 100644 index 0000000..cc75ede --- /dev/null +++ b/addons/3d2d_textscreens/resource/localization/en/3d2dtextscreens_tool.properties @@ -0,0 +1,14 @@ + +tool.textscreen.name=3D2D Textscreen +tool.textscreen.desc=Create a textscreen with multiple lines, font colours and sizes. +tool.textscreen.left=Spawn a textscreen. +tool.textscreen.right=Update textscreen with settings. +tool.textscreen.reload=Copy textscreen. +Undone.textscreens=Undone textscreen +Undone_textscreens=Undone textscreen +Cleanup.textscreens=Textscreens +Cleanup_textscreens=Textscreens +Cleaned.textscreens=Cleaned up all textscreens +Cleaned_textscreens=Cleaned up all textscreens +SBoxLimit.textscreens=You've hit the textscreen limit! +SBoxLimit_textscreens=You've hit the textscreen limit! \ No newline at end of file diff --git a/addons/3d2d_textscreens/resource/localization/es/3d2dtextscreens_tool.properties b/addons/3d2d_textscreens/resource/localization/es/3d2dtextscreens_tool.properties new file mode 100644 index 0000000..2a1497f --- /dev/null +++ b/addons/3d2d_textscreens/resource/localization/es/3d2dtextscreens_tool.properties @@ -0,0 +1,14 @@ + +tool.textscreen.name=Pantalla de texto 3D2D +tool.textscreen.desc=Crear a Pantalla de texto con múltiples líneas, colores de fuente y tamaños. +tool.textscreen.left=Aparecer a Pantalla de texto. +tool.textscreen.right=Actualizar Pantalla de texto with settings. +tool.textscreen.reload=Dupdo Pantalla de texto. +Undone.textscreens=Deshecho Pantalla de texto +Undone_textscreens=Deshecho Pantalla de texto +Cleanup.textscreens=Pantalla de texto +Cleanup_textscreens=Pantalla de texto +Cleaned.textscreens=Limpió todas las pantallas de texto +Cleaned_textscreens=Limpió todas las pantallas de texto +SBoxLimit.textscreens=¡Has alcanzado el límite de la pantalla de texto! +SBoxLimit_textscreens=¡Has alcanzado el límite de la pantalla de texto! \ No newline at end of file diff --git a/addons/3d2d_textscreens/resource/localization/pl/3d2dtextscreens_tool.properties b/addons/3d2d_textscreens/resource/localization/pl/3d2dtextscreens_tool.properties new file mode 100644 index 0000000..26ab9e1 --- /dev/null +++ b/addons/3d2d_textscreens/resource/localization/pl/3d2dtextscreens_tool.properties @@ -0,0 +1,14 @@ + +tool.textscreen.name=3D2D Textscreen +tool.textscreen.desc=Utwórz ekran tekstowy z wieloma liniami, kolorami czcionek i rozmiarami. +tool.textscreen.left=Utwórz ekran tekstowy. +tool.textscreen.right=Uaktualnij ekran tekstowy z wybranymi ustawieniami. +tool.textscreen.reload=Skopiuj ekran tekstowy. +Undone.textscreens=Cofnij utworzenie ekranu tekstowego +Undone_textscreens=Cofnij utworzenie ekranu tekstowego +Cleanup.textscreens=Ekrany tekstowe +Cleanup_textscreens=Ekrany tekstowe +Cleaned.textscreens=Usunięto wszystkie ekrany tekstowe +Cleaned_textscreens=Usunięto wszystkie ekrany tekstowe +SBoxLimit.textscreens=Przekroczyłeś limit ekranów tekstowych! +SBoxLimit_textscreens=Przekroczyłeś limit ekranów tekstowych! diff --git a/addons/3d2d_textscreens/resource/localization/ru/3d2dtextscreens_tool.properties b/addons/3d2d_textscreens/resource/localization/ru/3d2dtextscreens_tool.properties new file mode 100644 index 0000000..e2d630f --- /dev/null +++ b/addons/3d2d_textscreens/resource/localization/ru/3d2dtextscreens_tool.properties @@ -0,0 +1,14 @@ + +tool.textscreen.name=3D2D \u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0020\u044d\u043a\u0440\u0430\u043d +tool.textscreen.desc=\u0421\u043e\u0437\u0434\u0430\u0451\u0442\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0020\u044d\u043a\u0440\u0430\u043d\u0020\u0441\u0020\u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438\u0020\u043b\u0438\u043d\u0438\u044f\u043c\u0438\u002c\u0020\u0446\u0432\u0435\u0442\u0430\u043c\u0438\u0020\u0438\u0020\u0440\u0430\u0437\u043c\u0435\u0440\u0430\u043c\u0438\u0020\u0448\u0440\u0438\u0444\u0442\u0430\u002e +tool.textscreen.left=\u0421\u043e\u0437\u0434\u0430\u0442\u044c\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0020\u044d\u043a\u0440\u0430\u043d\u002e +tool.textscreen.right=\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0020\u044d\u043a\u0440\u0430\u043d\u0020\u0438\u0020\u0435\u0433\u043e\u0020\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u002e +tool.textscreen.reload=\u0421\u043a\u043e\u043f\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0020\u044d\u043a\u0440\u0430\u043d\u002e +Undone.textscreens=\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0020\u044d\u043a\u0440\u0430\u043d\u0020\u043e\u0442\u043c\u0435\u043d\u0451\u043d +Undone_textscreens=\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0439\u0020\u044d\u043a\u0440\u0430\u043d\u0020\u043e\u0442\u043c\u0435\u043d\u0451\u043d +Cleanup.textscreens=\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435\u0020\u044d\u043a\u0440\u0430\u043d\u044b +Cleanup_textscreens=\u0422\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435\u0020\u044d\u043a\u0440\u0430\u043d\u044b +Cleaned.textscreens=\u0423\u0431\u0440\u0430\u043d\u044b\u0020\u0432\u0441\u0435\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435\u0020\u044d\u043a\u0440\u0430\u043d\u044b +Cleaned_textscreens=\u0423\u0431\u0440\u0430\u043d\u044b\u0020\u0432\u0441\u0435\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0435\u0020\u044d\u043a\u0440\u0430\u043d\u044b +SBoxLimit.textscreens=\u0412\u044b\u0020\u0434\u043e\u0441\u0442\u0438\u0433\u043b\u0438\u0020\u043b\u0438\u043c\u0438\u0442\u0430\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0445\u0020\u044d\u043a\u0440\u0430\u043d\u043e\u0432\u0021 +SBoxLimit_textscreens=\u0412\u044b\u0020\u0434\u043e\u0441\u0442\u0438\u0433\u043b\u0438\u0020\u043b\u0438\u043c\u0438\u0442\u0430\u0020\u0442\u0435\u043a\u0441\u0442\u043e\u0432\u044b\u0445\u0020\u044d\u043a\u0440\u0430\u043d\u043e\u0432\u0021 \ No newline at end of file diff --git a/addons/3d2d_textscreens/resource/localization/tr/3d2dtextscreens_tool.properties b/addons/3d2d_textscreens/resource/localization/tr/3d2dtextscreens_tool.properties new file mode 100644 index 0000000..12bd8ec --- /dev/null +++ b/addons/3d2d_textscreens/resource/localization/tr/3d2dtextscreens_tool.properties @@ -0,0 +1,14 @@ + +tool.textscreen.name=3D2D Metin Ekranı +tool.textscreen.desc=Birden çok satır, yazı tipi rengi ve boyutu olan bir metin ekranı oluşturun. +tool.textscreen.left=Bir metin ekranı oluştur. +tool.textscreen.right=Metin ekranını ayarlarla güncelleyin. +tool.textscreen.reload=Metin ekranını kopyala. +Undone.textscreens=Bitmemiş metin ekranı +Undone_textscreens=Bitmemiş metin ekranı +Cleanup.textscreens=Metin Ekranları +Cleanup_textscreens=Metin Ekranları +Cleaned.textscreens=Tüm metin ekranlarını temizle +Cleaned_textscreens=Tüm metin ekranlarını temizle +SBoxLimit.textscreens=Metin ekranı sınırına ulaştınız! +SBoxLimit_textscreens=Metin ekranı sınırına ulaştınız! diff --git a/addons/_libnyx/lua/autorun/libnyx.lua b/addons/_libnyx/lua/autorun/libnyx.lua new file mode 100644 index 0000000..6192d3f --- /dev/null +++ b/addons/_libnyx/lua/autorun/libnyx.lua @@ -0,0 +1,156 @@ +-- libNyx by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw + +-- lua/autorun/libnyx_loader.lua +libNyx = libNyx or {} + +local function read_local_version() + local p1,p2 = "VERSION","libnyx/VERSION" + local v = file.Exists(p1,"GAME") and file.Read(p1,"GAME") or file.Exists(p1,"LUA") and file.Read(p1,"LUA") or file.Exists(p2,"LUA") and file.Read(p2,"LUA") or file.Exists(p2,"GAME") and file.Read(p2,"GAME") or "" + v = tostring(v or ""):gsub("[\r\n]","") + if v == "" then v = "0.0.0" end + return v +end + +libNyx.Version = libNyx.Version or read_local_version() + +local RAW_VERSION_URL = "https://raw.githubusercontent.com/maryblackfild/libnyx/main/VERSION" +local RAW_LOADER_URL = "https://raw.githubusercontent.com/maryblackfild/libnyx/main/lua/autorun/libnyx_loader.lua" +local HOMEPAGE = "https://github.com/maryblackfild/libnyx" + +local function norm(v) v = tostring(v or "") return (v:sub(1,1)=="v") and v:sub(2) or v end +local function say(kind,msg) + local h = Color(120,200,255) + local c = kind=="ok" and Color(120,220,120) or kind=="warn" and Color(255,220,120) or kind=="err" and Color(255,120,120) or Color(200,200,210) + MsgC(h,"[libNyx] ",c,msg,"\n") +end + +local function do_update_check() + say("info","Checking for updates…") + http.Fetch(RAW_VERSION_URL, function(body) + local remote = tostring(body or ""):gsub("[\r\n]","") + if remote=="" then return say("err","Update check failed.") end + local a,b = norm(libNyx.Version), norm(remote) + if a==b then + say("ok",("Up-to-date ✓ (latest: %s)"):format(remote)) + else + say("warn",("Update available ✱ installed %s → latest %s"):format(libNyx.Version, remote)) + say("info","Get it: "..HOMEPAGE) + end + end, function() + http.Fetch(RAW_LOADER_URL, function(body) + local remote = tostring(body or ""):match('libNyx%.Version%s*=%s*["\']([%w%._%-]+)["\']') + if not remote or remote=="" then return say("err","Update check failed.") end + local a,b = norm(libNyx.Version), norm(remote) + if a==b then + say("ok",("Up-to-date ✓ (latest: %s)"):format(remote)) + else + say("warn",("Update available ✱ installed %s → latest %s"):format(libNyx.Version, remote)) + say("info","Get it: "..HOMEPAGE) + end + end, function() say("err","Update check failed.") end) + end) +end + +if SERVER then + AddCSLuaFile("libnyx/lib/rndx.lua") + AddCSLuaFile("libnyx/lib/libnyx_components.lua") + AddCSLuaFile("libnyx/lib/libnyx_liquidglass.lua") + AddCSLuaFile("libnyx/lib/libnyx_maindemo.lua") + timer.Simple(0, function() say("info",("Loaded v%s (server)"):format(libNyx.Version)) do_update_check() end) + return +end + +local function hasFont(f) + local n="__nyx_font_test" + surface.CreateFont(n,{font=f,size=16,weight=500,extended=true}) + surface.SetFont(n) + local w,h=surface.GetTextSize("Aa") + return (w or 0)>0 and (h or 0)>0 +end + +local function precreate_alias_fonts() + local base = hasFont("Manrope") and "Manrope" or "Tahoma" + for sz=10,200 do + local name=("libNyx.%s.%d"):format(base, sz) + surface.CreateFont(name,{font=base,size=sz,weight=(sz>=28) and 500 or 400,extended=true}) + end + for sz=10,60 do + local name=("libNyx.UI.%d"):format(sz) + surface.CreateFont(name,{font=base,size=sz,weight=(sz>=28) and 500 or 400,extended=true}) + end +end + +local boot = {} +local function include_all_once() + if not boot.rndx then + libNyx.rndx = include("libnyx/lib/rndx.lua") + _G.RNDX = _G.RNDX or libNyx.rndx + boot.rndx = true + end + if not boot.components then + include("libnyx/lib/libnyx_components.lua") + boot.components = true + end + if not boot.demo then + include("libnyx/lib/libnyx_maindemo.lua") + boot.demo = true + end + if not boot.liquid then + include("libnyx/lib/libnyx_liquidglass.lua") + boot.liquid = true + end +end + +local function ready_gate_try() + local UI = _G.libNyx and _G.libNyx.UI + local ok = UI and UI.Draw and UI.Components and UI.Components.CreateSlider and RNDX + if not ok then return false end + return true +end + +local function run_after_include() + precreate_alias_fonts() + if _G.libNyx and _G.libNyx.UI and _G.libNyx.UI.InstallGlobalMenuSkin then _G.libNyx.UI.InstallGlobalMenuSkin() end + if _G.libNyx and _G.libNyx.UI and _G.libNyx.UI.InstallGlobalNotificationSkin then _G.libNyx.UI.InstallGlobalNotificationSkin() end +end + +local trying=0 +local function wait_until_ready() + trying = trying + 1 + if ready_gate_try() then + libNyx.Ready = true + return + end + if trying < 240 then timer.Simple(0, wait_until_ready) else say("warn","Init gate timed out; some UI may be unavailable.") end +end + +local function client_bootstrap() + include_all_once() + run_after_include() + wait_until_ready() +end + +local function gamemode_init_bridge() + if libNyx.__booted then return end + libNyx.__booted = true + client_bootstrap() +end + +hook.Add("OnGamemodeLoaded","libNyx.Loader.GMInit",gamemode_init_bridge) +hook.Add("Initialize","libNyx.Loader.Init",gamemode_init_bridge) +hook.Add("InitPostEntity","libNyx.Loader.InitPostEntity",function() if not libNyx.Ready then client_bootstrap() end end) +hook.Add("OnReloaded","libNyx.Loader.Reload",function() + boot = {} + libNyx.Ready = false + client_bootstrap() +end) + +timer.Simple(0, function() + if not libNyx.__booted then gamemode_init_bridge() end + say("info",("Loaded v%s (client)"):format(libNyx.Version)) + do_update_check() +end) + +-- libNyx by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw diff --git a/addons/_libnyx/lua/libnyx/VERSION b/addons/_libnyx/lua/libnyx/VERSION new file mode 100644 index 0000000..a3df0a6 --- /dev/null +++ b/addons/_libnyx/lua/libnyx/VERSION @@ -0,0 +1 @@ +0.8.0 diff --git a/addons/_libnyx/lua/libnyx/lib/libnyx_components.lua b/addons/_libnyx/lua/libnyx/lib/libnyx_components.lua new file mode 100644 index 0000000..d2e0b95 --- /dev/null +++ b/addons/_libnyx/lua/libnyx/lib/libnyx_components.lua @@ -0,0 +1,3205 @@ +-- libNyx by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw + +if SERVER then + AddCSLuaFile() + return +end + +local libNyx = libNyx or {} +libNyx.UI = libNyx.UI or {} +_G.libNyx = libNyx + +local RNDX = include("libnyx/lib/rndx.lua") + +local BASE_W, BASE_H = 1920, 1080 +local cvarScale = CreateClientConVar("cl_libnyx_ui_scale", "0", true, false, "UI scale multiplier (0 = auto).", 0, 2) +local cvarScaleMode = CreateClientConVar("cl_libnyx_ui_scalemode", "min", true, false, "Auto scale mode: min | h | w | avg") + +local _scale, _sx, _sy = 1.0, 1.0, 1.0 + +local function ComputeScale() + _sx = math.max(0.001, ScrW() / BASE_W) + _sy = math.max(0.001, ScrH() / BASE_H) + local manual = tonumber(cvarScale:GetString()) or 0 + if manual > 0 then + _scale = math.Clamp(manual, 0.50, 2.00) + return + end + local mode = string.lower(tostring(cvarScaleMode:GetString() or "min")) + local s + if mode == "h" then s = _sy + elseif mode == "w" then s = _sx + elseif mode == "avg" then s = (_sx + _sy) * 0.5 + else s = math.min(_sx, _sy) + end + _scale = math.Clamp(s, 0.75, 1.75) +end + +local function RecomputeStyle() + local S = libNyx.UI.Style + _scale = _scale or 1 + S.radius = math.floor(12 * _scale) + S.padding = math.floor(14 * _scale) + S.iconSize = math.floor(24 * _scale) + S.btnHeight = math.floor(44 * _scale) + S.rowHeight = math.floor(82 * _scale) + S.shadowSpread = math.floor(22 * _scale) + S.shadowIntensity = math.floor(36 * _scale) + S.strokeWidth = math.max(1, math.floor(1 * _scale)) + S.headerIndentX = math.floor(24 * _scale) + S.headerIndentY = math.floor(24 * _scale) + S.comboItemH = math.floor(34 * _scale) +end + +function libNyx.UI.Scale(n, axis) + local v = tonumber(n) or 0 + if axis == "w" then return math.floor(v * _sx) end + if axis == "h" then return math.floor(v * _sy) end + if axis == "min" then return math.floor(v * math.min(_sx, _sy)) end + if axis == "max" then return math.floor(v * math.max(_sx, _sy)) end + return math.floor(v * _scale) +end +function libNyx.UI.ScaleW(n) return libNyx.UI.Scale(n, "w") end +function libNyx.UI.ScaleH(n) return libNyx.UI.Scale(n, "h") end +function libNyx.UI.ScalePair(w, h) return libNyx.UI.ScaleW(w), libNyx.UI.ScaleH(h) end + +local function _refresh() + ComputeScale() + RecomputeStyle() +end +libNyx.UI.RecomputeScale = _refresh + +hook.Add("OnScreenSizeChanged", "libNyx.UI.Scale.refresh", _refresh) +cvars.AddChangeCallback("cl_libnyx_ui_scale", function() timer.Simple(0, _refresh) end, "libNyx.UI.Scale.cb1") +cvars.AddChangeCallback("cl_libnyx_ui_scalemode", function() timer.Simple(0, _refresh) end, "libNyx.UI.Scale.cb2") + +local c_mode = CreateClientConVar("cl_gsims_glass_mode", "1", true, false, "", 0, 2) +local c_budget = CreateClientConVar("cl_gsims_glass_budget", "6", true, false, "", 0, 128) +local c_minarea = CreateClientConVar("cl_gsims_glass_minarea", "16384", true, false, "", 0, 524288) +local c_str = CreateClientConVar("cl_gsims_glass_strength", "3", true, false, "", 1, 8) + +local function nowMode() return math.floor(c_mode:GetFloat()) end +local function nowBudget() return math.floor(c_budget:GetFloat()) end +local function nowMinArea() return math.floor(c_minarea:GetFloat()) end +local function nowStrength() return math.Clamp(math.floor(c_str:GetFloat()), 1, 8) end + +local function col(a,b,c,d) return Color(a or 255,b or 255,c or 255,d or 255) end + +local function getRNDX() + return rawget(_G,"rndx") or rawget(_G,"RNDX") or rawget(_G,"Rdx") or rawget(_G,"RDX") +end + +local function callRNDXBlur(x,y,w,h,str) + local R = getRNDX() + if not R then return false end + if isfunction(R.DrawBlurRect) then R.DrawBlurRect(x,y,w,h,str) return true end + if istable(R.Draw) and isfunction(R.Draw.BlurRect) then R.Draw.BlurRect(x,y,w,h,str) return true end + if isfunction(R.BlurRect) then R.BlurRect(x,y,w,h,str) return true end + if istable(R.UI) and isfunction(R.UI.BlurRect) then R.UI.BlurRect(x,y,w,h,str) return true end + if isfunction(R.Blur) then R.Blur(x,y,w,h,str) return true end + return false +end + +local G = {} +G.fid = -1 +G.used = 0 + +function G:beginFrame() + local f = FrameNumber() + if f ~= self.fid then + self.fid = f + self.used = 0 + end +end + +local function rb(x,y,w,h,r,c) draw.RoundedBox(r, x,y,w,h, c) end +local function ro(x,y,w,h,r,c,wid) + surface.SetDrawColor(c) + surface.DrawOutlinedRect(x,y,w,h, wid) +end + +function G:fake(x,y,w,h,opt) + local r = (opt and opt.radius) or (libNyx.UI.Style.radius or 6) + local fill = (opt and opt.fill) or (libNyx.UI.Style.glassFill or col(255,255,255,14)) + local stroke = (opt and opt.stroke ~= false) and ((opt and opt.strokeColor) or (libNyx.UI.Style.glassStroke or col(255,255,255,22))) + rb(x,y,w,h,r,fill) + if stroke then ro(x,y,w,h,r,stroke, libNyx.UI.Style.strokeWidth or 1) end +end + +function G:maskBegin(x,y,w,h,r) + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilWriteMask(255) + render.SetStencilTestMask(255) + render.SetStencilReferenceValue(1) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_REPLACE) + rb(x,y,w,h,r, col(255,255,255,255)) + render.SetStencilCompareFunction(STENCIL_EQUAL) + render.SetStencilPassOperation(STENCIL_KEEP) +end + +function G:maskEnd() + render.SetStencilEnable(false) +end + +function G:blur(x,y,w,h,opt) + local r = (opt and opt.radius) or (libNyx.UI.Style.radius or 6) + local s = nowStrength() + self:maskBegin(x,y,w,h,r) + render.SetScissorRect(x, y, x + w, y + h, true) + local ok = callRNDXBlur(x,y,w,h,s) + render.SetScissorRect(0,0,0,0,false) + self:maskEnd() + local fill = (opt and opt.fill) or (libNyx.UI.Style.glassTint or col(255,255,255,8)) + local stroke = (opt and opt.stroke ~= false) and ((opt and opt.strokeColor) or (libNyx.UI.Style.glassStroke or col(255,255,255,22))) + if ok then + rb(x,y,w,h,r,fill) + if stroke then ro(x,y,w,h,r,stroke, libNyx.UI.Style.strokeWidth or 1) end + else + self:fake(x,y,w,h,opt) + end +end + +local gsims_glass_orig = Draw and Draw.Glass + +local function gsims_glass(x,y,w,h,opt) + G:beginFrame() + local m = nowMode() + if m == 0 then return G:fake(x,y,w,h,opt) end + if m == 2 then + G.used = G.used + 1 + return G:blur(x,y,w,h,opt) + end + local area = math.max(1, math.floor(w) * math.floor(h)) + local allow = G.used < nowBudget() + local big = area >= nowMinArea() + local force = opt and opt.forceBlur == true + if force or (allow and big) then + G.used = G.used + 1 + return G:blur(x,y,w,h,opt) + else + return G:fake(x,y,w,h,opt) + end +end + +if Nyx and Nyx.UI and Nyx.UI.Draw then + Nyx.UI.Draw.Glass = gsims_glass +end + +concommand.Add("gsims_glass", function(p,cmd,args) + local sub = string.lower(args[1] or "") + if sub == "mode" then + local v = tonumber(args[2] or "") or 1 + RunConsoleCommand("cl_gsims_glass_mode", tostring(math.Clamp(v,0,2))) + elseif sub == "budget" then + local v = tonumber(args[2] or "") or 6 + RunConsoleCommand("cl_gsims_glass_budget", tostring(math.max(0, math.floor(v)))) + elseif sub == "minarea" then + local v = tonumber(args[2] or "") or 16384 + RunConsoleCommand("cl_gsims_glass_minarea", tostring(math.max(0, math.floor(v)))) + elseif sub == "strength" then + local v = tonumber(args[2] or "") or 3 + RunConsoleCommand("cl_gsims_glass_strength", tostring(math.Clamp(math.floor(v),1,8))) + elseif sub == "preset" then + local v = string.lower(args[2] or "balanced") + if v == "fast" then + RunConsoleCommand("cl_gsims_glass_mode","1") + RunConsoleCommand("cl_gsims_glass_budget","2") + RunConsoleCommand("cl_gsims_glass_minarea","32768") + RunConsoleCommand("cl_gsims_glass_strength","2") + elseif v == "pretty" then + RunConsoleCommand("cl_gsims_glass_mode","1") + RunConsoleCommand("cl_gsims_glass_budget","12") + RunConsoleCommand("cl_gsims_glass_minarea","8192") + RunConsoleCommand("cl_gsims_glass_strength","4") + else + RunConsoleCommand("cl_gsims_glass_mode","1") + RunConsoleCommand("cl_gsims_glass_budget","6") + RunConsoleCommand("cl_gsims_glass_minarea","16384") + RunConsoleCommand("cl_gsims_glass_strength","3") + end + elseif sub == "show" then + local have = getRNDX() and "ok" or "miss" + local msg = string.format("[gSims] glass mode=%d budget=%d minarea=%d strength=%d used=%d rndx=%s", + nowMode(), nowBudget(), nowMinArea(), nowStrength(), G.used, have) + if chat and chat.AddText then chat.AddText(Color(160,220,255), msg) end + MsgN(msg) + end +end) + +local Style = { + bgColor = Color(10,10,14,150), + panelColor = Color(16,18,24,120), + cardColor = Color(20,22,30,130), + accentColor = Color(90,160,255), + hoverColor = Color(52,58,70,120), + textColor = Color(245,245,250), + glassFill = Color(20,24,32,110), + glassStroke = Color(255,255,255,22), + blurIntensity = 1.0, + radius = 12, + padding = 14, + iconSize = 24, + btnHeight = 44, + rowHeight = 82, + shadowSpread = 22, + shadowIntensity= 36, + strokeWidth = 1, + headerIndentX = 24, + headerIndentY = 24, + gradientAlphaRow = 120, + gradientAlphaChip = 110, + gradientAlphaBtn = 130, +} +libNyx.UI.Style = Style +libNyx.UI.RecomputeScale() + +local baseFont = "Manrope" +local function hasSystemFont(name) + local test = ("libNyx.FontCheck.%s"):format(name) + surface.CreateFont(test, {font = name, size = 12, weight = 400, extended = true}) + surface.SetFont(test) + local w, h = surface.GetTextSize("Aa") + return (w or 0) > 0 and (h or 0) > 0 +end +if not hasSystemFont(baseFont) then baseFont = "Tahoma" end + +local fontCache = {} +for sz = 10, 200 do + local key = ("libNyx.%s.%d"):format(baseFont, sz) + surface.CreateFont(key, {font = baseFont, size = sz, weight = (sz >= 28) and 500 or 400, extended = true}) + fontCache[sz] = key +end +function libNyx.UI.Font(size) + size = math.Clamp(math.floor(tonumber(size) or 14), 10, 200) + return fontCache[size] +end + +local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth") + +local GRAD_PALETTE = { + Color( 90,160,255), + Color(255,125,155), + Color(120,220,160), + Color(255,200,120), + Color(170,120,255), + Color(120,200,255), +} +local function PalettePick(key) + key = tostring(key or "") + local sum = 0 + for i = 1, #key do sum = sum + key:byte(i) end + local idx = (sum % #GRAD_PALETTE) + 1 + return GRAD_PALETTE[idx] +end + +libNyx.UI.Sounds = { + hover = "nyx_uniqueui/nyxclick_2.mp3", + click = "nyx_uniqueui/nyxclick_3.mp3", +} + +function libNyx.UI.PlayHover() + surface.PlaySound(libNyx.UI.Sounds.hover) +end + +function libNyx.UI.PlayClick() + surface.PlaySound(libNyx.UI.Sounds.click) +end + +if not libNyx.UI._sfxRedirected then + local _PlaySound = surface.PlaySound + function surface.PlaySound(snd) + if snd == "buttons/lightswitch2.wav" then + snd = (libNyx.UI.Sounds and libNyx.UI.Sounds.hover) or snd + elseif snd == "ui/buttonclick.wav" or snd == "ui/buttonclickrelease.wav" then + snd = (libNyx.UI.Sounds and libNyx.UI.Sounds.click) or snd + end + return _PlaySound(snd) + end + libNyx.UI._sfxRedirected = true +end + +libNyx.UI._nobg = libNyx.UI._nobg or {} + +local function _NoBG(p) + if not IsValid(p) then return end + if p.SetPaintBackground then p:SetPaintBackground(false) end + if p.SetPaintBackgroundEnabled then p:SetPaintBackgroundEnabled(false) end + if p.SetPaintBorderEnabled then p:SetPaintBorderEnabled(false) end + if p.SetDrawBackground then p:SetDrawBackground(false) end +end + +function libNyx.UI.AutoNoBG(root) + if not IsValid(root) then return end + _NoBG(root) + for _, ch in ipairs(root:GetChildren()) do _NoBG(ch) end + if root._libnyx_nobg_hooked then return end + local old = root.OnChildAdded + root.OnChildAdded = function(self, pnl) + if isfunction(old) then old(self, pnl) end + _NoBG(pnl) + end + root._libnyx_nobg_hooked = true +end + +libNyx.UI.Draw = {} + +function libNyx.UI.Draw.Glass(x, y, w, h, opts) + opts = opts or {} + local r = opts.radius or Style.radius + local fill = opts.fill or Style.glassFill + local stroke = opts.stroke ~= false and (opts.strokeColor or Style.glassStroke) + local flags = opts.shape and opts.shape or 0 + local blurI = tonumber(opts.blurIntensity) or Style.blurIntensity + local T = RNDX() + T.Rect(x, y, w, h):Rad(r):Blur(blurI):Draw() + RNDX.Draw(r, x, y, w, h, fill, flags) + if stroke then + RNDX.DrawOutlined(r, x, y, w, h, stroke, Style.strokeWidth, flags) + end +end + +function libNyx.UI.CreateFrame(opts) + opts = opts or {} + local Style = libNyx.UI.Style + local W = math.max(tonumber(opts.w) or 900, libNyx.UI.Scale(240)) + local H = math.max(tonumber(opts.h) or 620, libNyx.UI.Scale(200)) + local title = tostring(opts.title or "") + local f = vgui.Create("DFrame") + f:SetTitle("") + f:ShowCloseButton(false) + f:SetSizable(false) + f:MakePopup() + libNyx.UI.AutoNoBG(f) + + f._targetW = W + f._targetH = H + f._minW = math.max(2, math.floor(W * 0.12)) + f._minH = math.max(2, math.floor(H * 0.12)) + f._overshoot = 1.04 + f._durOpen = 0.22 + f._durClose = 0.18 + f._phase = "opening" + f._t0 = SysTime() + f._contentAlpha = 0 + f._title = title + local cx, cy = ScrW() * 0.5, ScrH() * 0.5 + f:SetSize(f._minW, f._minH) + f:SetPos(cx - f._minW/2, cy - f._minH/2) + + local function easeOutBack(t) local c1=1.70158 local c3=c1+1 local u=t-1 return 1 + c3*(u*u*u) + c1*(u*u) end + local function easeInCubic(t) return t*t*t end + local function expApproach(cur, tgt, spd) local k=1-math.exp(-(spd or 10)*FrameTime()) return cur+(tgt-cur)*k end + local function applyContentAlpha(self, a) + a = math.Clamp(math.floor(a or 0), 0, 255) + for _, ch in ipairs(self:GetChildren()) do if IsValid(ch) then ch:SetAlpha(a) end end + end + local oldAdd = f.OnChildAdded + function f:OnChildAdded(pnl) + if oldAdd then oldAdd(self, pnl) end + libNyx.UI.AutoNoBG(pnl) + pnl:SetAlpha(math.floor(self._contentAlpha or 0)) + end + + local closeBtn + if libNyx.UI.Components and libNyx.UI.Components.CreateButton then + closeBtn = libNyx.UI.Components.CreateButton(f, "✕", {variant="ghost", align="center", radius=Style.radius}) + closeBtn:SetSize(libNyx.UI.Scale(40), libNyx.UI.Scale(40)) + if closeBtn.SetRippleStyle then closeBtn:SetRippleStyle(2) end + closeBtn._onClick = function() f:Close() end + else + closeBtn = vgui.Create("DButton", f) + closeBtn:SetText("✕") + closeBtn:SetSize(libNyx.UI.Scale(40), libNyx.UI.Scale(40)) + libNyx.UI.AutoNoBG(closeBtn) + closeBtn.DoClick = function() f:Close() end + end + + function f:PerformLayout(w, h) + closeBtn:SetPos(w - Style.headerIndentX - closeBtn:GetWide(), Style.headerIndentY) + end + + function f:Think() + local now = SysTime() + if self._phase == "opening" then + local t = math.TimeFraction(self._t0, self._t0 + self._durOpen, now) + if t >= 1 then + self._phase = "idle" + self:SetSize(self._targetW, self._targetH) + self:Center() + self._contentAlpha = 255 + applyContentAlpha(self, self._contentAlpha) + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(true) + else + local e = easeOutBack(t) + local ow, oh = self._targetW * self._overshoot, self._targetH * self._overshoot + local w, h = Lerp(e, self._minW, ow), Lerp(e, self._minH, oh) + if t > 0.85 then + local t2 = math.TimeFraction(0.85, 1.0, t) + local e2 = 1 - (1 - t2) * (1 - t2) + w = Lerp(e2, ow, self._targetW) + h = Lerp(e2, oh, self._targetH) + end + self:SetSize(math.max(1, w), math.max(1, h)) + self:SetPos(cx - w/2, cy - h/2) + self._contentAlpha = expApproach(self._contentAlpha or 0, 255 * math.Clamp(t ^ 1.6, 0, 1), 16) + applyContentAlpha(self, self._contentAlpha) + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + end + elseif self._phase == "closing" then + local t = math.TimeFraction(self._t0, self._t0 + self._durClose, now) + if t >= 1 then + self:Remove() + else + local e = easeInCubic(t) + local w = Lerp(e, self._targetW, self._minW) + local h = Lerp(e, self._targetH, self._minH) + self:SetSize(math.max(1, w), math.max(1, h)) + self:SetPos(cx - w/2, cy - h/2) + self._contentAlpha = expApproach(self._contentAlpha or 255, 0, 18) + applyContentAlpha(self, self._contentAlpha) + end + end + end + + function f:Paint(w, h) + local r = math.max(Style.radius, libNyx.UI.Scale(14)) + libNyx.UI.Draw.Glass(0, 0, w, h, {radius = r, fill = Style.bgColor, stroke = true, strokeColor = Style.glassStroke, blurIntensity = 1.1}) + if self._title ~= "" then + draw.SimpleText(self._title, libNyx.UI.Font(libNyx.UI.Scale(30)), Style.headerIndentX, Style.headerIndentY, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + end + + local realRemove = f.Remove + function f:Close() + if self._phase == "closing" then return end + self._phase = "closing" + self._t0 = SysTime() + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) + end + function f:Remove() + if self._phase ~= "closing" and self._phase ~= "idle" then return end + realRemove(self) + end + return f +end + +function libNyx.UI.Draw.Panel(x, y, w, h, opts) + opts = opts or {} + local r = opts.radius or Style.radius + local col = opts.color or Style.cardColor + local doShadow = opts.shadow == true + local flags = opts.shape or 0 + if opts.glass == true or opts.blur == true then + libNyx.UI.Draw.Glass(x, y, w, h, { + radius = r, + fill = col, + stroke = opts.stroke, + strokeColor = opts.strokeColor, + shape = flags, + blurIntensity = opts.blurIntensity or Style.blurIntensity + }) + return + end + if doShadow then + RNDX.DrawShadows(r, x, y, w, h, Color(0,0,0, 190), Style.shadowSpread, Style.shadowIntensity, flags) + end + RNDX.Draw(r, x, y, w, h, col, flags) + if opts.outline then + RNDX.DrawOutlined(r, x, y, w, h, opts.outlineColor or Style.glassStroke, opts.outline, flags) + end +end + +libNyx.UI.Components = libNyx.UI.Components or {} +local Components = libNyx.UI.Components + +libNyx.UI._fx = libNyx.UI._fx or nil +local function FX() + if IsValid(libNyx.UI._fx) then return libNyx.UI._fx end + local pnl = vgui.Create("DPanel") + pnl:SetZPos(32767) + pnl:SetMouseInputEnabled(false) + pnl:SetKeyboardInputEnabled(false) + pnl:SetSize(ScrW(), ScrH()) + pnl:SetPos(0,0) + libNyx.UI.AutoNoBG(pnl) + pnl._anims = {} + pnl.Paint = function(s,w,h) + local now = SysTime() + for i = #s._anims, 1, -1 do + local a = s._anims[i] + local t = math.TimeFraction(a.t0, a.t0 + a.dur, now) + if t >= 1 then + if isfunction(a.cb) then a.cb() end + table.remove(s._anims, i) + else + local e = t < 0.5 and (4*t*t*t) or (1 - (-2*t+2)^3/2) + local x = Lerp(e, a.sx, a.ex) + local y = Lerp(e, a.sy, a.ey - a.arc*math.sin(t*math.pi)) + surface.SetDrawColor(255,255,255,255) + surface.SetMaterial(a.mat) + surface.DrawTexturedRect(x - a.size/2, y - a.size/2, a.size, a.size) + end + end + if #s._anims == 0 then s:SetVisible(false) end + end + hook.Add("OnScreenSizeChanged","libNyx.UI.FX",function() + if not IsValid(pnl) then return end + pnl:SetSize(ScrW(), ScrH()) + pnl:SetPos(0,0) + end) + libNyx.UI._fx = pnl + return pnl +end + +function libNyx.UI.FlyIcon(mat, sx, sy, ex, ey, size, dur, cb) + local fx = FX() + fx:SetVisible(true) + table.insert(fx._anims, { + mat = mat, sx = sx, sy = sy, ex = ex, ey = ey, + size = size or libNyx.UI.Scale(36), + dur = dur or 0.35, t0 = SysTime(), + arc = math.min(64, math.Distance(sx,sy,ex,ey)*0.25), + cb = cb + }) +end + +local function FitModel(mp) + if not IsValid(mp) or not IsValid(mp.Entity) then return end + local mn, mx = mp.Entity:GetRenderBounds() + local size = mx - mn + local center = (mn + mx) * 0.5 + local maxdim = math.max(size.x, size.y, size.z) + local ang = Angle(12, 25, 0) + local fov = mp:GetFOV() + local dist = (maxdim * 0.55) / math.rad(fov * 0.5) + local camPos = center + ang:Forward() * -dist + Vector(0, 0, size.z * 0.08) + mp:SetCamPos(camPos) + mp:SetLookAt(center + Vector(0, 0, size.z * 0.05)) + mp:SetAmbientLight(Vector(255, 255, 255)) + mp:SetDirectionalLight(BOX_TOP, Color(255,255,255)) +end + +function Components.CreateCell(parent, opts) + opts = opts or {} + local sz = opts.size or libNyx.UI.Scale(88) + local r = opts.radius or libNyx.UI.Scale(14) + local tint = opts.tint or Style.accentColor + + local cell = vgui.Create("DButton", parent) + cell:SetText("") + cell:SetSize(sz, sz) + + cell._radius = r + cell._tint = tint + cell._iconMat = nil + cell._iconSize = math.floor(sz * 0.6) + cell._mp = nil + cell._info = nil + cell._infoBox = nil + cell._lastScreenPos = {x = 0, y = 0} + + local function expApproach(cur, tgt, spd) + local k = 1 - math.exp(-(spd or 10) * FrameTime()) + return cur + (tgt - cur) * k + end + + local function normalizeTags(t) + local out = {} + if not istable(t) then return out end + for _, v in ipairs(t) do + if isstring(v) then + out[#out+1] = {text = v, color = PalettePick(v)} + elseif istable(v) then + out[#out+1] = { + text = tostring(v.text or v.label or v[1] or ""), + color = v.color or v.col or v[2] or PalettePick(v.text or v[1] or "?") + } + end + end + return out + end + + local function removeInfoBox() + if IsValid(cell._infoBox) then + cell._infoBox:Remove() + cell._infoBox = nil + end + end + + local function ensureInfoBox() + if IsValid(cell._infoBox) then return cell._infoBox end + if not cell._info then return nil end + + local ib = vgui.Create("DPanel") + ib:SetDrawOnTop(true) + ib:SetMouseInputEnabled(true) + ib:SetKeyboardInputEnabled(false) + + ib._title = tostring(cell._info.title or "") + ib._desc = tostring(cell._info.desc or "") + ib._tags = normalizeTags(cell._info.tags) + ib._tint = cell._tint + ib._state = "opening" + ib._t0 = SysTime() + ib._durOpen = 0.16 + ib._durClose = 0.14 + ib._vis = 0 + ib._hoverA = 0 + + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(20))) + local tw, th = surface.GetTextSize(ib._title ~= "" and ib._title or " ") + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(16))) + local dw, dh = surface.GetTextSize(ib._desc ~= "" and ib._desc or " ") + + local pad = libNyx.UI.Scale(12) + local gap = libNyx.UI.Scale(8) + local tagH = libNyx.UI.Scale(22) + local ww = math.max(libNyx.UI.Scale(240), tw + pad*2, dw + pad*2) + local hh = pad + th + (ib._desc ~= "" and (gap + dh) or 0) + ( (#ib._tags>0) and (gap + tagH) or 0 ) + pad + + ib:SetSize(ww, hh) + + local function place() + local sx, sy = cell:LocalToScreen(0, 0) + local cw, ch = cell:GetSize() + local w, h = ib:GetSize() + local x = sx + cw + libNyx.UI.Scale(10) + local y = sy + math.floor((ch - h) * 0.5) + x = math.min(x, ScrW() - w - libNyx.UI.Scale(8)) + y = math.Clamp(y, libNyx.UI.Scale(8), ScrH() - h - libNyx.UI.Scale(8)) + ib:SetPos(x, y) + end + place() + ib.Think = function(s) + local sx, sy = cell:LocalToScreen(0, 0) + if sx ~= (cell._lastScreenPos.x or 0) or sy ~= (cell._lastScreenPos.y or 0) then + place() + cell._lastScreenPos.x, cell._lastScreenPos.y = sx, sy + end + local tgtH = s:IsHovered() and 1 or 0 + s._hoverA = expApproach(s._hoverA or 0, tgtH, 10) + + local now = SysTime() + if s._state == "opening" then + local t = math.TimeFraction(s._t0, s._t0 + s._durOpen, now) + if t >= 1 then + s._state = "open" + s._vis = 1 + else + s._vis = 1 - (1 - t) * (1 - t) + end + elseif s._state == "closing" then + local t = math.TimeFraction(s._t0, s._t0 + s._durClose, now) + if t >= 1 then + s:Remove() + cell._infoBox = nil + return + else + s._vis = 1 - (t * t * t) + end + else + s._vis = expApproach(s._vis or 1, 1, 12) + if (not cell:IsHovered()) and (not s:IsHovered()) then + s._state = "closing" + s._t0 = SysTime() + end + end + end + + ib.Paint = function(s, w, h) + local v = math.Clamp(s._vis or 0, 0, 1) + local sc = Lerp(v, 0.92, 1.00) + local dx = math.floor((w - w*sc) * 0.5) + local dy = math.floor((h - h*sc) * 0.5) + local baseFillA = Lerp(s._hoverA or 0, 90, 70) + local strokeA = 20 + local fillCol = Color(16,18,24, math.floor(baseFillA * v)) + local strokeCol = Color(255,255,255, math.floor(strokeA * v)) + local blurI = 1.15 + 0.20 * (s._hoverA or 0) + + libNyx.UI.Draw.Glass(dx, dy, math.floor(w*sc), math.floor(h*sc), { + radius = libNyx.UI.Scale(10), + fill = fillCol, + stroke = true, + strokeColor = strokeCol, + blurIntensity = blurI + }) + + local ca = math.floor(255 * v) + local slide = math.floor(libNyx.UI.Scale(4) * (1 - v)) + + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(20))) + draw.SimpleText(s._title or "", libNyx.UI.Font(libNyx.UI.Scale(20)), + dx + libNyx.UI.Scale(12), dy + libNyx.UI.Scale(12) + slide, + Color(Style.textColor.r, Style.textColor.g, Style.textColor.b, ca), + TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + + if (s._desc or "") ~= "" then + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(16))) + local _, th = surface.GetTextSize(s._title ~= "" and s._title or " ") + draw.SimpleText(s._desc or "", libNyx.UI.Font(libNyx.UI.Scale(16)), + dx + libNyx.UI.Scale(12), dy + libNyx.UI.Scale(12) + th + libNyx.UI.Scale(6) + slide, + Color(255,255,255, math.floor(220 * v)), + TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + if #s._tags > 0 then + local pad = libNyx.UI.Scale(12) + local tagH = libNyx.UI.Scale(22) + local chipR = libNyx.UI.Scale(8) + local y = dy + h*sc - tagH - pad + local x = dx + pad + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(14))) + for _, tg in ipairs(s._tags) do + local t = tg.text or "" + local tw = select(1, surface.GetTextSize(t)) + local cw = tw + libNyx.UI.Scale(14) + libNyx.UI.Draw.Panel(x, y, cw, tagH, { + radius = chipR, + color = Color((tg.color or Style.accentColor).r, (tg.color or Style.accentColor).g, (tg.color or Style.accentColor).b, math.floor(150 * v)), + glass = true, + stroke = true, + strokeColor = Color(255,255,255, math.floor(16 * v)) + }) + draw.SimpleText(t, libNyx.UI.Font(libNyx.UI.Scale(14)), + x + cw/2, y + tagH/2, + Color(255,255,255, math.floor(240 * v)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + x = x + cw + libNyx.UI.Scale(6) + end + end + end + + ib.OnCursorExited = function(s) + if not cell:IsHovered() and s._state ~= "closing" then + s._state = "closing" + s._t0 = SysTime() + end + end + + cell._infoBox = ib + return ib + end + + function cell:SetItemIcon(mat, size, info) + if isstring(mat) then mat = Material(mat, "noclamp smooth") end + self._iconMat = mat + if size then self._iconSize = size end + self._info = info + if IsValid(self._mp) then self._mp:Remove() self._mp = nil end + removeInfoBox() + self:InvalidateLayout(true) + end + + function cell:SetItemModel(path) + self._iconMat = nil + if not IsValid(self._mp) then + self._mp = vgui.Create("DModelPanel", self) + self._mp:Dock(FILL) + self._mp:DockMargin(libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6)) + self._mp:SetFOV(28) + function self._mp:LayoutEntity(ent) end + end + self._mp:SetModel(path or "models/props_c17/oildrum001.mdl") + removeInfoBox() + end + + function cell:ClearItem() + self._iconMat = nil + if IsValid(self._mp) then self._mp:Remove() self._mp = nil end + self._info = nil + removeInfoBox() + self:InvalidateLayout(false) + end + + function cell:HasItem() + return self._iconMat ~= nil or IsValid(self._mp) + end + + function cell:GetCenterScreen() + local x, y = self:LocalToScreen(self:GetWide()/2, self:GetTall()/2) + return x, y + end + + function cell:PerformLayout(w, h) + local dock = self:GetDock() + if dock == LEFT or dock == RIGHT then + self:SetWide(h) + elseif dock == TOP or dock == BOTTOM then + self:SetTall(w) + elseif dock ~= FILL then + local side = math.min(w, h) + self:SetSize(side, side) + end + end + + function cell:OnSizeChanged(w, h) + local side = math.min(w, h) + self._iconSize = math.floor(side * 0.6) + self._lastScreenPos.x, self._lastScreenPos.y = -1, -1 + end + + function cell:OnCursorEntered() + if self:HasItem() and self._info then + local ib = ensureInfoBox() + if IsValid(ib) and ib._state == "closing" then + ib._state = "opening" + ib._t0 = SysTime() + end + end + end + + function cell:OnCursorExited() + local ib = self._infoBox + if IsValid(ib) and (not ib:IsHovered()) and ib._state ~= "closing" then + ib._state = "closing" + ib._t0 = SysTime() + end + end + + function cell:OnRemove() + removeInfoBox() + end + + cell.Paint = function(s, w, h) + libNyx.UI.Draw.Glass(0, 0, w, h, { + radius = s._radius, + fill = Color(16,18,24,120), + stroke = true, + strokeColor = Style.glassStroke, + blurIntensity = 1.05 + }) + + if s._iconMat then + surface.SetDrawColor(255, 255, 255, 255) + surface.SetMaterial(s._iconMat) + surface.DrawTexturedRect((w - s._iconSize)/2, (h - s._iconSize)/2, s._iconSize, s._iconSize) + end + end + + return cell +end + +libNyx.UI._drag = libNyx.UI._drag or {active=false} + +local function easeInOutCubic(t) + if t < 0.5 then return 4*t*t*t end + t = (t - 1); return 1 + 4*t*t*t +end + +local function ensureDragHook() + if libNyx.UI._drag._hooked then return end + hook.Add("PostRenderVGUI", "zzz.libNyx.UI.DragOverlay", function() + local d = libNyx.UI._drag + if not d or not d.active then return end + local mx, my = gui.MousePos() + mx = mx + (d.offx or 0) + my = my + (d.offy or 0) + local now = SysTime() + local a = 1 + local boxSize, rad, iconSize + if d.phase == "pickup" then + local t = math.TimeFraction(d.t0, d.t0 + d.dur, now) + if t >= 1 then + d.phase = "drag" + t = 1 + end + local e = easeInOutCubic(math.Clamp(t, 0, 1)) + boxSize = Lerp(e, d.boxFrom, d.boxTo) + rad = Lerp(e, d.radFrom, d.radTo) + iconSize = Lerp(e, d.iconFrom, d.iconTo) + elseif d.phase == "drop" then + local t = math.TimeFraction(d.t0, d.t0 + d.dur, now) + if t >= 1 then + d.active = false + if IsValid(d.applyTo) and d.mat then + d.applyTo:SetItemIcon(d.mat, d.origIconSize, d.info) + elseif IsValid(d.src) and d.mat then + d.src:SetItemIcon(d.mat, d.origIconSize, d.info) + end + if d.onDone then pcall(d.onDone) end + return + end + + local e = easeInOutCubic(math.Clamp(t, 0, 1)) + boxSize = Lerp(e, d.boxTo, d.boxTo * 0.92) + rad = Lerp(e, d.radTo, d.radTo * 0.85) + iconSize = Lerp(e, d.iconTo, d.iconTo * 0.90) + a = 1 - e + else + boxSize = d.boxTo + rad = d.radTo + iconSize = d.iconTo + end + local bx = mx - boxSize/2 + local by = my - boxSize/2 + libNyx.UI.Draw.Glass(bx, by, boxSize, boxSize, { + radius = rad, + fill = Color(20,24,32, math.floor(120 * a)), + stroke = true, + strokeColor = Color(255,255,255, math.floor(22 * a)), + blurIntensity = 1.25 + }) + if d.mat then + surface.SetDrawColor(255, 255, 255, math.floor(255 * a)) + surface.SetMaterial(d.mat) + surface.DrawTexturedRect(mx - iconSize/2, my - iconSize/2, iconSize, iconSize) + end + local tgt = vgui.GetHoveredPanel() + while IsValid(tgt) and not tgt._isInteractiveCell do tgt = tgt:GetParent() end + local S = libNyx.UI.Style + if IsValid(tgt) and tgt._isInteractiveCell then + local tx, ty = tgt:LocalToScreen(0, 0) + d._tx, d._ty = tx, ty + d._tw, d._th = tgt:GetWide(), tgt:GetTall() + d._tr = tgt._radius or S.radius + d._talpha = 1 + else + d._talpha = 0 + end + local dt = FrameTime() + local k = 1 - math.pow(0.0001, dt * 60) + local ka = 1 - math.pow(0.0001, dt * 45) + d._hx = Lerp(k, d._hx or d._tx or 0, d._tx or (d._hx or 0)) + d._hy = Lerp(k, d._hy or d._ty or 0, d._ty or (d._hy or 0)) + d._hw = Lerp(k, d._hw or d._tw or 0, d._tw or (d._hw or 0)) + d._hh = Lerp(k, d._hh or d._th or 0, d._th or (d._hh or 0)) + d._hr = Lerp(k, d._hr or d._tr or S.radius, d._tr or (d._hr or S.radius)) + d._ha = Lerp(ka, d._ha or 0, d._talpha or 0) + if (d._ha or 0) > 0.01 and d._hx and d._hy and d._hw and d._hh then + local acc = S.accentColor + local x = math.floor(d._hx + 0.5) + local y = math.floor(d._hy + 0.5) + local w = math.floor(d._hw + 0.5) + local h = math.floor(d._hh + 0.5) + local r = math.floor((d._hr or S.radius) + 0.5) + local aL = math.floor(210 * d._ha) + local sw = math.max(1, math.floor(1 + d._ha)) + RNDX.DrawOutlined(r, x, y, w, h, Color(acc.r, acc.g, acc.b, aL), sw, 0) + end + end) + libNyx.UI._drag._hooked = true +end + +function libNyx.UI.StartDragIcon(src, mat, size, offx, offy) + if isstring(mat) then mat = Material(mat, "noclamp smooth") end + local S = libNyx.UI.Style + local base = tonumber(size) or libNyx.UI.Scale(36) + ensureDragHook() + libNyx.UI._drag = { + active = true, + src = src, + mat = mat, + origIconSize = base, + offx = offx or 0, + offy = offy or 0, + + info = (IsValid(src) and src._info) or nil, + + phase = "pickup", + t0 = SysTime(), + dur = 0.16, + boxFrom = math.max(18, base * 0.88), + boxTo = base + libNyx.UI.Scale(18), + radFrom = math.max(4, S.radius * 0.5), + radTo = math.floor(S.radius * 1.15), + iconFrom = base * 0.92, + iconTo = base, + _tx=nil,_ty=nil,_tw=nil,_th=nil,_tr=nil,_talpha=0, + _hx=nil,_hy=nil,_hw=nil,_hh=nil,_hr=nil,_ha=0, + } +end + +function libNyx.UI.StopDragIcon(applyTo) + local d = libNyx.UI._drag + if not d or not d.active then return end + d.applyTo = applyTo + d.phase = "drop" + d.t0 = SysTime() + d.dur = 0.18 +end + +local function libnyx_find_cell_under_cursor() + local p = vgui.GetHoveredPanel() + while IsValid(p) and not p._isInteractiveCell do p = p:GetParent() end + return p +end + +libNyx.UI._inv = libNyx.UI._inv or {holding=nil, from=nil} + +function Components.CreateInteractiveCell(parent, opts) + local cell = Components.CreateCell(parent, opts) + cell._isInteractiveCell = true + function cell:OnMousePressed(mc) + if mc ~= MOUSE_LEFT then return end + if libNyx.UI._drag and libNyx.UI._drag.active then return end + if not self:HasItem() or not self._iconMat then return end + local lx, ly = self:LocalCursorPos() + local offx = lx - self:GetWide()/2 + local offy = ly - self:GetTall()/2 + libNyx.UI.StartDragIcon(self, self._iconMat, self._iconSize, offx, offy) + self:ClearItem() + self:MouseCapture(true) + surface.PlaySound("ui/buttonclick.wav") + end + function cell:OnMouseReleased(mc) + if mc ~= MOUSE_LEFT then return end + self:MouseCapture(false) + local tgt = libnyx_find_cell_under_cursor() + if IsValid(tgt) and tgt ~= self and not tgt:HasItem() then + libNyx.UI.StopDragIcon(tgt) + else + libNyx.UI.StopDragIcon(self) + end + surface.PlaySound("ui/buttonclickrelease.wav") + end + return cell +end + +libNyx.UI._rippleStyle = libNyx.UI._rippleStyle or 1 +function libNyx.UI.SetRippleStyle(style) + if isstring(style) then + local s = string.lower(style) + if s == "ring" or s == "2" then libNyx.UI._rippleStyle = 2 else libNyx.UI._rippleStyle = 1 end + else + libNyx.UI._rippleStyle = (tonumber(style) == 2) and 2 or 1 + end +end +function libNyx.UI.GetRippleStyle() return libNyx.UI._rippleStyle end + +local function makeRipple(btn, style) + btn._ripples = {} + btn._rippleDur = 0.45 + btn._rippleSpeed = 340 + btn._rippleStyle = style and ((tonumber(style) == 2 or string.lower(tostring(style)) == "ring") and 2 or 1) or nil + btn._rippleThick = libNyx.UI.Scale(4) + function btn:SetRippleStyle(s) + self._rippleStyle = (tonumber(s) == 2 or string.lower(tostring(s)) == "ring") and 2 or 1 + end +end + +local function paintRipples(self, w, h, color) + local now = SysTime() + local variant = self._rippleStyle or libNyx.UI.GetRippleStyle() + local thick = math.max(1, self._rippleThick or libNyx.UI.Scale(3)) + for i = #self._ripples, 1, -1 do + local r = self._ripples[i] + local dt = now - r.t0 + if dt >= self._rippleDur then + table.remove(self._ripples, i) + else + local radius = dt * self._rippleSpeed + local alpha = math.Clamp(220 * (1 - dt / self._rippleDur), 0, 220) + if variant == 2 then + surface.SetDrawColor(color.r, color.g, color.b, alpha) + for t = 0, thick - 1 do + surface.DrawCircle(r.x, r.y, math.max(0, radius - t), color.r, color.g, color.b, alpha) + end + else + surface.SetDrawColor(color.r, color.g, color.b, alpha) + draw.NoTexture() + local seg = 28 + local verts = {{x = r.x, y = r.y}} + for k = 0, seg do + local ang = (k / seg) * math.pi * 2 + verts[#verts+1] = {x = r.x + math.cos(ang) * radius, y = r.y + math.sin(ang) * radius} + end + surface.DrawPoly(verts) + end + end + end +end + +function Components.CreateCheckbox(parent, opts) + opts = opts or {} + + local box = vgui.Create("DButton", parent) + box:SetText("") + box:SetCursor("hand") + box._variant = string.lower(opts.variant or "switch") + box._checked = tobool(opts.checked) + box._tint = opts.tint or Style.accentColor + box._label = opts.label or "" + box._size = math.max(libNyx.UI.Scale(20), tonumber(opts.size) or libNyx.UI.Scale(22)) + box._gap = libNyx.UI.Scale(10) + box._font = libNyx.UI.Font(libNyx.UI.Scale(18)) + box._anim = box._checked and 1 or 0 + box._onChange = opts.onChange + box._group = opts.group + + libNyx.UI._checkboxGroups = libNyx.UI._checkboxGroups or {} + + local function ensureGroup(self, name) + if not name or name == "" then return end + local t = libNyx.UI._checkboxGroups + t[name] = t[name] or {} + if not table.HasValue(t[name], self) then table.insert(t[name], self) end + end + local function leaveGroup(self, name) + if not name or name == "" then return end + local t = libNyx.UI._checkboxGroups[name] + if not t then return end + for i = #t, 1, -1 do + if t[i] == self then table.remove(t, i) end + end + end + local function notifyGroup(self) + if self._variant ~= "radio" then return end + local name = self._group + if not name or name == "" then return end + local t = libNyx.UI._checkboxGroups[name] or {} + for _, other in ipairs(t) do + if IsValid(other) and other ~= self and other._variant == "radio" then + other._checked = false + other._anim = 0 + other:InvalidateLayout(false) + end + end + end + + function box:SetGroup(name) + if self._group == name then return end + leaveGroup(self, self._group) + self._group = name + ensureGroup(self, name) + end + + if box._variant == "radio" and (not box._group or box._group == "") then + box._group = "auto_radio_group_" .. tostring(parent or box:GetParent() or "root") + end + ensureGroup(box, box._group) + + function box:ControlSize() + local h = self._size + if self._variant == "radio" then + return h, h + end + local mul = (self._variant == "switch") and 1.95 or 1.65 + return math.floor(h * mul), h + end + + function box:_RefreshSize() + local cw, ch = self:ControlSize() + surface.SetFont(self._font) + local tw = surface.GetTextSize(self._label or "") + local w = cw + (tw > 0 and (self._gap + tw) or 0) + libNyx.UI.Scale(4) + local h = math.max(ch, libNyx.UI.Scale(28)) + self:SetSize(w, h) + end + + function box:SetLabel(t) self._label = tostring(t or "") self:_RefreshSize() end + function box:SetVariant(v) + v = string.lower(v or "switch") + if self._variant == "radio" and v ~= "radio" then + leaveGroup(self, self._group) + end + self._variant = v + if self._variant == "radio" and (not self._group or self._group == "") then + self._group = "auto_radio_group_" .. tostring(self:GetParent() or "root") + ensureGroup(self, self._group) + end + self:_RefreshSize() + end + + function box:SetValue(b) + b = tobool(b) + if self._variant == "radio" then + if self._checked ~= b then + self._checked = b + if b then notifyGroup(self) end + if isfunction(self._onChange) then self._onChange(self._checked, self) end + end + else + if self._checked ~= b then + self._checked = b + if isfunction(self._onChange) then self._onChange(self._checked, self) end + end + end + self._anim = self._checked and 1 or 0 + self:InvalidateLayout(false) + end + function box:GetValue() return self._checked end + function box:SetOnChange(fn) self._onChange = fn end + function box:OnCursorEntered() end + + function box:DoClick() + if self._variant == "radio" then + self._checked = not self._checked + if self._checked then notifyGroup(self) end + if isfunction(self._onChange) then self._onChange(self._checked, self) end + else + self._checked = not self._checked + if isfunction(self._onChange) then self._onChange(self._checked, self) end + end + surface.PlaySound("ui/buttonclickrelease.wav") + end + + function box:OnRemove() + leaveGroup(self, self._group) + end + + box.Think = function(self) + local target = self._checked and 1 or 0 + self._anim = Lerp(FrameTime() * 12, self._anim or target, target) + end + + box.Paint = function(self, w, h) + local tint = self._tint + local cw, ch = self:ControlSize() + local cx = 0 + local cy = (h - ch) / 2 + local a = math.Clamp(self._anim or 0, 0, 1) + + if self._variant == "radio" then + local r = math.floor(ch/2) + local strokeA = 38 + (120 - 38) * a + libNyx.UI.Draw.Glass(cx, cy, ch, ch, {radius = r, fill = Color(20,24,32,110), stroke = true, strokeColor = Color(tint.r, tint.g, tint.b, math.floor(strokeA)), blurIntensity = 1.0}) + local dot = math.floor(ch * 0.5 * a) + if a > 0.01 and dot > 0 then + local dx = cx + (ch - dot)/2 + local dy = cy + (ch - dot)/2 + libNyx.UI.Draw.Panel(dx, dy, dot, dot, {radius = dot/2, color = Color(tint.r, tint.g, tint.b, math.floor(215*a)), glass = true}) + end + else + local r = math.floor(ch/2) + if self._variant == "switch" then + local trackCol = Color(Lerp(a, 30, tint.r), Lerp(a, 34, tint.g), Lerp(a, 42, tint.b), math.floor(Lerp(a, 100, 170))) + libNyx.UI.Draw.Panel(cx, cy, cw, ch, {radius = r, color = trackCol, glass = true, stroke = true}) + local pad = libNyx.UI.Scale(2) + local knob = ch - pad*2 + local kx = Lerp(a, cx + pad, cx + cw - pad - knob) + libNyx.UI.Draw.Panel(kx, cy + pad, knob, knob, {radius = knob/2, color = Color(245,245,252,230), glass = true, stroke = true}) + else + libNyx.UI.Draw.Panel(cx, cy, cw, ch, {radius = r, color = Color(30,34,42,120), glass = true, stroke = true}) + local pad = libNyx.UI.Scale(3) + local knob = ch - pad*2 + local kx = Lerp(a, cx + pad, cx + cw - pad - knob) + local fillA = math.floor(Lerp(a, 60, 205)) + libNyx.UI.Draw.Panel(kx, cy + pad, knob, knob, {radius = knob/2, color = Color(tint.r, tint.g, tint.b, fillA), glass = true}) + RNDX.DrawOutlined(knob/2, kx, cy + pad, knob, knob, Color(255,255,255, math.floor(Lerp(a, 40, 140))), 1) + end + end + + if self._label ~= "" then + local xText = cx + cw + self._gap + draw.SimpleText(self._label, self._font, xText, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end + + box:_RefreshSize() + return box +end + +function Components.CreateButton(parent, text, opts) + opts = opts or {} + local btn = vgui.Create("DButton", parent) + btn:SetText("") + btn:SetTall(opts.h or Style.btnHeight) + btn:SetFont(libNyx.UI.Font(libNyx.UI.Scale(20))) + btn._text = text or "Button" + btn._icon = opts.icon + btn._align = opts.align or "center" + btn._variant = opts.variant or "primary" + btn._radius = opts.radius or Style.radius + btn._iconSize = opts.iconSize or Style.iconSize + btn._onClick = opts.onClick + btn._tint = opts.tint + btn._centerTint = opts.centerTint or opts.tint2 + local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth") + local MAT_GRADIENT_R = Material("vgui/gradient-r", "noclamp smooth") + local function lighten(col, k) + return Color( + math.Clamp(col.r + (255 - col.r) * k, 0, 255), + math.Clamp(col.g + (255 - col.g) * k, 0, 255), + math.Clamp(col.b + (255 - col.b) * k, 0, 255), + col.a or 255 + ) + end + local function DuoDefaults() + local base = Color(104, 76, 230, 205) + local center = Color(184, 160, 255, 140) + return base, center + end + makeRipple(btn,2) + function btn:DoClick() + libNyx.UI.PlayClick() + if isfunction(self._onClick) then self._onClick(self) end + end + function btn:OnCursorEntered() + local mx, my = gui.MousePos() + mx, my = self:ScreenToLocal(mx, my) + table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()}) + libNyx.UI.PlayHover() + end + btn.Paint = function(self, w, h) + local pad = Style.padding + local r = self._radius + local v = self._variant + local align = ((v == "primary_center") or (v == "center_duo")) and "center" or (self._align or "center") + if v == "center_duo" then + local defBase, defCenter = DuoDefaults() + local baseCol = self._tint or defBase + local centerCol = self._centerTint or (self._tint and lighten(self._tint, 0.26)) or defCenter + local aAdj = self:IsDown() and 20 or (self:IsHovered() and 10 or 0) + local bg = Color(baseCol.r, baseCol.g, baseCol.b, math.Clamp((baseCol.a or 205) + aAdj, 120, 255)) + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = bg, glass = true}) + local light = Color(centerCol.r, centerCol.g, centerCol.b, math.Clamp((centerCol.a or 140) + aAdj, 0, 255)) + local coverage = 0.78 + local halfCov = math.floor(w * coverage * 0.5) + local cx = math.floor(w * 0.5) + local overlap = 2 + local ro = 0 + if not (MAT_GRADIENT_L:IsError() or MAT_GRADIENT_R:IsError()) then + local lx, lw = cx - halfCov - overlap, halfCov + overlap + RNDX.DrawMaterial(ro, lx, 0, lw, h, light, MAT_GRADIENT_R, 0) + local rx, rw = cx, halfCov + overlap + RNDX.DrawMaterial(ro, rx, 0, rw, h, light, MAT_GRADIENT_L, 0) + end + elseif v == "primary" or v == "primary_center" then + local a = self:IsDown() and 235 or (self:IsHovered() and 220 or 205) + local col = Color(Style.accentColor.r, Style.accentColor.g, Style.accentColor.b, a) + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = col, glass = true}) + elseif v == "ghost" then + local col = self:IsHovered() and Style.hoverColor or Color(0,0,0,0) + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = col, glass = true, stroke = true}) + elseif v == "gradient" then + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(20,22,30,120), glass = true, stroke = true}) + if not MAT_GRADIENT_L:IsError() then + local tint = self._tint or PalettePick(self._text) + local a = self:IsDown() and (Style.gradientAlphaBtn + 20) + or self:IsHovered() and (Style.gradientAlphaBtn + 10) + or Style.gradientAlphaBtn + local gcol = Color(tint.r, tint.g, tint.b, a) + local gw = w * 0.6 + RNDX.DrawMaterial(r, 0, 0, gw, h, gcol, MAT_GRADIENT_L, 0) + end + else + local col = self:IsHovered() and Color(Style.panelColor.r,Style.panelColor.g,Style.panelColor.b, 160) or Style.panelColor + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = col, glass = true}) + end + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(20))) + local tw = surface.GetTextSize(self._text or "") + if align == "left" then + local x = pad + if self._icon and not self._icon:IsError() then + surface.SetDrawColor(Style.textColor) + surface.SetMaterial(self._icon) + surface.DrawTexturedRect(x, (h - self._iconSize)/2, self._iconSize, self._iconSize) + x = x + self._iconSize + pad + end + draw.SimpleText(self._text, libNyx.UI.Font(libNyx.UI.Scale(20)), x, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + else + local icw = (self._icon and not self._icon:IsError()) and (self._iconSize + pad) or 0 + local content = icw + tw + local startX = math.floor((w - content) * 0.5) + if self._icon and not self._icon:IsError() then + surface.SetDrawColor(Style.textColor) + surface.SetMaterial(self._icon) + surface.DrawTexturedRect(startX, (h - self._iconSize)/2, self._iconSize, self._iconSize) + startX = startX + icw + end + draw.SimpleText(self._text, libNyx.UI.Font(libNyx.UI.Scale(20)), startX, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + paintRipples(self, w, h, Style.textColor) + end + return btn +end + +function libNyx.UI.Components.CreateSlider(parent, opts) + opts = opts or {} + local SUI = libNyx.UI + local Style = SUI.Style + local sld = vgui.Create("DPanel", parent) + sld:SetTall(Style.btnHeight) + SUI.AutoNoBG(sld) + + sld._min = tonumber(opts.min) or 0 + sld._max = tonumber(opts.max) or 100 + sld._dec = math.max(0, tonumber(opts.decimals) or 0) + sld._value = math.Clamp(tonumber(opts.value) or sld._min, sld._min, sld._max) + sld._lerpVal = sld._value + sld._tint = opts.tint or Style.accentColor + sld._gap = SUI.Scale(10) + sld._dragging = false + sld._hoverA = 0 + sld._knobA = 0 + sld._pulse = 0 + sld._lastText = "" + sld._font = SUI.Font(SUI.Scale(18)) + sld._fontNum = SUI.Font(SUI.Scale(18)) + + local MAT_GRADIENT_L = Material("vgui/gradient-l","noclamp smooth") + local function fmtn(v,d) return (d or 0) <= 0 and tostring(math.Round(v)) or string.format("%."..d.."f", v) end + local function fracFromValue(v) local r=sld._max - sld._min if r<=0 then return 0 end return (v - sld._min)/r end + local function valueFromFrac(f) return sld._min + f*(sld._max - sld._min) end + local function clamp01(x) return x<0 and 0 or (x>1 and 1 or x) end + local function expApproach(cur,tgt,spd) local k=1-math.exp(-(spd or 12)*FrameTime()) return cur+(tgt-cur)*k end + + local function counterWidth(txt) + surface.SetFont(sld._fontNum) + local w = select(1, surface.GetTextSize(txt)) + return math.Clamp(w + SUI.Scale(24), SUI.Scale(50), SUI.Scale(140)) + end + + local function trackBounds(w,h) + local num = fmtn(sld._lerpVal, sld._dec) + local ta = counterWidth(num) + local th = SUI.Scale(6) + local tx = 0 + local tw = math.max(1, w - ta - sld._gap) + local ty = math.floor(h*0.5 - th*0.5) + return tx, ty, tw, th, ta + end + + function sld:SetMin(v) self._min = tonumber(v) or self._min self:SetValue(self._value) end + function sld:SetMax(v) self._max = tonumber(v) or self._max self:SetValue(self._value) end + function sld:SetDecimals(d) self._dec = math.max(0, tonumber(d) or self._dec) end + + function sld:SetValue(v) + local nv = math.Clamp(tonumber(v) or self._value, self._min, self._max) + if nv == self._value then return end + self._value = nv + if isfunction(self.OnValueChanged) then self:OnValueChanged(nv) end + end + function sld:GetValue() return self._value end + + function sld:OnMousePressed(mc) + if mc ~= MOUSE_LEFT then return end + local lx, ly = self:LocalCursorPos() + local tx, ty, tw, th = trackBounds(self:GetWide(), self:GetTall()) + if lx >= tx and lx <= (tx+tw) and ly >= ty - SUI.Scale(8) and ly <= ty + th + SUI.Scale(8) then + self._dragging = true + self:MouseCapture(true) + local f = clamp01((lx - tx) / tw) + self:SetValue(valueFromFrac(f)) + end + end + + function sld:OnMouseReleased(mc) + if mc ~= MOUSE_LEFT then return end + self._dragging = false + self:MouseCapture(false) + end + + function sld:OnCursorMoved(x,y) + if not self._dragging then return end + local tx, _, tw = trackBounds(self:GetWide(), self:GetTall()) + local f = clamp01((x - tx) / tw) + self:SetValue(valueFromFrac(f)) + end + + function sld:OnMouseWheeled(dl) + local base = (self._max - self._min) * 0.01 + local gran = self._dec > 0 and (1 / (10 ^ self._dec)) or 1 + local step = math.max(base, gran) + self:SetValue(self._value + step * (dl > 0 and 1 or -1)) + end + + function sld:Think() + self._lerpVal = Lerp(FrameTime()*12, self._lerpVal or self._value, self._value) + local txt = fmtn(self._lerpVal, self._dec) + if txt ~= self._lastText then + self._lastText = txt + self._pulse = 1 + end + local hov = self:IsHovered() or self._dragging + self._hoverA = expApproach(self._hoverA, hov and 1 or 0, 10) + self._knobA = expApproach(self._knobA, self._dragging and 1 or (hov and 0.6 or 0), 12) + self._pulse = expApproach(self._pulse, 0, 8) + if self._dragging then self:SetCursor("sizewe") else self:SetCursor("hand") end + end + + function sld:Paint(w,h) + local tx, ty, tw, th = trackBounds(w,h) + local f = (self._max - self._min) == 0 and 0 or (self._lerpVal - self._min) / (self._max - self._min) + local kn = SUI.Scale(16) + SUI.Scale(6) * self._knobA + + SUI.Draw.Panel(tx, ty, tw, th, {radius = th/2, color = Color(30,34,42,130 + math.floor(30*self._knobA)), glass = true}) + + local minCenter = kn * 0.5 + local maxCenter = math.max(minCenter, tw - kn * 0.5) + local centerOff = math.Clamp(tw * f, minCenter, maxCenter) + local kx = math.floor(tx + centerOff) + local ky = math.floor(h*0.5 - kn*0.5) + + local fillW = math.max(th, math.min(tw, kx - tx)) + if not MAT_GRADIENT_L:IsError() then + local gcol = Color(self._tint.r, self._tint.g, self._tint.b, 170) + if RNDX and RNDX.DrawMaterial then RNDX.DrawMaterial(th/2, tx, ty, fillW, th, gcol, MAT_GRADIENT_L, 0) + else draw.RoundedBox(th/2, tx, ty, fillW, th, self._tint) end + else + draw.RoundedBox(th/2, tx, ty, fillW, th, self._tint) + end + + SUI.Draw.Panel(kx - kn/2, ky, kn, kn, {radius = kn/2, color = Color(240,240,255, math.floor(180 + 50*self._knobA)), glass = true, stroke = true}) + + local num = fmtn(self._lerpVal, self._dec) + local scale = 1 + 0.40 * self._pulse + local fs = SUI.Font(math.Clamp(math.floor(18 * scale), 10, 200)) + draw.SimpleText(num, fs, w - SUI.Scale(8), h/2, Style.textColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + sld._lastText = fmtn(sld._value, sld._dec) + return sld +end + +function Components.CreateDropdown(parent, opts) + opts = opts or {} + local combo = vgui.Create("DComboBox", parent) + combo._valueStr = "" + combo._placeholder = opts.placeholder or "Выберите…" + combo._tint = opts.tint or PalettePick("dropdown") + combo:SetText("") + if IsValid(combo.DropButton) then + combo.DropButton:SetText("") + combo.DropButton.Paint = function() end + end + function combo:SetText(_) end + function combo:SetValue(str) self._valueStr = tostring(str or "") end + function combo:GetValue() return self._valueStr or "" end + function combo:Paint(w,h) + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = Style.radius, color = Style.panelColor, glass = true, stroke = true}) + if not MAT_GRADIENT_L:IsError() then + local gw = w * 0.45 + local col = Color(self._tint.r, self._tint.g, self._tint.b, 90) + RNDX.DrawMaterial(Style.radius, 0, 0, gw, h, col, MAT_GRADIENT_L, 0) + end + local txt = (self._valueStr ~= "" and self._valueStr) or self._placeholder + draw.SimpleText(txt, libNyx.UI.Font(libNyx.UI.Scale(20)), Style.padding, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText("▼", libNyx.UI.Font(libNyx.UI.Scale(18)), w - Style.padding, h/2, Style.textColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + local function normalizeChoices() + local out, ch = {}, istable(opts.choices) and opts.choices or {} + for _, v in ipairs(ch) do + if istable(v) then + local lbl = v.label or v.text or v[1] or "" + local ic = v.icon or v[2] + if isstring(ic) then ic = Material(ic, "noclamp smooth") end + out[#out+1] = { label = tostring(lbl), icon = ic } + else + out[#out+1] = { label = tostring(v) } + end + end + return out + end + function combo:OpenMenu() + if IsValid(self.Menu) then self.Menu:Remove() self.Menu = nil end + local m = DermaMenu(self) + m:SetDrawOnTop(true) + m:SetPaintBackground(false) + m:SetPaintBorderEnabled(false) + local radius = math.max(Style.radius, libNyx.UI.Scale(12)) + local itemH = math.max(libNyx.UI.Scale(58), tonumber(opts.itemHeight) or 0) + local iconSize = libNyx.UI.Scale(22) + local fontOpt = libNyx.UI.Font(libNyx.UI.Scale(20)) + m._openT = SysTime() + m._dur = 0.18 + m._rev = 0 + m._tint = self._tint + m.Paint = function(s,w,h) + local t = math.TimeFraction(s._openT, s._openT + s._dur, SysTime()) + s._rev = math.Clamp(1 - (1 - t) ^ 3, 0, 1) + local clip = s._rev < 0.999 + if clip then + local sx, sy = s:LocalToScreen(0, 0) + local rh = math.max(1, h * s._rev) + render.SetScissorRect(sx, sy, sx + w, sy + rh, true) + end + libNyx.UI.Draw.Glass(0, 0, w, h, {radius = radius, fill = Color(16,18,24, 70), stroke = true, strokeColor = Color(255,255,255,22), blurIntensity = 1.35}) + if clip then render.SetScissorRect(0,0,0,0,false) end + end + for _, data in ipairs(normalizeChoices()) do + local label, icon = data.label, data.icon + local opt = m:AddOption(label, function() + self:SetValue(label) + if isfunction(opts.onSelect) then opts.onSelect(label) end + end) + opt:SetText("") + opt:SetTall(itemH) + opt._label = label + opt._icon = icon + opt._iconSize = iconSize + opt._tint = self._tint + makeRipple(opt,2) + function opt:OnCursorEntered() + local mx, my = gui.MousePos() + mx, my = self:ScreenToLocal(mx, my) + self._ripples = self._ripples or {} + table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()}) + surface.PlaySound("buttons/lightswitch2.wav") + end + opt.Paint = function(s, w, h) + local hovered = s:IsHovered() + local r = libNyx.UI.Scale(10) + libNyx.UI.Draw.Glass(0, 0, w, h, {radius = r, fill = Color(20,24,32, hovered and 95 or 65), stroke = false, blurIntensity = hovered and 1.20 or 0.95}) + if hovered and not MAT_GRADIENT_L:IsError() then + local gcol = Color(s._tint.r, s._tint.g, s._tint.b, 120) + RNDX.DrawMaterial(r, 0, 0, math.floor(w * 0.55), h, gcol, MAT_GRADIENT_L, 0) + end + local x = Style.padding + if s._icon and not s._icon:IsError() then + surface.SetDrawColor(Style.textColor) + surface.SetMaterial(s._icon) + surface.DrawTexturedRect(x, (h - s._iconSize)/2, s._iconSize, s._iconSize) + x = x + s._iconSize + Style.padding + end + draw.SimpleText(s._label or "", fontOpt, x, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + paintRipples(s, w, h, Style.textColor) + end + end + local x, y = self:LocalToScreen(0, self:GetTall() + 4) + m:Open() + m:SetMinimumWidth(self:GetWide()) + m:SetAlpha(0) + m:SetPos(x, y - libNyx.UI.Scale(8)) + m:MoveTo(x, y, m._dur, 0, 0.2) + m:AlphaTo(255, m._dur, 0) + self.Menu = m + return m + end + return combo +end + +function Components.CreateList(parent, opts) + opts = opts or {} + local list = vgui.Create("DScrollPanel", parent) + list._rowH = math.max(libNyx.UI.Scale(66), opts.rowHeight or Style.rowHeight) + list._rows = {} + local vbar = list:GetVBar() + vbar:SetWide(opts.vbarWidth or libNyx.UI.Scale(12)) + function vbar:Paint(w,h) draw.RoundedBox(4,0,0,w,h, Color(36,36,44,200)) end + function vbar.btnUp:Paint() end + function vbar.btnDown:Paint() end + function vbar.btnGrip:Paint(w,h) draw.RoundedBox(4,0,0,w,h, Style.accentColor) end + function list:SetRowHeight(h) self._rowH = math.max(libNyx.UI.Scale(66), h or self._rowH) end + function list:GetSelected() return self._selected end + function list:SetSelected(row) self._selected = row end + function list:ClearRows() + for _, r in ipairs(self._rows) do if IsValid(r) then r:Remove() end end + self._rows = {} + self._selected = nil + end + local function makeRowPaint(row) + row.Paint = function(self, w, h) + local base = (list:GetSelected() == self) and Color(Style.accentColor.r,Style.accentColor.g,Style.accentColor.b, 190) + or (self:IsHovered() and Style.hoverColor or Style.cardColor) + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = Style.radius, color = base, glass = true}) + if not self._plain and not MAT_GRADIENT_L:IsError() then + local gw = w * 0.5 + local tint = PalettePick(self._title or self._rightText or "row") + local col = Color(tint.r, tint.g, tint.b, Style.gradientAlphaRow) + RNDX.DrawMaterial(Style.radius, 0, 0, gw, h, col, MAT_GRADIENT_L, 0) + end + local pad = Style.padding + local x = pad + local y = pad + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(22))) + local rightW = 0 + if self._rightText and self._rightText ~= "" then + rightW = select(1, surface.GetTextSize(self._rightText)) + pad + end + local rightLimitX = w - pad - rightW + if self._icon and not self._icon:IsError() then + local ic = math.max(libNyx.UI.Scale(24), Style.iconSize) + surface.SetDrawColor(255,255,255) + surface.SetMaterial(self._icon) + surface.DrawTexturedRect(x, (h - ic)/2, ic, ic) + x = x + ic + pad + end + draw.SimpleText(self._title or "Без названия", libNyx.UI.Font(libNyx.UI.Scale(26)), x, y + libNyx.UI.Scale(2), Style.textColor) + if istable(self._labels) and #self._labels > 0 then + local chipH = libNyx.UI.Scale(26) + local chipY = h - pad - chipH + local cx = x + local hidden = 0 + surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(18))) + for i, chip in ipairs(self._labels) do + local t = chip.text or tostring(chip) or "" + local tw = select(1, surface.GetTextSize(t)) + local cw, ch = tw + libNyx.UI.Scale(18), chipH + if (cx + cw) > rightLimitX then + hidden = (#self._labels - i + 1) + break + end + local baseCol = chip.color or PalettePick(t) + local fill = Color( math.max(0, baseCol.r - 10), math.max(0, baseCol.g - 10), math.max(0, baseCol.b - 10), 175 ) + local chipR = libNyx.UI.Scale(9) + libNyx.UI.Draw.Panel(cx, chipY, cw, ch, {radius = chipR, color = fill, glass = true, stroke = true, strokeColor = Color(255,255,255,18)}) + if not MAT_GRADIENT_L:IsError() then + local gw = cw * 0.65 + local gcol = Color(baseCol.r, baseCol.g, baseCol.b, Style.gradientAlphaChip) + RNDX.DrawMaterial(chipR, cx, chipY, gw, ch, gcol, MAT_GRADIENT_L, 0) + end + draw.SimpleText(t, libNyx.UI.Font(libNyx.UI.Scale(18)), cx + cw/2, chipY + ch/2, chip.textColor or Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cx = cx + cw + libNyx.UI.Scale(6) + end + if hidden > 0 and (cx + libNyx.UI.Scale(34)) <= rightLimitX then + local t = "+" .. hidden + local tw = select(1, surface.GetTextSize(t)) + local cw, ch = tw + libNyx.UI.Scale(18), chipH + local chipR = libNyx.UI.Scale(9) + libNyx.UI.Draw.Panel(cx, chipY, cw, ch, {radius = chipR, color = Color(36,36,44,175), glass = true, stroke = true, strokeColor = Color(255,255,255,18)}) + if not MAT_GRADIENT_L:IsError() then + local gw = cw * 0.65 + local gcol = Color(Style.accentColor.r, Style.accentColor.g, Style.accentColor.b, Style.gradientAlphaChip) + RNDX.DrawMaterial(chipR, cx, chipY, gw, ch, gcol, MAT_GRADIENT_L, 0) + end + draw.SimpleText(t, libNyx.UI.Font(libNyx.UI.Scale(18)), cx + cw/2, chipY + ch/2, Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + if self._rightText and self._rightText ~= "" then + draw.SimpleText(self._rightText, libNyx.UI.Font(libNyx.UI.Scale(28)), w - pad, h/2, Style.textColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + paintRipples(self, w, h, Style.textColor) + end + end + function list:AddRow(data) + data = data or {} + local row = list:Add("DButton") + row:Dock(TOP) + row:DockMargin(0, 0, 0, libNyx.UI.Scale(10)) + row:SetTall(self._rowH) + row:SetText("") + row._title = data.title or "" + row._subtitle = data.subtitle + row._icon = data.icon + row._labels = istable(data.labels) and data.labels or {} + row._rightText = data.rightText + row._onClick = data.onClick + row._plain = data.gradient == false or data.plain == true + makeRipple(row) + makeRowPaint(row) + function row:DoClick() + list:SetSelected(self) + if isfunction(self._onClick) then self._onClick(self) end + end + function row:OnCursorEntered() + local mx, my = gui.MousePos() + mx, my = self:ScreenToLocal(mx, my) + table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()}) + surface.PlaySound("buttons/lightswitch2.wav") + end + table.insert(self._rows, row) + return row + end + return list +end + +function Components.CreateVBox(parent, opts) + opts = opts or {} + local w = opts.w or libNyx.UI.Scale(140) + local h = opts.h or libNyx.UI.Scale(200) + local pnl = vgui.Create("DButton", parent) + pnl:SetText("") + pnl:SetSize(w, h) + pnl._variant = (opts.variant or "center_gradient") + pnl._tint = opts.tint or Color(140,120,255) + pnl._title = opts.title or "" + pnl._icon = opts.icon + pnl._iconSize = opts.iconSize or libNyx.UI.Scale(40) + pnl._onClick = opts.onClick + pnl._phase = 0 + pnl._hover = false + pnl._hoverA = 0 + pnl._pulseT = 0 + pnl._titleFont = libNyx.UI.Font(libNyx.UI.Scale(22)) + pnl._modelA = 0 + pnl._spinA = 0 + if isstring(pnl._icon) then pnl._icon = Material(pnl._icon, "noclamp smooth") end + + function pnl:DoClick() + surface.PlaySound("nyx_uniqueui/nyxclick_3.wav") + if isfunction(self._onClick) then self._onClick(self) end + end + function pnl:OnCursorEntered() + self._hover = true + surface.PlaySound("nyx_uniqueui/nyxclick_2.wav") + end + function pnl:OnCursorExited() + self._hover = false + end + + local wantModel = (pnl._variant == "model") or (pnl._variant == "vertical_gradient" and isstring(opts.model) and opts.model ~= "") + if wantModel then + pnl._mp = vgui.Create("DModelPanel", pnl) + pnl._mp:SetSize(w, h) + pnl._mp:SetPos(0, 0) + pnl._mp:SetModel(opts.model or "models/props_c17/oildrum001.mdl") + pnl._mp:SetFOV(28) + function pnl._mp:LayoutEntity(ent) end + pnl._baseCam = nil + local function FitModelLocal(mp) + if not IsValid(mp) or not IsValid(mp.Entity) then return end + local mn, mx = mp.Entity:GetRenderBounds() + local size = mx - mn + local center = (mn + mx) * 0.5 + local maxdim = math.max(size.x, size.y, size.z) + local ang = Angle(12, 25, 0) + local fov = mp:GetFOV() + local dist = (maxdim * 0.52) / math.tan(math.rad(fov * 0.5)) + local camPos = center + ang:Forward() * -dist + Vector(0, 0, size.z * 0.04) + mp:SetCamPos(camPos) + mp:SetLookAt(center) + mp:SetAmbientLight(Vector(255, 255, 255)) + mp:SetDirectionalLight(BOX_TOP, Color(255,255,255)) + mp:SetDirectionalLight(BOX_FRONT, Color(pnl._tint.r, pnl._tint.g, pnl._tint.b)) + mp:SetDirectionalLight(BOX_RIGHT, Color(210,220,255)) + mp:SetDirectionalLight(BOX_LEFT, Color(210,210,210)) + mp:SetDirectionalLight(BOX_BACK, Color(190,200,220)) + mp:SetDirectionalLight(BOX_BOTTOM, Color(160,170,180)) + pnl._baseCam = {center = center, dist = dist, ang = ang} + end + timer.Simple(0, function() if IsValid(pnl._mp) then FitModelLocal(pnl._mp) end end) + end + + local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth") + local MAT_GRADIENT_R = Material("vgui/gradient-r", "noclamp smooth") + local function lighten(c, k) + return Color( + math.Clamp(c.r + (255 - c.r) * k, 0, 255), + math.Clamp(c.g + (255 - c.g) * k, 0, 255), + math.Clamp(c.b + (255 - c.b) * k, 0, 255), + c.a or 255 + ) + end + local function expApproach(cur, tgt, spd) + local k = 1 - math.exp(-(spd or 10) * FrameTime()) + return cur + (tgt - cur) * k + end + + pnl.Think = function(self) + self._hoverA = expApproach(self._hoverA, self._hover and 1 or 0, 8) + self._pulseT = (self._pulseT + FrameTime() * (0.8 + 1.2 * self._hoverA)) % (math.pi * 2) + if self._variant == "sunburst" then + local sp = Lerp(self._hoverA, 0.35, 1.1) + self._phase = (self._phase + FrameTime() * sp) % (math.pi * 2) + end + if IsValid(self._mp) and self._baseCam then + self._modelA = expApproach(self._modelA, self._hover and 1 or 0, 6) + self._spinA = expApproach(self._spinA, self._hover and 1 or 0, 5) + local distMul = Lerp(self._modelA, 1.0, 0.75) + local yawJig = math.sin(CurTime() * (0.6 + 1.0 * self._spinA)) * 6 * self._spinA + local ang = Angle(self._baseCam.ang.p, self._baseCam.ang.y + yawJig, self._baseCam.ang.r) + local camPos = self._baseCam.center + ang:Forward() * -(self._baseCam.dist * distMul) + self._mp:SetCamPos(camPos) + self._mp:SetLookAt(self._baseCam.center) + self._mp:SetFOV(Lerp(self._modelA, 28, 26)) + end + end + + pnl.Paint = function(self, w, h) + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = Style.radius, color = Color(0,0,0,0), glass = true, stroke = true}) + local cx, cy = w * 0.5, h * 0.5 + if self._variant == "center_gradient" then + local base = self._tint + local light = lighten(base, 0.26) light.a = math.floor(Lerp(self._hoverA, 120, 155)) + local coverage = Lerp(self._hoverA, 0.80, 0.92) + local halfCov = math.floor(w * coverage * 0.5) + local cxpix = math.floor(w * 0.5 + math.sin(self._pulseT) * libNyx.UI.Scale(4) * self._hoverA) + local overlap = 2 + RNDX.DrawMaterial(0, cxpix - halfCov - overlap, 0, halfCov + overlap, h, light, MAT_GRADIENT_R, 0) + RNDX.DrawMaterial(0, cxpix, 0, halfCov + overlap, h, light, MAT_GRADIENT_L, 0) + elseif self._variant == "vertical_gradient" then + local base = self._tint + local col = Color( + math.Clamp(base.r + (255 - base.r) * Lerp(self._hoverA, 0.18, 0.28), 0, 255), + math.Clamp(base.g + (255 - base.g) * Lerp(self._hoverA, 0.18, 0.28), 0, 255), + math.Clamp(base.b + (255 - base.b) * Lerp(self._hoverA, 0.18, 0.28), 0, 255), + Lerp(self._hoverA, 120, 165) + ) + local gh = math.floor(h * Lerp(self._hoverA, 0.65, 0.78)) + local y0 = h - gh - math.floor(libNyx.UI.Scale(4) * self._hoverA) + local MAT_GRADIENT_U = Material("vgui/gradient-u", "noclamp smooth") + local rect = RNDX().Rect(0, y0, w, gh):Radii(0, 0, Style.radius, Style.radius):Material(MAT_GRADIENT_U):Color(col) + local mat = rect:GetMaterial() + surface.SetMaterial(mat) + surface.SetDrawColor(col) + surface.DrawTexturedRectUV(0, y0, w, gh, 0, 1, 1, 0) + elseif self._variant == "sunburst" then + local rays, step = 18, (math.pi * 2) / 18 + local R = math.max(w, h) + local a1 = math.floor(Lerp(self._hoverA, 95, 140)) + local a2 = math.floor(Lerp(self._hoverA, 40, 80)) + local col1 = Color(self._tint.r, self._tint.g, self._tint.b, a1) + local col2 = Color(self._tint.r, self._tint.g, self._tint.b, a2) + draw.NoTexture() + for i = 0, rays - 1 do + local a0 = self._phase + i * step + local a1r = a0 + step * Lerp(self._hoverA, 0.55, 0.70) + local v = { + {x = cx, y = cy}, + {x = cx + math.cos(a0) * R, y = cy + math.sin(a0) * R}, + {x = cx + math.cos(a1r) * R, y = cy + math.sin(a1r) * R}, + } + surface.SetDrawColor((i % 2 == 0) and col1 or col2) + surface.DrawPoly(v) + end + end + if not wantModel and self._icon and not self._icon:IsError() then + local s = self._iconSize * (1 + 0.08 * self._hoverA + 0.04 * math.sin(self._pulseT)) + surface.SetDrawColor(Style.textColor) + surface.SetMaterial(self._icon) + surface.DrawTexturedRect(cx - s/2, cy - s/2 - libNyx.UI.Scale(10), s, s) + end + end + + pnl.PaintOver = function(self, w, h) + local padX = libNyx.UI.Scale(10) + local padY = libNyx.UI.Scale(6) + local txt = self._title or "" + surface.SetFont(self._titleFont) + local tw, th = surface.GetTextSize(txt) + local bw, bh = tw + padX * 2, th + padY * 2 + local x = math.floor((w - bw) / 2) + local y = h - Style.padding - bh - math.floor(libNyx.UI.Scale(2) * self._hoverA) + libNyx.UI.Draw.Glass(x, y, bw, bh, {radius = libNyx.UI.Scale(8), fill = Color(16,18,24, math.floor(150 + 30 * self._hoverA)), stroke = true, strokeColor = Color(255,255,255,20), blurIntensity = 1.0}) + draw.SimpleText(txt, self._titleFont, x + bw/2, y + bh/2, Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + return pnl +end + +function Components.CreateTabs(parent, opts) + opts = opts or {} + + local pnl = vgui.Create("DPanel", parent) + pnl._items = istable(opts.items) and opts.items or {} + pnl._active = opts.default + pnl._onChange = opts.onChange + + pnl:SetPaintBackground(false) + pnl:SetPaintBorderEnabled(false) + pnl:SetPaintBackgroundEnabled(false) + pnl.Paint = nil + + local font = libNyx.UI.Font(libNyx.UI.Scale(18)) + local tabH = math.max(libNyx.UI.Scale(42), tonumber(opts.height or 0)) + local padY = libNyx.UI.Scale(6) + local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth") + + local rail = vgui.Create("DPanel", pnl) + rail:Dock(FILL) + rail:DockMargin(Style.padding, padY, Style.padding, padY) + rail:SetPaintBackground(false) + rail:SetPaintBorderEnabled(false) + rail:SetPaintBackgroundEnabled(false) + rail.Paint = nil + + pnl._btns = {} + rail._indX = 0 + rail._indW = 0 + rail._indH = tabH + rail._tgtX = 0 + rail._tgtW = 0 + rail._tgtH = tabH + rail._haveInd = false + + local function syncIndicatorTo(id, snap) + local b = pnl._btns[id] + if not IsValid(b) then rail._haveInd = false return end + local x, y = b:GetPos() + local w, h = b:GetSize() + rail._tgtX = x + rail._tgtW = w + rail._tgtH = tabH + rail._haveInd = true + if snap then + rail._indX = rail._tgtX + rail._indW = rail._tgtW + rail._indH = rail._tgtH + end + rail:InvalidateLayout(false) + end + + local function expApproach(cur, tgt, speed) + local k = 1 - math.exp(-(speed or 12) * FrameTime()) + return cur + (tgt - cur) * k + end + + rail.Think = function(s) + if not s._haveInd then return end + local hovering = false + for _, b in pairs(pnl._btns) do + if IsValid(b) and b:IsHovered() then hovering = true break end + end + local grow = libNyx.UI.Scale(6) + s._tgtH = hovering and (tabH + grow) or tabH + s._indX = expApproach(s._indX or s._tgtX, s._tgtX, 10) + s._indW = expApproach(s._indW or s._tgtW, s._tgtW, 12) + s._indH = expApproach(s._indH or s._tgtH, s._tgtH, 9) + end + + rail.Paint = function(s, w, h) + if not s._haveInd then return end + local r = libNyx.UI.Scale(10) + local bx = math.floor(s._indX + 0.5) + local bw = math.floor(s._indW + 0.5) + local bh = math.floor(s._indH + 0.5) + local by = math.floor((tabH - bh) * 0.5) + libNyx.UI.Draw.Panel(bx, by, bw, bh, {radius = r, color = Color(32,38,48,135), glass = true}) + if not MAT_GRADIENT_L:IsError() then + local c = Color(Style.accentColor.r, Style.accentColor.g, Style.accentColor.b, 120) + RNDX.DrawMaterial(r, bx, by, bw, bh, c, MAT_GRADIENT_L, 0) + end + end + + local function makeBtn(it, idx) + local b = vgui.Create("DButton", rail) + b:SetText("") + b:SetDrawBackground(false) + b:SetPaintBackgroundEnabled(false) + b:SetPaintBorderEnabled(false) + b:Dock(LEFT) + b:DockMargin(libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6)) + b:SetTall(tabH) + + b._id = tostring(it.id or it.label or idx or "") + b._label = tostring(it.label or b._id) + b._icon = isstring(it.icon) and Material(it.icon, "noclamp smooth") or it.icon + b._iconSize = libNyx.UI.Scale(18) + + surface.SetFont(font) + local tw = surface.GetTextSize(b._label) + local padX = libNyx.UI.Scale(16) + local icw = (b._icon and not b._icon:IsError()) and (b._iconSize + libNyx.UI.Scale(6)) or 0 + b:SetWide(padX * 2 + icw + tw) + + makeRipple(b, 2) + function b:OnCursorEntered() + local mx, my = gui.MousePos() + mx, my = self:ScreenToLocal(mx, my) + self._ripples = self._ripples or {} + table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()}) + if libNyx.UI and libNyx.UI.PlayHover then libNyx.UI.PlayHover() end + end + + function b:DoClick() + pnl:SetActive(self._id) + if isfunction(pnl._onChange) then pnl._onChange(self._id, self) end + surface.PlaySound("ui/buttonclickrelease.wav") + end + + b.Paint = function(s, w, h) + local selected = (pnl._active == s._id) + local r = libNyx.UI.Scale(10) + if not selected and s:IsHovered() then + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(30,34,42,90), glass = true}) + end + local x = padX + if s._icon and not s._icon:IsError() then + surface.SetDrawColor(Style.textColor.r, Style.textColor.g, Style.textColor.b, selected and 255 or 200) + surface.SetMaterial(s._icon) + surface.DrawTexturedRect(x, (h - s._iconSize) / 2, s._iconSize, s._iconSize) + x = x + s._iconSize + libNyx.UI.Scale(6) + end + draw.SimpleText( + s._label, font, x, h / 2, + selected and Style.textColor or Color(Style.textColor.r, Style.textColor.g, Style.textColor.b, 200), + TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER + ) + paintRipples(s, w, h, Style.textColor) + end + + pnl._btns[b._id] = b + return b + end + + function pnl:SetItems(items) + for _, c in ipairs(rail:GetChildren()) do if IsValid(c) then c:Remove() end end + pnl._btns = {} + pnl._items = items or {} + for i, it in ipairs(pnl._items) do makeBtn(it, i) end + rail:InvalidateLayout(true) + timer.Simple(0, function() + if not IsValid(pnl) then return end + local first = pnl._active + if not first and #pnl._items > 0 then + first = pnl._items[1].id or pnl._items[1].label + pnl._active = first + end + syncIndicatorTo(pnl._active, true) + end) + end + + function pnl:SetActive(id) + pnl._active = id + syncIndicatorTo(id, false) + rail:InvalidateLayout(false) + end + + function pnl:GetActive() + return pnl._active + end + + pnl.SetOnChange = function(self, fn) self._onChange = fn end + + pnl:SetItems(pnl._items) + if pnl._active == nil and #pnl._items > 0 then + pnl:SetActive(pnl._items[1].id or pnl._items[1].label) + else + pnl:SetActive(pnl._active) + end + + return pnl +end + +function Components.CreateCategoryCard(parent, opts) + opts = opts or {} + local card = vgui.Create("DButton", parent) + card:SetText("") + card:SetTall(opts.h or libNyx.UI.Scale(120)) + card._title = opts.title or "Category" + card._desc = opts.desc or "—" + card._icon = opts.icon + if isstring(card._icon) then + card._icon = Material(card._icon, "noclamp smooth") + end + if not (card._icon and not card._icon:IsError()) then + card._icon = Material("icon16/star.png", "noclamp smooth") + end + card._from = opts.from or Color(125, 82,255) + card._to = opts.to or Color( 40,192,255) + card._variant = opts.variant or "vibrant" + card._radius = opts.radius or libNyx.UI.Scale(18) + card._onClick = opts.onClick + card._titleFont = libNyx.UI.Font(libNyx.UI.Scale(30)) + card._descFont = libNyx.UI.Font(libNyx.UI.Scale(17)) + card._hoverA = 0 + makeRipple(card) + function card:DoClick() + if libNyx.UI and libNyx.UI.PlayClick then libNyx.UI.PlayClick() end + if isfunction(self._onClick) then self._onClick(self) end + end + function card:OnCursorEntered() + local mx, my = gui.MousePos() + mx, my = self:ScreenToLocal(mx, my) + self._ripples = self._ripples or {} + table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()}) + if libNyx.UI and libNyx.UI.PlayHover then libNyx.UI.PlayHover() end + end + local function expApproach(cur, tgt, spd) + local k = 1 - math.exp(-(spd or 10) * FrameTime()) + return cur + (tgt - cur) * k + end + local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth") + local MAT_GRADIENT_R = Material("vgui/gradient-r", "noclamp smooth") + local function lighten(c, k) + return Color( + math.Clamp(c.r + (255 - c.r) * k, 0, 255), + math.Clamp(c.g + (255 - c.g) * k, 0, 255), + math.Clamp(c.b + (255 - c.b) * k, 0, 255), + c.a or 255 + ) + end + card.Think = function(self) + self._hoverA = expApproach(self._hoverA or 0, self:IsHovered() and 1 or 0, 10) + end + card.Paint = function(self, w, h) + local r = self._radius + local a = math.Clamp(self._hoverA or 0, 0, 1) + if self._variant == "glass" then + local fillA = math.floor(Lerp(a, 120, 145)) + local strokeA = math.floor(Lerp(a, 20, 32)) + libNyx.UI.Draw.Glass(0, 0, w, h, {radius = r, fill = Color(20,24,32, fillA), stroke = true, strokeColor = Color(255,255,255, strokeA), blurIntensity = 1.1 + 0.15 * a}) + if not MAT_GRADIENT_L:IsError() then + local c1 = Color(self._from.r, self._from.g, self._from.b, math.floor(Lerp(a, 95, 140))) + local c2 = Color(self._to.r, self._to.g, self._to.b, math.floor(Lerp(a, 95, 140))) + RNDX.DrawMaterial(r, 0, 0, w*0.65, h, c1, MAT_GRADIENT_L, 0) + RNDX.DrawMaterial(r, w*0.35, 0, w*0.65, h, c2, MAT_GRADIENT_R, 0) + end + else + local baseCol = Color(self._from.r, self._from.g, self._from.b, math.floor(Lerp(a, 220, 235))) + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = baseCol, shadow = true}) + if not (MAT_GRADIENT_L:IsError() or MAT_GRADIENT_R:IsError()) then + local colL = Color(self._to.r, self._to.g, self._to.b, 255) + local colR = Color(self._from.r, self._from.g, self._from.b, 255) + RNDX.DrawMaterial(r, 0, 0, w, h, colL, MAT_GRADIENT_L, 0) + RNDX.DrawMaterial(r, 0, 0, w, h, colR, MAT_GRADIENT_R, 0) + end + local outlineA = math.floor(18 * a) + if outlineA > 0 then + RNDX.DrawOutlined(r, 0, 0, w, h, Color(255,255,255, outlineA), 1) + end + end + if self._icon and not self._icon:IsError() then + local sz = math.floor(h * 1.25) + local s = 1 + 0.06 * a + local sz2 = math.floor(sz * s) + local oy = -libNyx.UI.Scale(4) * a + local alpha = (self._variant == "vibrant") and math.floor(Lerp(a, 44, 64)) or math.floor(Lerp(a, 36, 54)) + surface.SetDrawColor(255,255,255, alpha) + surface.SetMaterial(self._icon) + surface.DrawTexturedRect(w - sz2 + libNyx.UI.Scale(28), (h - sz2) / 2 + oy, sz2, sz2) + end + local padX = libNyx.UI.Scale(18) + local topY = libNyx.UI.Scale(18) + surface.SetFont(self._titleFont) + local _, th = surface.GetTextSize(self._title or "") + local titleCol = lighten(Style.textColor, a * 0.08) + draw.SimpleText(self._title, self._titleFont, padX, topY + th/2, titleCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + surface.SetFont(self._descFont) + local _, dh = surface.GetTextSize(self._desc or "") + draw.SimpleText(self._desc or "", self._descFont, padX, topY + th + libNyx.UI.Scale(6) + dh/2, Color(255,255,255, math.floor(Lerp(a, 200, 230))), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + local dashW, dashH = libNyx.UI.Scale(18), libNyx.UI.Scale(4) + local dashA = math.floor(Lerp(a, 70, 110)) + libNyx.UI.Draw.Panel(padX, h - libNyx.UI.Scale(18), dashW, dashH, {radius = dashH/2, color = Color(255,255,255, dashA)}) + paintRipples(self, w, h, Style.textColor) + end + return card +end + +function Components.CreateSearchBox(parent, opts) + opts = opts or {} + local box = vgui.Create("DPanel", parent) + box:SetTall(opts.h or libNyx.UI.Scale(38)) + box._radius = opts.radius or libNyx.UI.Scale(12) + box._placeholder = opts.placeholder or "Search" + box._tint = opts.tint or Style.accentColor + box._debounce = tonumber(opts.debounce or 0.12) + box._focusA = 0 + box._gradA = 0 + box._langID = "EN" + box._lastText = "" + local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth") + local MAT_ICON_SEARCH = Material("icon16/magnifier.png", "noclamp smooth") + local MAT_ICON_CLEAR = Material("icon16/cross.png", "noclamp smooth") + + local btnClear = vgui.Create("DButton", box) + btnClear:SetText("") + btnClear:Dock(RIGHT) + btnClear:SetWide(box:GetTall()) + btnClear.Paint = function(s,w,h) + if not IsValid(box.Entry) or (box.Entry:GetText() == "" or box.Entry:GetText() == nil) then return end + if s:IsHovered() then + libNyx.UI.Draw.Panel(libNyx.UI.Scale(6), libNyx.UI.Scale(5), w-libNyx.UI.Scale(12), h-libNyx.UI.Scale(10), {radius = (h-libNyx.UI.Scale(10))/2, color = Color(30,34,42,110), glass = true}) + end + surface.SetDrawColor(Style.textColor.r, Style.textColor.g, Style.textColor.b, 210) + local ic = libNyx.UI.Scale(14) + surface.SetMaterial(MAT_ICON_CLEAR) + surface.DrawTexturedRect((w-ic)/2, (h-ic)/2, ic, ic) + end + btnClear.DoClick = function() + if not IsValid(box.Entry) then return end + box.Entry:SetText("") + if isfunction(opts.onClear) then opts.onClear() end + if isfunction(opts.onChange) then opts.onChange("") end + end + + local entry = vgui.Create("DTextEntry", box) + entry:SetUpdateOnType(true) + entry:SetText("") + entry:SetFont(libNyx.UI.Font(libNyx.UI.Scale(18))) + entry:SetTextColor(Style.textColor) + entry:SetCursorColor(Style.textColor) + entry:SetHistoryEnabled(false) + if entry.SetDrawLanguageID then entry:SetDrawLanguageID(false) end + box.Entry = entry + + function box:PerformLayout(w,h) + local leftPad = libNyx.UI.Scale(12) + libNyx.UI.Scale(16) + libNyx.UI.Scale(8) + local rightPad = btnClear:GetWide() + libNyx.UI.Scale(6) + entry:SetPos(leftPad, 0) + entry:SetSize(w - leftPad - rightPad, h) + end + + function entry:Paint(w,h) + self:DrawTextEntryText(Style.textColor, Color(255,255,255,20), Style.textColor) + end + + function entry:OnChange() + if not isfunction(opts.onChange) then return end + local name = "libnyx_search_" .. tostring(box) + timer.Create(name, box._debounce, 1, function() + if IsValid(box) and IsValid(box.Entry) then + opts.onChange(box.Entry:GetText() or "") + end + end) + end + + function entry:OnEnter() + if isfunction(opts.onSubmit) then opts.onSubmit(self:GetText() or "") end + end + + function entry:OnGetFocus() + box._focused = true + end + + function entry:OnLoseFocus() + box._focused = false + end + + local function expApproach(cur, tgt, spd) + local k = 1 - math.exp(-(spd or 10) * FrameTime()) + return cur + (tgt - cur) * k + end + + local function isCyrillicChar(ch) + if not ch or ch == "" then return false end + if utf8 and utf8.codepoint then + local cp = utf8.codepoint(ch) + if cp then + if (cp >= 0x0400 and cp <= 0x04FF) or (cp >= 0x0500 and cp <= 0x052F) then return true end + end + end + return false + end + + box.Think = function(self) + local f = self._focused and 1 or 0 + self._focusA = expApproach(self._focusA, f, 9) + self._gradA = expApproach(self._gradA, f, 7) + local id + if entry.GetLanguageID then + id = entry:GetLanguageID() + if isstring(id) and #id >= 2 then + id = string.upper(string.sub(id,1,2)) + else + id = nil + end + end + if not id then + local t = entry:GetText() or "" + if t ~= self._lastText then + self._lastText = t + local len = (utf8 and utf8.len and utf8.len(t)) or #t + if len > 0 and utf8 and utf8.sub then + local ch = utf8.sub(t, len, len) + if isCyrillicChar(ch) then + id = "RU" + end + end + end + end + if id then self._langID = id end + end + + function box:Paint(w, h) + libNyx.UI.Draw.Glass(0, 0, w, h, {radius = self._radius, fill = Color(16,18,24,110), stroke = true, strokeColor = Style.glassStroke, blurIntensity = 1.0}) + local baseCov = 0.45 + local focusedCov = 0.34 + local cov = Lerp(self._gradA, baseCov, focusedCov) + if not MAT_GRADIENT_L:IsError() then + local gcol = Color(self._tint.r, self._tint.g, self._tint.b, 70) + local gw = w * cov + RNDX.DrawMaterial(self._radius, 0, 0, gw, h, gcol, MAT_GRADIENT_L, 0) + end + local ic = libNyx.UI.Scale(16) + local ix = libNyx.UI.Scale(12) + surface.SetDrawColor(Style.textColor.r, Style.textColor.g, Style.textColor.b, 190) + surface.SetMaterial(MAT_ICON_SEARCH) + surface.DrawTexturedRect(ix, (h-ic)/2, ic, ic) + if (entry:GetText() == "" or entry:GetText() == nil) and not entry:HasFocus() then + draw.SimpleText(self._placeholder, libNyx.UI.Font(libNyx.UI.Scale(18)), ix + ic + libNyx.UI.Scale(8), h/2, Color(255,255,255,120), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + local focusAlpha = math.floor(180 * self._focusA) + if focusAlpha > 1 then + local sw = math.max(1, math.floor(1 + 2 * self._focusA)) + RNDX.DrawOutlined(self._radius, 0, 0, w, h, Color(self._tint.r, self._tint.g, self._tint.b, focusAlpha), sw, 0) + end + if entry:HasFocus() then + local code = self._langID or "EN" + local showRU = code == "RU" + local rightPad = btnClear:GetWide() + libNyx.UI.Scale(6) + local rx = w - rightPad - libNyx.UI.Scale(6) + if showRU then + local ph = libNyx.UI.Scale(18) + local px = rx - libNyx.UI.Scale(4) + local py = (h - ph) / 2 + local pw = libNyx.UI.Scale(28) + libNyx.UI.Draw.Panel(px - pw, py, pw, ph, {radius = ph/2, color = Color(30,34,42,160), glass = true}) + draw.SimpleText("RU", libNyx.UI.Font(libNyx.UI.Scale(14)), px - pw/2, h/2, Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText(code, libNyx.UI.Font(libNyx.UI.Scale(14)), rx, h/2, Color(255,255,255,150), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end + end + + function box:GetText() return entry:GetText() end + function box:SetText(t) entry:SetText(t or "") end + function box:Focus() entry:RequestFocus() end + return box +end + +libNyx.UI.SmoothScroll = libNyx.UI.SmoothScroll or {} + +function libNyx.UI.SmoothScroll.ApplyToScrollPanel(sp, opts) + if not IsValid(sp) or sp._libnyxSmoothInstalled then return end + local vbar = sp:GetVBar() + if not IsValid(vbar) then return end + sp._libnyxSmoothInstalled = true + local step = tonumber(opts and opts.step) or libNyx.UI.Scale(90) + local speed = tonumber(opts and opts.speed) or 18 + local fadeHold = tonumber(opts and opts.fadeHold) or 0.9 + local barW = tonumber(opts and opts.width) or libNyx.UI.Scale(12) + local r = libNyx.UI.Scale(6) + vbar:SetWide(barW) + sp._ss = sp._ss or {} + sp._ss.cur = vbar:GetScroll() + sp._ss.tgt = sp._ss.cur + sp._ss.visA = 0 + sp._ss.lastPing = 0 + sp._ss.drag = false + + local function expApproach(cur, tgt, spd) + local k = 1 - math.exp(-(spd) * FrameTime()) + return cur + (tgt - cur) * k + end + local function maxScroll() + local can = IsValid(sp:GetCanvas()) and sp:GetCanvas():GetTall() or 0 + return math.max(0, can - sp:GetTall()) + end + local function ping() + sp._ss.lastPing = CurTime() + end + + local oldAddScroll = vbar.AddScroll + vbar.AddScroll = function(self, dlta) + local m = maxScroll() + sp._ss.tgt = math.Clamp(sp._ss.tgt + dlta * step, 0, m) + ping() + end + + local oldOnMousePressed = vbar.OnMousePressed + vbar.OnMousePressed = function(self, mc) + sp._ss.drag = true + if isfunction(oldOnMousePressed) then oldOnMousePressed(self, mc) end + ping() + end + local oldOnMouseReleased = vbar.OnMouseReleased + vbar.OnMouseReleased = function(self, mc) + sp._ss.drag = false + if isfunction(oldOnMouseReleased) then oldOnMouseReleased(self, mc) end + ping() + end + if IsValid(vbar.btnGrip) then + local og = vbar.btnGrip.OnMousePressed + vbar.btnGrip.OnMousePressed = function(s, mc) + sp._ss.drag = true + if isfunction(og) then og(s, mc) end + ping() + end + local org = vbar.btnGrip.OnMouseReleased + vbar.btnGrip.OnMouseReleased = function(s, mc) + sp._ss.drag = false + if isfunction(org) then org(s, mc) end + ping() + end + end + + local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth") + vbar.Paint = function(s, w, h) + local a = math.floor(130 * sp._ss.visA) + if a <= 0 then return end + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(36,36,44, a), glass = true}) + end + vbar.btnUp.Paint = function() end + vbar.btnDown.Paint = function() end + vbar.btnGrip.Paint = function(s, w, h) + local a = math.floor(200 * sp._ss.visA) + if a <= 4 then return end + libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(24,28,36, a + 35), glass = true, stroke = true, strokeColor = Color(255,255,255, 16)}) + if not MAT_GRADIENT_L:IsError() then + local c = Style.accentColor + local g = Color(c.r, c.g, c.b, math.Clamp(110 + math.floor(100 * sp._ss.visA), 80, 200)) + RNDX.DrawMaterial(r, 0, 0, w, h, g, MAT_GRADIENT_L, 0) + end + end + + local oldThink = sp.Think + sp.Think = function(self) + if isfunction(oldThink) then oldThink(self) end + local m = maxScroll() + sp._ss.tgt = math.Clamp(sp._ss.tgt, 0, m) + if sp._ss.drag then + sp._ss.cur = vbar:GetScroll() + sp._ss.tgt = sp._ss.cur + else + sp._ss.cur = expApproach(sp._ss.cur, sp._ss.tgt, speed) + if math.abs(sp._ss.cur - vbar:GetScroll()) > 0.5 then + vbar:SetScroll(sp._ss.cur) + end + end + local want = 0 + if vbar:IsHovered() or (IsValid(vbar.btnGrip) and vbar.btnGrip:IsHovered()) or sp._ss.drag or (CurTime() - sp._ss.lastPing) < fadeHold then + want = 1 + end + sp._ss.visA = expApproach(sp._ss.visA, want, 12) + end +end + +function libNyx.UI.SmoothScroll.InstallUnder(root, opts) + if not IsValid(root) then return end + local function apply(p) + if not IsValid(p) then return end + if isfunction(p.GetVBar) and IsValid(p:GetVBar()) and p:GetVBar().SetUp then + libNyx.UI.SmoothScroll.ApplyToScrollPanel(p, opts) + end + for _, ch in ipairs(p:GetChildren()) do apply(ch) end + local old = p.OnChildAdded + p.OnChildAdded = function(s, child) + if isfunction(old) then old(s, child) end + apply(child) + end + end + apply(root) +end + +function libNyx.UI.InstallGlobalScroll(root, opts) + libNyx.UI.SmoothScroll.InstallUnder(root, opts) +end + +local ny = _G.libNyx or {} +ny.UI = ny.UI or {} +_G.libNyx = ny + +local function NyScale(n) return (ny.UI.Scale and ny.UI.Scale(n)) or n end +local function NyFont(sz) return (ny.UI.Font and ny.UI.Font(sz)) or "DermaDefault" end +local function lerpExp(cur, tgt, speed) + local k = 1 - math.exp(-(speed or 14) * FrameTime()) + return cur + (tgt - cur) * k +end + +ny.__nyxMenuSkinInstalled = ny.__nyxMenuSkinInstalled or false +function ny.UI.InstallGlobalMenuSkin(opt) + if ny.__nyxMenuSkinInstalled then return end + opt = opt or {} + + local S = ny.UI.Style or {} + local radius = opt.radius or S.radius or 10 + local fill = opt.fill or Color(16,18,24, 210) + local stroke = opt.stroke or S.glassStroke or Color(255,255,255, 22) + local textColor = opt.textColor or Color(235,235,240) + local rowHover = opt.rowHover or Color(255,255,255, 10) + local rowActive = opt.rowActive or Color(255,255,255, 16) + local blurIntensity = opt.blurIntensity or 1.12 + local fontObj = opt.font and (type(opt.font)=="function" and opt.font() or opt.font) or NyFont(NyScale(16)) + local padding = opt.padding or NyScale(4) + + local function styleMenuTable() + local T = vgui.GetControlTable("DMenu") + if T then + T.Paint = function(self, w, h) + if ny.UI.Draw and ny.UI.Draw.Glass then + ny.UI.Draw.Glass(0, 0, w, h, { + radius = NyScale(radius), + fill = fill, + stroke = true, + strokeColor = stroke, + blurIntensity = blurIntensity + }) + else + draw.RoundedBox(NyScale(radius), 0, 0, w, h, fill) + surface.SetDrawColor(stroke) + surface.DrawOutlinedRect(0, 0, w, h) + end + end + local oldPerform = T.PerformLayout + T.PerformLayout = function(self, ...) + self:SetPadding(padding) + if oldPerform then oldPerform(self, ...) end + self:SizeToContents() + end + end + + local O = vgui.GetControlTable("DMenuOption") + if O then + local oldSetText = O.SetText + O.SetText = function(self, txt) + oldSetText(self, txt) + self:SetFont(fontObj) + self:SetTextColor(textColor) + end + O.Paint = function(self, w, h) + self._nyxHover = lerpExp(self._nyxHover or 0, self:IsHovered() and 1 or 0, 18) + local a = self._nyxHover or 0 + local r = NyScale(6) + local c = Color(rowHover.r, rowHover.g, rowHover.b, math.floor(rowHover.a * a)) + if ny.UI.Draw and ny.UI.Draw.Panel then + ny.UI.Draw.Panel(2, 1, w-4, h-2, { + radius = r, + color = c, + glass = false, + stroke = false + }) + else + draw.RoundedBox(r, 2, 1, w-4, h-2, c) + end + if self.m_bSelected or self:GetToggle() then + local act = Color(rowActive.r, rowActive.g, rowActive.b, rowActive.a) + if ny.UI.Draw and ny.UI.Draw.Panel then + ny.UI.Draw.Panel(2, 1, w-4, h-2, {radius = r, color = act}) + else + draw.RoundedBox(r, 2, 1, w-4, h-2, act) + end + end + return false + end + end + local D = vgui.GetControlTable("DMenuDivider") + if D then + D.Paint = function(self, w, h) + surface.SetDrawColor(255,255,255, 18) + surface.DrawRect(NyScale(8), math.floor(h*0.5), w - NyScale(16), 1) + end + D.GetSpacing = function() return NyScale(6) end + end + end + if vgui.GetControlTable("DMenu") then + styleMenuTable() + else + timer.Simple(0, styleMenuTable) + end + + ny.__nyxMenuSkinInstalled = true +end + +if CLIENT and (ny.UI.AutoInstallMenuSkin ~= false) then + timer.Simple(0, function() + if _G.libNyx and _G.libNyx.UI and not _G.libNyx.__nyxMenuSkinInstalled then + _G.libNyx.UI.InstallGlobalMenuSkin() + end + end) +end + +do + local ny = _G.libNyx or {} + ny.UI = ny.UI or {} + _G.libNyx = ny + + local Style = ny.UI.Style + local SCALE = ny.UI.Scale + local FONT = ny.UI.Font + local DrawPanel = ny.UI.Draw and ny.UI.Draw.Panel + local DARK = (Style and (Style.panelColor or Color(20,22,30,130))) or Color(20,22,30,130) + + local NOTIFY_GENERIC = _G.NOTIFY_GENERIC or 0 + local NOTIFY_ERROR = _G.NOTIFY_ERROR or 1 + local NOTIFY_UNDO = _G.NOTIFY_UNDO or 2 + local NOTIFY_HINT = _G.NOTIFY_HINT or 3 + local NOTIFY_CLEANUP = _G.NOTIFY_CLEANUP or 4 + + local ICONS = { + info = Material("icon16/information.png","noclamp smooth"), + ok = Material("icon16/accept.png","noclamp smooth"), + undo = Material("icon16/arrow_undo.png","noclamp smooth"), + error = Material("icon16/exclamation.png","noclamp smooth"), + broom = Material("icon16/bin.png","noclamp smooth"), + mail = Material("icon16/email.png","noclamp smooth"), + box = Material("icon16/box.png","noclamp smooth"), + star = Material("icon16/star.png","noclamp smooth"), + } + + local TYPES = { + [NOTIFY_GENERIC] = { icon = ICONS.info }, + [NOTIFY_ERROR] = { icon = ICONS.error }, + [NOTIFY_UNDO] = { icon = ICONS.undo }, + [NOTIFY_HINT] = { icon = ICONS.star }, + [NOTIFY_CLEANUP] = { icon = ICONS.broom }, + } + + local function pickIcon(msg, fallback) + local m = string.lower(tostring(msg or "")) + if m:find("mail",1,true) or m:find("letter",1,true) or m:find("письм",1,true) then return ICONS.mail end + if m:find("parcel",1,true) or m:find("package",1,true) or m:find("посылк",1,true) then return ICONS.box end + return fallback or ICONS.info + end + + ny.UI._Notify = ny.UI._Notify or {} + + local function Layer() + if IsValid(ny.UI._Notify.layer) then return ny.UI._Notify.layer end + local L = vgui.Create("DPanel") + L:SetZPos(32767) + L:SetDrawOnTop(true) + L:SetMouseInputEnabled(false) + L:SetKeyboardInputEnabled(false) + L:SetSize(ScrW(), ScrH()) + L:SetPos(0,0) + ny.UI.AutoNoBG(L) + L.list = {} + function L:Relayout() + local padR = SCALE(24) + local gap = SCALE(8) + local total = 0 + for i = 1, #self.list do + local p = self.list[i] + if IsValid(p) then + total = total + p:GetTall() + if i < #self.list then total = total + gap end + end + end + local y = math.max(SCALE(14), math.floor(ScrH()*0.5 - total*0.5)) + for i = 1, #self.list do + local p = self.list[i] + if IsValid(p) then + local x = ScrW() - padR - p:GetWide() + if p._spawn then + p:SetPos(x, y) + p._spawn = nil + else + p:MoveTo(x, y, 0.15, 0, 0.2) + end + y = y + p:GetTall() + gap + end + end + end + hook.Add("OnScreenSizeChanged","libNyx.Notify.Relayout",function() + if not IsValid(L) then return end + L:SetSize(ScrW(),ScrH()) + L:SetPos(0,0) + L:Relayout() + end) + ny.UI._Notify.layer = L + return L + end + + local function Toast(text, icon, life) + local L = Layer() + local p = vgui.Create("DButton", L) + p:SetText("") + p:SetDrawOnTop(true) + p:SetZPos(32766) + p:SetMouseInputEnabled(true) + p:SetKeyboardInputEnabled(false) + p._icon = icon or pickIcon(text, ICONS.info) + p._life = math.max(0.5, tonumber(life) or 4) + p._killAt = SysTime() + p._life + p._fade = 0 + p._slide = 1 + p._spawn = true + + local padX = SCALE(14) + local h = SCALE(40) + local iconW = SCALE(22) + local font = FONT(SCALE(18)) + + surface.SetFont(font) + local tw = select(1, surface.GetTextSize(tostring(text or ""))) + local w = math.Clamp(tw + padX*2 + iconW + SCALE(14), SCALE(220), math.floor(ScrW()*0.5)) + p:SetSize(w, h) + + function p:DoClick() + self._killAt = SysTime() - 0.01 + surface.PlaySound("ui/buttonclickrelease.wav") + end + function p:OnCursorEntered() + self._killAt = SysTime() + 1.2 + self._life*0.25 + surface.PlaySound("buttons/lightswitch2.wav") + end + + p.Think = function(s) + local now = SysTime() + local want = (s._killAt or now) > now and 1 or 0 + s._fade = s._fade + (want - s._fade) * math.min(FrameTime()*16,1) + s._slide = s._slide + ((want==1) and -s._slide or (1 - s._slide)) * math.min(FrameTime()*10,1) + if s._fade <= 0.02 and want == 0 then + s:Remove() + for i=#L.list,1,-1 do if L.list[i]==s then table.remove(L.list,i) break end end + L:Relayout() + end + end + + p.Paint = function(s, w, h) + local a = math.Clamp(s._fade or 0, 0, 1) + if a <= 0.001 then return end + local r = math.max(Style.radius, SCALE(10)) + local slide = math.floor(SCALE(6) * (s._slide or 0)) + if RNDX and RNDX.EnsureFB then RNDX.EnsureFB() end + if DrawPanel then + DrawPanel(0,0,w,h,{radius=r,color=Color(DARK.r,DARK.g,DARK.b, math.floor(95*a)),glass=true}) + end + if RNDX and RNDX().Liquid then + RNDX().Liquid(0,0,w,h) + :Rad(r) + :Strength(0.014) + :Speed(0.35) + :Saturation(1.06) + :Tint(20,24,32) + :TintStrength(0.10) + :Shimmer(22.0) + :Grain(0.005) + :Alpha(0.95*a) + :GlassBlur(0.02,0.38) + :EdgeSmooth(2.0) + :Draw() + end + if DrawPanel then + local sheenH = math.max(1, math.floor(h*0.26)) + local sw = math.max(1, w - SCALE(16)) + DrawPanel(SCALE(8), SCALE(6), sw, sheenH, {radius=math.floor(r*0.8), color=Color(255,255,255, math.floor(10*a))}) + end + draw.SimpleText( + tostring(text or ""), + font, + padX, h/2 + slide*0.25, + Color(Style.textColor.r,Style.textColor.g,Style.textColor.b, math.floor(255*a)), + TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER + ) + if s._icon and not s._icon:IsError() then + local ic = iconW + surface.SetDrawColor(255,255,255, math.floor(255*a)) + surface.SetMaterial(s._icon) + surface.DrawTexturedRect(w - padX - ic, (h - ic)/2, ic, ic) + end + end + + table.insert(L.list, 1, p) + L:Relayout() + return p + end + + function ny.UI.PushNotify(msg, typeOrColor, len, icon) + local t = TYPES[tonumber(typeOrColor) or NOTIFY_GENERIC] or TYPES[NOTIFY_GENERIC] + local ico = icon or pickIcon(msg, t.icon) + return Toast(msg, ico, len) + end + + if not ny.UI.__notifySkinInstalled then + ny.UI.__notifySkinInstalled = true + ny.UI._origNotification = ny.UI._origNotification or {} + ny.UI._origNotification.AddLegacy = notification.AddLegacy + ny.UI._origNotification.AddProgress = notification.AddProgress + ny.UI._origNotification.Kill = notification.Kill + + notification.AddLegacy = function(txt, kind, length) + ny.UI.PushNotify(txt, kind, length) + end + + local progress = {} + notification.AddProgress = function(id, txt, frac) + if not IsValid(progress[id]) then + local row = Toast(txt, ICONS.info, 9999) + row._progress = 0 + row.PaintOver = function(s, w, h) + if not s._progress then return end + local pad = SCALE(8) + local ph = SCALE(4) + local pw = math.floor((w - pad*2) * math.Clamp(s._progress, 0, 1)) + if DrawPanel then + DrawPanel(pad, h - ph - pad, w - pad*2, ph, {radius = ph/2, color = Color(40,44,52,150), glass = true}) + if pw > 1 then + DrawPanel(pad, h - ph - pad, pw, ph, {radius = ph/2, color = Color(Style.accentColor.r,Style.accentColor.g,Style.accentColor.b,190), glass = true}) + end + end + end + progress[id] = row + end + progress[id]._progress = tonumber(frac) or 0 + return id + end + + notification.Kill = function(id) + local p = progress[id] + if IsValid(p) then p._killAt = SysTime() - 0.01 end + progress[id] = nil + end + + local function patchGamemode() + local gm = gmod and gmod.GetGamemode and gmod.GetGamemode() + if not gm or gm.__nyxAddNotifyPatched then return end + gm.__nyxAddNotifyPatched = true + gm._nyxOrigAddNotify = gm.AddNotify + function gm:AddNotify(txt, kind, length) + ny.UI.PushNotify(txt, kind, length) + end + end + + timer.Simple(0, patchGamemode) + hook.Add("OnGamemodeLoaded","libNyx.Notify.PatchGM",patchGamemode) + hook.Add("DarkRPFinishedLoading","libNyx.Notify.PatchGM",patchGamemode) + end + + function ny.UI.InstallGlobalNotificationSkin() + Layer() + end + + timer.Simple(0, function() + if _G.libNyx and _G.libNyx.UI then + _G.libNyx.UI.InstallGlobalNotificationSkin() + end + end) +end + + +local ny = _G.libNyx or {} +ny.UI = ny.UI or {} +_G.libNyx = ny + +local STYLE = ny.UI.Style or {} +local Components = ny.UI.Components or {} +local SCALE = NyScale +local FONT = NyFont + +local function NyDialogFrame(title, w, h) + w = w or SCALE(460) + h = h or SCALE(210) + local f + if ny.UI.CreateFrame then + f = ny.UI.CreateFrame({title = title or "", w = w, h = h}) + else + f = vgui.Create("DFrame") + f:SetTitle(title or "") + f:SetSize(w, h) + f:Center() + f:MakePopup() + f:ShowCloseButton(true) + end + local inner = vgui.Create("DPanel", f) + inner:Dock(FILL) + inner:DockMargin(SCALE(24), SCALE(70), SCALE(24), SCALE(24)) + if ny.UI.AutoNoBG then ny.UI.AutoNoBG(inner) end + return f, inner +end + +function ny.UI.CreateMessageBox(opt) + opt = opt or {} + local title = opt.title or "" + local text = opt.text or "" + local btnText = opt.buttonText or "OK" + local w = opt.w or SCALE(460) + local h = opt.h or SCALE(210) + local onClick = opt.onClick + + local frame, inner = NyDialogFrame(title, w, h) + + local top = vgui.Create("DPanel", inner) + top:Dock(FILL) + top:DockMargin(0, 0, 0, SCALE(8)) + if ny.UI.AutoNoBG then ny.UI.AutoNoBG(top) end + + local lbl = vgui.Create("DLabel", top) + lbl:Dock(FILL) + lbl:SetFont(FONT(SCALE(20))) + lbl:SetTextColor(STYLE.textColor or color_white) + lbl:SetText(text) + lbl:SetWrap(true) + lbl:SetContentAlignment(5) + + local btnRow = vgui.Create("DPanel", inner) + btnRow:Dock(BOTTOM) + btnRow:SetTall((STYLE.btnHeight or SCALE(44)) + SCALE(4)) + if ny.UI.AutoNoBG then ny.UI.AutoNoBG(btnRow) end + + local ok + if Components.CreateButton then + ok = Components.CreateButton(btnRow, btnText, {variant = "primary_center"}) + else + ok = vgui.Create("DButton", btnRow) + ok:SetText(btnText) + ok:SetFont(FONT(SCALE(18))) + end + surface.SetFont(FONT(SCALE(18))) + local tw = select(1, surface.GetTextSize(btnText)) + ok:SetWide(math.max(SCALE(110), tw + SCALE(40))) + ok:SetTall(STYLE.btnHeight or SCALE(44)) + + function ok:DoClick() + if ny.UI.PlayClick then ny.UI.PlayClick() end + if isfunction(onClick) then onClick(frame) end + if frame.Close then frame:Close() else frame:Remove() end + end + + function btnRow:PerformLayout(w, h) + if not IsValid(ok) then return end + local bw, bh = ok:GetWide(), ok:GetTall() + local x = math.floor((w - bw) * 0.5) + local y = math.floor((h - bh) * 0.5) + ok:SetPos(x, y) + end + + function frame:OnKeyCodePressed(key) + if key == KEY_ENTER or key == KEY_PAD_ENTER or key == KEY_SPACE then + if IsValid(ok) then ok:DoClick() end + elseif key == KEY_ESCAPE then + if self.Close then self:Close() else self:Remove() end + end + end + + return frame +end + +function ny.UI.CreateInputBox(opt) + opt = opt or {} + local title = opt.title or "" + local text = opt.text or "" + local default = opt.default or "" + local confirmText = opt.confirmText or "OK" + local cancelText = opt.cancelText or "Cancel" + local onConfirm = opt.onConfirm + local onCancel = opt.onCancel + local w = opt.w or SCALE(500) + local h = opt.h or SCALE(250) + + local frame, inner = NyDialogFrame(title, w, h) + + local top = vgui.Create("DPanel", inner) + top:Dock(FILL) + top:DockMargin(0, 0, 0, SCALE(10)) + if ny.UI.AutoNoBG then ny.UI.AutoNoBG(top) end + + local input = vgui.Create("DTextEntry", top) + input:Dock(BOTTOM) + input:SetTall(SCALE(34)) + input:DockMargin(0, SCALE(12), 0, 0) + input:SetFont(FONT(SCALE(18))) + input:SetText(default or "") + input:SetUpdateOnType(false) + input:SetHistoryEnabled(false) + function input:Paint(w, h) + if ny.UI.Draw and ny.UI.Draw.Panel then + ny.UI.Draw.Panel(0, 0, w, h, { + radius = SCALE(8), + color = Color(20, 24, 32, 150), + glass = true, + stroke = true, + strokeColor = STYLE.glassStroke or Color(255,255,255,24) + }) + else + draw.RoundedBox(4, 0, 0, w, h, Color(20,24,32,220)) + end + self:DrawTextEntryText(STYLE.textColor or color_white, Color(255,255,255,25), STYLE.textColor or color_white) + end + + local lbl = vgui.Create("DLabel", top) + lbl:Dock(FILL) + lbl:SetFont(FONT(SCALE(20))) + lbl:SetTextColor(STYLE.textColor or color_white) + lbl:SetText(text) + lbl:SetWrap(true) + lbl:SetContentAlignment(5) + lbl:DockMargin(0, 0, 0, SCALE(6)) + + local btnRow = vgui.Create("DPanel", inner) + btnRow:Dock(BOTTOM) + btnRow:SetTall((STYLE.btnHeight or SCALE(44)) + SCALE(4)) + btnRow:DockMargin(0, SCALE(4), 0, 0) + if ny.UI.AutoNoBG then ny.UI.AutoNoBG(btnRow) end + + surface.SetFont(FONT(SCALE(18))) + local twOk = select(1, surface.GetTextSize(confirmText)) + local twCancel= select(1, surface.GetTextSize(cancelText)) + local wOk = math.max(SCALE(110), twOk + SCALE(40)) + local wCancel = math.max(SCALE(110), twCancel + SCALE(40)) + + local btnCancel, btnOk + if Components.CreateButton then + btnCancel = Components.CreateButton(btnRow, cancelText, {variant = "ghost"}) + btnOk = Components.CreateButton(btnRow, confirmText, {variant = "primary_center"}) + else + btnCancel = vgui.Create("DButton", btnRow) + btnCancel:SetText(cancelText) + btnCancel:SetFont(FONT(SCALE(18))) + btnOk = vgui.Create("DButton", btnRow) + btnOk:SetText(confirmText) + btnOk:SetFont(FONT(SCALE(18))) + end + + btnCancel:SetWide(wCancel) + btnOk:SetWide(wOk) + local btnH = STYLE.btnHeight or SCALE(44) + btnCancel:SetTall(btnH) + btnOk:SetTall(btnH) + + local function doConfirm() + local txt = input:GetText() or "" + if ny.UI.PlayClick then ny.UI.PlayClick() end + if isfunction(onConfirm) then onConfirm(txt) end + if frame.Close then frame:Close() else frame:Remove() end + end + + local function doCancel() + local txt = input:GetText() or "" + if ny.UI.PlayClick then ny.UI.PlayClick() end + if isfunction(onCancel) then onCancel(txt) end + if frame.Close then frame:Close() else frame:Remove() end + end + + function btnOk:DoClick() + doConfirm() + end + + function btnCancel:DoClick() + doCancel() + end + + function btnRow:PerformLayout(w, h) + if not (IsValid(btnOk) and IsValid(btnCancel)) then return end + local gap = SCALE(12) + local total = btnOk:GetWide() + btnCancel:GetWide() + gap + local x = math.floor((w - total) * 0.5) + local y = math.floor((h - btnOk:GetTall()) * 0.5) + btnCancel:SetPos(x, y) + x = x + btnCancel:GetWide() + gap + btnOk:SetPos(x, y) + end + + function input:OnEnter() + doConfirm() + end + + function frame:OnKeyCodePressed(key) + if key == KEY_ENTER or key == KEY_PAD_ENTER then + if IsValid(btnOk) then btnOk:DoClick() end + elseif key == KEY_ESCAPE then + if IsValid(btnCancel) then btnCancel:DoClick() else + if self.Close then self:Close() else self:Remove() end + end + end + end + + timer.Simple(0, function() + if IsValid(input) then + input:RequestFocus() + input:SelectAllText(true) + end + end) + + return frame, input +end + +local _orig_Derma_Message = Derma_Message +local _orig_Derma_StringRequest = Derma_StringRequest + +function Derma_Message(text, title, button) + button = button or "OK" + if not _G.libNyx or not _G.libNyx.UI or not _G.libNyx.UI.CreateMessageBox then + if _orig_Derma_Message then + return _orig_Derma_Message(text, title, button) + end + end + return _G.libNyx.UI.CreateMessageBox({ + title = title, + text = text, + buttonText = button + }) +end + +function Derma_StringRequest(title, subtitle, default, confirm, cancel, confirmText, cancelText) + confirmText = confirmText or "OK" + cancelText = cancelText or "Cancel" + if not _G.libNyx or not _G.libNyx.UI or not _G.libNyx.UI.CreateInputBox then + if _orig_Derma_StringRequest then + return _orig_Derma_StringRequest(title, subtitle, default, confirm, cancel, confirmText, cancelText) + end + end + local frame, entry = _G.libNyx.UI.CreateInputBox({ + title = title, + text = subtitle, + default = default, + confirmText = confirmText, + cancelText = cancelText, + onConfirm = confirm, + onCancel = cancel + }) + return frame, entry +end + + +-- libNyx by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw + diff --git a/addons/_libnyx/lua/libnyx/lib/libnyx_liquidglass.lua b/addons/_libnyx/lua/libnyx/lib/libnyx_liquidglass.lua new file mode 100644 index 0000000..4451dd7 --- /dev/null +++ b/addons/_libnyx/lua/libnyx/lib/libnyx_liquidglass.lua @@ -0,0 +1,410 @@ +-- libNyx and LiquidGlass shader by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw + +if SERVER then AddCSLuaFile() return end + +local RNDX = include("libnyx/lib/rndx.lua") + +surface.CreateFont("libNyx.Manrope.Liquid",{font="Manrope",size=30,weight=800,antialias=true,extended=true}) +--surface.CreateFont("libNyx.UI.18",{font="Manrope",size=18,weight=700,antialias=true,extended=true}) +--surface.CreateFont("libNyx.UI.16",{font="Manrope",size=16,weight=600,antialias=true,extended=true}) +--surface.CreateFont("libNyx.UI.14",{font="Manrope",size=14,weight=600,antialias=true,extended=true}) + +CreateClientConVar("libnyx_liquid_size","300",true,false) + +local Nyx = libNyx and libNyx.UI +local Style = Nyx and Nyx.Style +local function s(n) return (Nyx and Nyx.Scale and Nyx.Scale(n)) or n end +local function f(sz) return (Nyx and Nyx.Font and Nyx.Font(s(sz))) or "DermaDefault" end + +local function clp(x,a,b) if xb then return b else return x end end +local function rstep(x,st) if not st or st<=0 then return x end return math.Round(x/st)*st end +local function sdec(st) local q=tostring(st or "") local p=q:find("%.") return p and (#q-p) or 0 end +local function dup(t) local r={} for k,v in pairs(t) do r[k]=v end return r end + +local function fmt(v) + if type(v)~="number" then return tostring(v or 0) end + local out = string.format("%.6f", v):gsub("0+$",""):gsub("%.$","") + return (out == "" and "0") or out +end + +local DEF = { + size = tonumber(GetConVar("libnyx_liquid_size"):GetString()) or 300, + rad = 32, + strength = 0.012, + speed = 0, + sat = 1.06, + tr = 255, tg = 255, tb = 255, + tints = 0.06, + blur_all = 0.10, + blur_rad = 0, + edge = 2.0, + shimmer = 25.2, + grain = 0.01, + alpha = 0.95, + shape = RNDX.SHAPE_IOS, + shadow_enabled = true, + shadow_spread = 40, + shadow_intensity = 56 +} + +local function mk() return dup(DEF) end + +local function open() + if not Nyx then return end + if IsValid(libNyx.LiquidGlassUI) then libNyx.LiquidGlassUI:Remove() return end + + local st = mk() + local ui = { sliders = {} } + + local rt = vgui.Create("EditablePanel") + rt:SetSize(ScrW(),ScrH()) + rt:MakePopup() + gui.EnableScreenClicker(true) + libNyx.LiquidGlassUI = rt + Nyx.AutoNoBG(rt) + Nyx.InstallGlobalScroll(rt,{step=s(90),speed=18,fadeHold=0.9,width=s(12)}) + + function rt:OnRemove() gui.EnableScreenClicker(false) end + function rt:OnKeyCodePressed(k) if k==KEY_ESCAPE then self:Remove() end end + + local pad = s(24) + local lw = math.min(s(380), math.max(s(300), math.floor(ScrW()*0.26))) + + local nav = vgui.Create("DPanel", rt) + nav:SetPos(pad, pad) + nav:SetSize(lw, ScrH() - pad*2) + Nyx.AutoNoBG(nav) + nav.Paint = function(p,w,h) + Nyx.Draw.Glass(0,0,w,h,{ + radius=s(18), + fill=Style and Style.bgColor or Color(10,10,14,150), + stroke=true, + strokeColor=Style and Style.glassStroke or Color(255,255,255,22), + blurIntensity=1.1 + }) + draw.SimpleText("libNyx · Liquid Glass", f(22), s(18), s(16), Style and Style.textColor or color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + end + + local bd = vgui.Create("DScrollPanel", nav) + bd:Dock(FILL) + bd:DockMargin(s(12), s(50), s(12), s(12)) + Nyx.AutoNoBG(bd) + Nyx.SmoothScroll.ApplyToScrollPanel(bd,{step=s(90),speed=18,fadeHold=0.9,width=s(12)}) + local vb = bd:GetVBar() + vb:SetWide(0) + vb.Paint = function() end + if vb.btnUp then vb.btnUp:SetVisible(false) end + if vb.btnDown then vb.btnDown:SetVisible(false) end + if vb.btnGrip then vb.btnGrip:SetVisible(false) end + + local function title(t) + local pnl = vgui.Create("DPanel", bd) + pnl:Dock(TOP) + pnl:DockMargin(0, s(10), 0, s(6)) + pnl:SetTall(s(30)) + Nyx.AutoNoBG(pnl) + pnl.Paint = function(_,w,h) + draw.SimpleText(t, f(18), 0, h/2, Style and Style.textColor or color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end + + local function row(h) + local r = vgui.Create("DPanel", bd) + r:Dock(TOP) + r:DockMargin(0, s(6), 0, 0) + r:SetTall(h or s(44)) + Nyx.AutoNoBG(r) + r.Paint = function(_,w,h) + Nyx.Draw.Panel(0,0,w,h,{radius=s(12),color=Style and Style.panelColor or Color(20,22,30,130),glass=true,stroke=true}) + end + return r + end + + local clampBox = function() end + + local function slider(lbl,key,min,max,step,tint) + local r = row(s(48)) + local L = vgui.Create("DLabel", r) + L:SetFont(f(18)) + L:SetTextColor(Style and Style.textColor or color_white) + L:SetText(lbl) + L:SizeToContents() + L:SetPos(s(12), s(12)) + local sl = Nyx.Components.CreateSlider(r,{min=min,max=max,decimals=sdec(step),value=st[key],tint=tint or (Style and Style.accentColor)}) + sl:Dock(FILL) + sl:DockMargin(s(140), 0, s(12), 0) + function sl:OnValueChanged(v) + local val = clp(rstep(v,step),min,max) + st[key] = val + if key=="size" then + RunConsoleCommand("libnyx_liquid_size", tostring(math.floor(val))) + clampBox() + end + end + ui.sliders[key] = sl + return sl + end + + local function drop(lbl,choices,onPick) + local r = row(s(48)) + local L = vgui.Create("DLabel", r) + L:SetFont(f(18)) + L:SetTextColor(Style and Style.textColor or color_white) + L:SetText(lbl) + L:SizeToContents() + L:SetPos(s(12), s(12)) + local dd = Nyx.Components.CreateDropdown(r,{choices=choices,placeholder="",onSelect=onPick}) + dd:Dock(RIGHT) + dd:SetWide(s(150)) + dd:DockMargin(0, s(6), s(8), s(6)) + return dd + end + + local function toggle(lbl,init,onChange) + local r = row(s(44)) + local sw = Nyx.Components.CreateCheckbox(r,{variant="switch",checked=init,label=lbl}) + sw:Dock(LEFT) + sw:DockMargin(s(10), s(6), 0, s(6)) + sw:SetOnChange(function(v) if onChange then onChange(v) end end) + return sw + end + + title("Layout") + slider("Size","size",220,520,1) + ui.sliders.rad = slider("Corner","rad",0,64,1) + local shapeDD = drop("Shape",{ + {label="iOS"}, + {label="Figma"}, + {label="Circle"}, + },function(lbl) + if lbl=="iOS" then st.shape=RNDX.SHAPE_IOS + elseif lbl=="Figma" then st.shape=RNDX.SHAPE_FIGMA + else st.shape=RNDX.SHAPE_CIRCLE end + clampBox() + end) + + title("Visuals") + slider("Strength","strength",0,0.06,0.001) + slider("Speed","speed",0,4,0.1) + slider("Saturation","sat",0.5,1.6,0.01) + slider("Tint R","tr",0,255,1,Color(255,110,110)) + slider("Tint G","tg",0,255,1,Color(110,255,110)) + slider("Tint B","tb",0,255,1,Color(110,110,255)) + slider("Tint Amt","tints",0,0.35,0.005) + + title("Blur & Edge") + slider("Glass Blur","blur_all",0,0.5,0.01) + slider("Blur Rad","blur_rad",0,4,0.1) + slider("Edge Px","edge",0,6,0.1) + + title("FX") + slider("Shimmer","shimmer",0,50,0.1) + slider("Grain","grain",0,0.08,0.001) + slider("Alpha","alpha",0.2,1.0,0.01) + + title("Shadow") + local swShadow = toggle("Enable Shadow",st.shadow_enabled,function(v) st.shadow_enabled=v end) + slider("Spread","shadow_spread",0,120,1) + slider("Intensity","shadow_intensity",0,180,1) + + local bar = vgui.Create("DPanel", nav) + bar:Dock(BOTTOM) + bar:SetTall(s(52)) + Nyx.AutoNoBG(bar) + bar.Paint = function(_,w,h) + Nyx.Draw.Panel(0,0,w,h,{radius=s(12),color=Style and Style.cardColor or Color(16,18,24,120),glass=true,stroke=true}) + end + + local btnW = (lw - s(8*4)) / 3 + + local function shapeLbl() + if st.shape==RNDX.SHAPE_FIGMA then return "Figma" + elseif st.shape==RNDX.SHAPE_CIRCLE then return "Circle" + else return "iOS" end + end + + local btnRst = Nyx.Components.CreateButton(bar,"Reset",{variant="ghost"}) + btnRst:Dock(LEFT) + btnRst:DockMargin(s(8), s(6), s(6), s(6)) + btnRst:SetWide(btnW) + btnRst._onClick = function() + st = mk() + RunConsoleCommand("libnyx_liquid_size", tostring(st.size)) + for k,ctrl in pairs(ui.sliders) do if IsValid(ctrl) then ctrl:SetValue(st[k] or 0) end end + if IsValid(swShadow) then swShadow:SetChecked(st.shadow_enabled and true or false) end + if IsValid(shapeDD) and shapeDD.SetSelectedLabel then shapeDD:SetSelectedLabel(shapeLbl()) end + clampBox() + if notification and notification.AddLegacy then notification.AddLegacy("libNyx: settings reset.", NOTIFY_HINT, 2) end + if chat and chat.AddText then chat.AddText(Color(140,180,255), "[libNyx] ", color_white, "Liquid Glass settings reset.") end + end + + local function shapeConst() + if st.shape==RNDX.SHAPE_FIGMA then return "RNDX.SHAPE_FIGMA" + elseif st.shape==RNDX.SHAPE_CIRCLE then return "RNDX.SHAPE_CIRCLE" + else return "RNDX.SHAPE_IOS" end + end + + local function ensurePos() + local sw,sh = rt:GetWide(), rt:GetTall() + local bw,bh = st.size, st.size + local leftBound = pad + lw + s(12) + local minX = math.max(leftBound, pad) + local maxX = sw - pad - bw + local minY = pad + local maxY = sh - pad - bh + if not st.posX then st.posX = sw - bw - pad end + if not st.posY then st.posY = pad + s(20) end + st.posX = clp(st.posX, minX, math.max(minX, maxX)) + st.posY = clp(st.posY, minY, math.max(minY, maxY)) + end + + local btnCP = Nyx.Components.CreateButton(bar,"Copy",{variant="ghost"}) + btnCP:Dock(LEFT) + btnCP:DockMargin(s(6), s(6), s(6), s(6)) + btnCP:SetWide(btnW) + btnCP._onClick = function() + ensurePos() + local rr = (st.shape==RNDX.SHAPE_CIRCLE) and math.floor((st.size or 0)*0.5) or (st.rad or 0) + local lines = { + "RNDX().Liquid(bx,by,bw,bh)", + " :Rad("..fmt(rr)..")", + " :Color(255,255,255,255)", + " :Tint("..math.floor(st.tr or 0)..","..math.floor(st.tg or 0)..","..math.floor(st.tb or 0)..")", + " :TintStrength("..fmt(st.tints or 0)..")", + " :Saturation("..fmt(st.sat or 1)..")", + " :GlassBlur("..fmt(st.blur_all or 0)..","..fmt(st.blur_rad or 0)..")", + " :EdgeSmooth("..fmt(st.edge or 0)..")", + " :Strength("..fmt(st.strength or 0)..")", + " :Speed("..fmt(st.speed or 0)..")", + " :Shimmer("..fmt(st.shimmer or 0)..")", + " :Grain("..fmt(st.grain or 0)..")", + " :Alpha("..fmt(st.alpha or 1)..")", + " :Flags("..shapeConst()..")" + } + if st.shadow_enabled and ((st.shadow_spread or 0)>0 or (st.shadow_intensity or 0)>0) then + table.insert(lines, #lines, " :Shadow("..fmt(st.shadow_spread or 0)..","..fmt(st.shadow_intensity or 0)..")") + end + local code = table.concat(lines, "\n") + SetClipboardText(code) + if notification and notification.AddLegacy then notification.AddLegacy("libNyx: code copied to clipboard.", NOTIFY_GENERIC, 3) end + if chat and chat.AddText then chat.AddText(Color(140,180,255), "[libNyx] ", color_white, "Liquid Glass builder copied.") end + end + + local btnX = Nyx.Components.CreateButton(bar,"Close",{variant="ghost"}) + btnX:Dock(FILL) + btnX:DockMargin(s(6), s(6), s(8), s(6)) + btnX._onClick = function() if IsValid(rt) then rt:Remove() end end + + if IsValid(shapeDD) and shapeDD.SetSelectedLabel then shapeDD:SetSelectedLabel(shapeLbl()) end + + local drag = {on=false,dx=0,dy=0} + rt._hoverA = 0 + + local function clampReal() + local sw,sh = rt:GetWide(), rt:GetTall() + local bw,bh = st.size, st.size + local leftBound = pad + lw + s(12) + local minX = math.max(leftBound, pad) + local maxX = sw - pad - bw + local minY = pad + local maxY = sh - pad - bh + if not st.posX then st.posX = sw - bw - pad end + if not st.posY then st.posY = pad + s(20) end + st.posX = clp(st.posX, minX, math.max(minX, maxX)) + st.posY = clp(st.posY, minY, math.max(minY, maxY)) + end + clampBox = clampReal + clampReal() + + hook.Add("OnScreenSizeChanged","libNyx.LiquidGlass.Relayout",function() + if not IsValid(rt) or not IsValid(nav) or not IsValid(bd) or not IsValid(bar) then return end + rt:SetSize(ScrW(),ScrH()) + lw = math.min(s(380), math.max(s(300), math.floor(ScrW()*0.26))) + nav:SetPos(pad, pad) + nav:SetSize(lw, ScrH() - pad*2) + clampReal() + end) + + function rt:OnMousePressed(mc) + if mc ~= MOUSE_LEFT then return end + local mx,my = self:LocalCursorPos() + local bw,bh = st.size,st.size + if mx>=st.posX and mx<=st.posX+bw and my>=st.posY and my<=st.posY+bh then + drag.on = true + drag.dx = mx - st.posX + drag.dy = my - st.posY + self:MouseCapture(true) + end + end + + function rt:OnMouseReleased(mc) + if mc ~= MOUSE_LEFT then return end + if drag.on then + drag.on = false + self:MouseCapture(false) + end + end + + function rt:OnCursorMoved(x,y) + if not drag.on then return end + st.posX = x - drag.dx + st.posY = y - drag.dy + clampReal() + end + + function rt:Think() + local mx,my = self:LocalCursorPos() + local bw,bh = st.size,st.size + local inside = mx>=st.posX and mx<=st.posX+bw and my>=st.posY and my<=st.posY+bh + local tgt = (inside or drag.on) and 1 or 0 + self._hoverA = Lerp(FrameTime()*10, self._hoverA, tgt) + if inside or drag.on then self:SetCursor("sizeall") else self:SetCursor("arrow") end + end + + local function curRad(w,h) + if st.shape == RNDX.SHAPE_CIRCLE then + return math.floor(math.min(w,h) * 0.5) + end + return st.rad + end + + function rt:Paint(sw,sh) + clampReal() + local bx,by = st.posX, st.posY + local bw,bh = st.size, st.size + local rr = curRad(bw,bh) + + RNDX().Rect(bx,by,bw,bh):Rad(rr):Flags(st.shape):Blur(1):Draw() + + local liq = RNDX().Liquid(bx,by,bw,bh) + :Rad(rr) + :Color(255,255,255,255) + :Tint(st.tr,st.tg,st.tb) + :TintStrength(st.tints) + :Saturation(st.sat) + :GlassBlur(st.blur_all,st.blur_rad) + :EdgeSmooth(st.edge) + :Strength(st.strength) + :Speed(st.speed) + :Shimmer(st.shimmer) + :Grain(st.grain) + :Alpha(st.alpha) + :Flags(st.shape) + + if st.shadow_enabled and (st.shadow_spread>0 or st.shadow_intensity>0) then liq:Shadow(st.shadow_spread,st.shadow_intensity) end + liq:Draw() + + local a = self._hoverA + local c1 = Color(255,255,255, math.floor(235 * (1-a))) + local c2 = Color(255,255,255, math.floor(235 * a)) + draw.SimpleText("Liquid Glass", "libNyx.Manrope.Liquid", bx + bw/2, by + bh/2, c1, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("Hold to drag", "libNyx.Manrope.Liquid", bx + bw/2, by + bh/2, c2, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end + +concommand.Add("libnyx_liquid", open) + + +-- libNyx and LiquidGlass shader by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw diff --git a/addons/_libnyx/lua/libnyx/lib/libnyx_maindemo.lua b/addons/_libnyx/lua/libnyx/lib/libnyx_maindemo.lua new file mode 100644 index 0000000..6ceeb94 --- /dev/null +++ b/addons/_libnyx/lua/libnyx/lib/libnyx_maindemo.lua @@ -0,0 +1,415 @@ +-- libNyx by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw + +if SERVER then + AddCSLuaFile() + return +end + +local libNyx = libNyx or {} +libNyx.UI = libNyx.UI or {} +_G.libNyx = libNyx + +local RNDX = include("libnyx/lib/rndx.lua") +if not (libNyx.UI and libNyx.UI.Draw and libNyx.UI.Components) then + include("libnyx/lib/libnyx_components.lua") +end + +local Style = libNyx.UI.Style +local Components = libNyx.UI.Components + +local GRAD_PALETTE = { + Color( 90,160,255), + Color(255,125,155), + Color(120,220,160), + Color(255,200,120), + Color(170,120,255), + Color(120,200,255), +} +local function PalettePick(key) + key = tostring(key or "") + local sum = 0 + for i = 1, #key do sum = sum + key:byte(i) end + local idx = (sum % #GRAD_PALETTE) + 1 + return GRAD_PALETTE[idx] +end + +local showcase +function libNyx.UI.OpenShowcase() + if IsValid(showcase) then showcase:Close() end + local W = math.min(ScrW() - libNyx.UI.Scale(80), 1200) + local H = math.min(ScrH() - libNyx.UI.Scale(80), 780) + showcase = libNyx.UI.CreateFrame({w = W, h = H, title = "Демонстрация libNyx UI"}) + + local pages = vgui.Create("DPanel", showcase) + pages:Dock(FILL) + pages:DockMargin(Style.padding, Style.padding, Style.padding, Style.padding) + pages.Paint = nil + + local page1, page2 + local function showPage(which) + if IsValid(page1) then page1:SetVisible(which == 1) end + if IsValid(page2) then page2:SetVisible(which == 2) end + end + + local nav = Components.CreateTabs(showcase, { + items = { + {id="p1", label="демка 1", icon=Material("icon16/page_white.png","noclamp smooth")}, + {id="p2", label="демка 2", icon=Material("icon16/page_white_text.png","noclamp smooth")}, + }, + default = "p1", + onChange = function(id) showPage(id == "p1" and 1 or 2) end + }) + nav:Dock(TOP) + nav:SetTall(libNyx.UI.Scale(52)) + nav:DockMargin(Style.headerIndentX, Style.headerIndentY + libNyx.UI.Scale(8), Style.headerIndentX, libNyx.UI.Scale(6)) + + + page1 = vgui.Create("DPanel", pages) + page1:Dock(FILL) + page1.Paint = nil + + do + + local left = vgui.Create("DPanel", page1) + left:Dock(LEFT) + left:SetWide(math.floor(W * 0.40)) + left.Paint = function(s,w,h) + libNyx.UI.Draw.Panel(0,0,w,h,{radius=Style.radius, color=Style.panelColor, glass=true}) + end + left:DockMargin(0, 0, Style.padding, 0) + + local leftStack = vgui.Create("DPanel", left) + leftStack:Dock(FILL) + leftStack:DockPadding(Style.padding, Style.padding, Style.padding, Style.padding) + leftStack.Paint = nil + + local checksRow = vgui.Create("DPanel", leftStack) + checksRow:Dock(TOP) + checksRow:SetTall(libNyx.UI.Scale(32)) + checksRow:DockMargin(0, 0, 0, libNyx.UI.Scale(16)) + checksRow.Paint = nil + + local function addChk(var, text, startOn) + local c = Components.CreateCheckbox(checksRow, { + variant = var, + label = text, + checked = startOn, + tint = Style.accentColor, + onChange = function(v) + chat.AddText(Color(0,255,0), "[libNyx] ", Color(255,255,0), text, Color(200,200,200), " = ", v and "ON" or "OFF") + end + }) + c:Dock(LEFT) + c:DockMargin(0, 0, libNyx.UI.Scale(16), 0) + return c + end + + addChk("switch", "Switch", true) + addChk("knob", "Knob", true) + addChk("radio", "Radio", true) + + -- buttons + local b1 = Components.CreateButton(leftStack, "Основная", {variant="primary", align="left"}) + b1:Dock(TOP) b1:DockMargin(0, 0, 0, libNyx.UI.Scale(10)) + + local b2 = Components.CreateButton(leftStack, "Мягкая", {variant="soft", align="left"}) + b2:Dock(TOP) b2:DockMargin(0, 0, 0, libNyx.UI.Scale(10)) + + local b3 = Components.CreateButton(leftStack, "Прозрачная", {variant="ghost", align="left"}) + b3:Dock(TOP) b3:DockMargin(0, 0, 0, libNyx.UI.Scale(10)) + + local b4 = Components.CreateButton(leftStack, "Градиентная",{variant="gradient", align="left", tint = PalettePick("btn")}) + b4:Dock(TOP) b4:DockMargin(0, 0, 0, libNyx.UI.Scale(16)) + + local b5 = Components.CreateButton(leftStack, "солид", {variant="primary_center"}) + b5:Dock(TOP) b5:DockMargin(0, 0, 0, libNyx.UI.Scale(10)) + + local b6 = Components.CreateButton(leftStack, "Центр-гр", {variant="center_duo", tint = Color(97,17,191), centerTint = Color(136,49,238)}) + b6:Dock(TOP) + b6:DockMargin(0, 0, 0, libNyx.UI.Scale(16)) + + local sld = Components.CreateSlider(leftStack, {min=1, max=100, value=42, decimals=0, tint=Style.accentColor}) + sld:Dock(TOP) sld:DockMargin(0, 0, 0, libNyx.UI.Scale(16)) + sld:SetTall(libNyx.UI.Scale(30)) + + local dd = Components.CreateDropdown(leftStack, { + placeholder = "Выберите категорию", + choices = {"Недвижимость", "Бизнес", "Промо", "VIP"}, + onSelect = function(val) chat.AddText(Color(0,255,0), "[libNyx] Вы выбрали: ", Color(255,255,0), val) end, + tint = PalettePick("dropdown") + }) + dd:Dock(TOP) dd:DockMargin(0, 0, 0, libNyx.UI.Scale(8)) + dd:SetTall(libNyx.UI.Scale(36)) + + -- bullets + local feats = {"The libNyx 1.0","Nyx Team топ","бээ-бээ барашек"} + for _, t in ipairs(feats) do + local row = vgui.Create("DPanel", leftStack) + row:Dock(TOP) + row:DockMargin(0, 0, 0, libNyx.UI.Scale(8)) + row:SetTall(libNyx.UI.Scale(30)) + row.Paint = function(s,w,h) + libNyx.UI.Draw.Panel(0,0,w,h,{radius=libNyx.UI.Scale(8), color=Style.cardColor, glass=true}) + draw.SimpleText("• "..t, libNyx.UI.Font(libNyx.UI.Scale(18)), libNyx.UI.Scale(8), h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + end + + -- right pane + local right = vgui.Create("DPanel", page1) + right:Dock(FILL) + right.Paint = function(s,w,h) + libNyx.UI.Draw.Panel(0,0,w,h,{radius=Style.radius, color=Style.panelColor, glass=true}) + end + + -- bottom boxes strip + local strip = vgui.Create("DPanel", right) + strip:Dock(BOTTOM) + strip:SetTall(libNyx.UI.Scale(220)) + strip:DockMargin(Style.padding, 0, Style.padding, Style.padding) + strip.Paint = nil + + local bx1 = Components.CreateVBox(strip, {variant = "center_gradient", title = "Бокс1", icon = Material("icon16/heart.png"), tint = Color(140,120,255)}) + bx1:Dock(LEFT); bx1:DockMargin(0,0,Style.padding,0) + + local bx2 = Components.CreateVBox(strip, {variant = "sunburst", title = "Бокс2", icon = Material("icon16/star.png"), tint = Color(255,180,90)}) + bx2:Dock(LEFT); bx2:DockMargin(0,0,Style.padding,0) + + local bx3 = Components.CreateVBox(strip, {variant = "model", title = "Бокс3", model = "models/props_c17/oildrum001.mdl", tint = Color(120,200,255)}) + bx3:Dock(LEFT); bx3:DockMargin(0,0,Style.padding,0) + + local bx4 = Components.CreateVBox(strip, {variant = "vertical_gradient", title = "Бокс4", icon = Material("icon16/flag_yellow.png"), tint = Color(120,180,255)}) + bx4:Dock(LEFT) + + -- list + local list = Components.CreateList(right, {rowHeight = Style.rowHeight, vbarWidth = libNyx.UI.Scale(12)}) + list:Dock(FILL) + list:DockMargin(Style.padding, Style.padding, Style.padding, Style.padding) + + local iconShop = Material("icon16/cart.png") + local iconHome = Material("icon16/house.png") + + list:AddRow({ + title = "Строка с иконкой и метками", + icon = iconShop, + labels = { + {text="Премиум", color=Color(255,215,0)}, + {text="Новинка", color=Color(90,160,255)}, + {text="Рекомендуем", color=Color(120,220,160)}, + }, + rightText = "12 000 kr", + onClick = function() chat.AddText(Color(0,255,0),"[libNyx] Нажата строка 1") end + }) + + list:AddRow({ + title = "Строка без иконки", + labels = { {text="Скоро", color=Color(170,120,255)} }, + rightText = "Бесплатно", + gradient = false, + onClick = function() chat.AddText(Color(0,255,0),"[libNyx] Нажата строка 2 (plain)") end + }) + + list:AddRow({ + title = "Строка с множеством меток", + icon = iconHome, + labels = { + {text="Скидка", color=Color(120,220,160)}, + {text="Ограничено", color=Color(255,125,155)}, + {text="-15%", color=Color(255,200,120)}, + {text="Сегодня", color=Color(120,200,255)}, + {text="Дополнительно", color=Color(90,160,255)}, + }, + rightText = "8 500 kr", + }) + + + local inv = vgui.Create("DPanel", right) + inv:Dock(TOP) + inv:SetTall(libNyx.UI.Scale(110)) + inv:DockMargin(Style.padding, Style.padding, Style.padding, 0) + inv.Paint = nil + + local invCellA = Components.CreateInteractiveCell(inv, {size=libNyx.UI.Scale(88), tint=Color(140,120,255)}) + invCellA:Dock(LEFT) + invCellA:DockMargin(0,0,Style.padding,0) + + local invCellB = Components.CreateInteractiveCell(inv, {size=libNyx.UI.Scale(88), tint=Color(120,200,255)}) + invCellB:Dock(LEFT) + + invCellA:SetItemIcon( + Material("materials_umbrellyx/moreicons/doughnut-1.png", "noclamp smooth"), + libNyx.UI.Scale(40), + { + title = "Пончик «Глазурь»", + desc = "Сладкий круглый десерт с сахарной глазурью. Даёт +25 к настроению и немного утоляет голод.", + tags = { + {text="Еда", color=Color(255,180,90)}, + {text="Сладкое", color=Color(255,125,155)}, + {text="Эпик", color=Color(170,120,255)} + } + } + ) + + -- (optional) leave empty to demo drag-drop target + -- if u want a second example, uncomment: + -- invCellB:SetItemIcon(Material("icon16/box.png","noclamp smooth"), libNyx.UI.Scale(36), { + -- title = "Пустая коробка", + -- desc = "Прочная тара для переноски мелких предметов.", + -- tags = {"Контейнер", {text="Обычный", color=Color(120,200,255)}} + -- }) + + end + + page2 = vgui.Create("DPanel", pages) + page2:Dock(FILL) + page2:SetVisible(false) + page2.Paint = function(s,w,h) + libNyx.UI.Draw.Panel(0,0,w,h,{radius=Style.radius, color=Style.panelColor, glass=true}) + end + + do + local subnav = Components.CreateTabs(page2, { + items = { + { id = "stylish", label = "Стильно", icon = Material("icon16/page_white_text.png","noclamp smooth") }, + { id = "pretty", label = "Красиво", icon = Material("icon16/newspaper.png","noclamp smooth") }, + { id = "modern", label = "Современно", icon = Material("icon16/fire.png","noclamp smooth") }, + { id = "nyxnyx", label = "никс-никс-никс", icon = Material("icon16/heart.png","noclamp smooth") }, + { id = "woohoo", label = "Делаем вуху?", icon = Material("icon16/user.png","noclamp smooth") }, + { id = "r34", label = "r34", icon = Material("icon16/star.png","noclamp smooth") }, + }, + default = "pretty", + onChange = function() + page2:InvalidateLayout(true) + end + }) + subnav:Dock(TOP) + subnav:SetTall(libNyx.UI.Scale(52)) + subnav:DockMargin(Style.padding, Style.padding, Style.padding, libNyx.UI.Scale(12)) + + local content = vgui.Create("DPanel", page2) + content:Dock(FILL) + content:DockMargin(Style.padding, 0, Style.padding, Style.padding) + content.Paint = function(s,w,h) + libNyx.UI.Draw.Panel(0,0,w,h,{radius=Style.radius, color=Color(12,14,20,110), glass=true}) + end + + local toolbar = vgui.Create("DPanel", page2) + toolbar:Dock(TOP) + toolbar:SetTall(libNyx.UI.Scale(48)) + toolbar:DockMargin(Style.padding, Style.padding, Style.padding, 0) + toolbar.Paint = nil + + local flow = vgui.Create("DIconLayout", content) + flow:Dock(FILL) + flow:DockMargin(Style.padding, Style.padding, Style.padding, Style.padding) + flow:SetSpaceX(Style.padding) + flow:SetSpaceY(Style.padding) + + local function cardWidth() + local w = content:GetWide() + return math.max(libNyx.UI.Scale(220), math.floor((w - Style.padding*3) / 2)) + end + + local data = { + {variant="vibrant", title="Nyx Team", desc="Делаем не только красиво.", from=Color(129,82,255), to=Color(40,192,255), icon="werewolf/0x00000000!0x8aaf8d0ab771a3b9.0x00b2d882.png"}, + {variant="vibrant", title="Nyx Team", desc="Делаем не только красиво.", from=Color(255,94,176), to=Color(255,142,220), icon="werewolf/0x00000000!0xfce5734b8472e83f.0x00b2d882.png"}, + {variant="glass", title="Nyx Team", desc="Делаем не только красиво.", from=Color(58,160,255), to=Color(40,120,255), icon="werewolf/0x00000000!0xfc708fa9e974b2cb.0x00b2d882.png"}, + {variant="glass", title="Nyx Team", desc="Делаем не только красиво.", from=Color(72,210,150), to=Color(28,190,140), icon="werewolf/0x00000000!0xf4e5b382cf4f862d.0x00b2d882.png"}, + } + + local function clearFlow() + for _, ch in ipairs(flow:GetChildren()) do if IsValid(ch) then ch:Remove() end end + end + + local function renderCards(query) + query = string.Trim(string.lower(query or "")) + clearFlow() + for _, t in ipairs(data) do + local hay = string.lower((t.title or "") .. " " .. (t.desc or "")) + if query == "" or string.find(hay, query, 1, true) then + local c = Components.CreateCategoryCard(flow, t) + c:SetSize(cardWidth(), libNyx.UI.Scale(120)) + end + end + flow:InvalidateLayout(true) + end + + local search = Components.CreateSearchBox(toolbar, { + placeholder = "Поиск…", + tint = Style.accentColor, + onChange = function(q) renderCards(q) end, + onSubmit = function(q) renderCards(q) end, + onClear = function() renderCards("") end + }) + search:Dock(FILL) + + content.OnSizeChanged = function() + local cw = cardWidth() + for _, ch in ipairs(flow:GetChildren()) do + ch:SetSize(cw, libNyx.UI.Scale(120)) + end + flow:InvalidateLayout(true) + end + + renderCards("") + end + + showPage(1) +end + +concommand.Add("libnyx_ui_showcase", function() -- comm to open + libNyx.UI.OpenShowcase() +end) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +-- libNyx by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw diff --git a/addons/_libnyx/lua/libnyx/lib/rndx.lua b/addons/_libnyx/lua/libnyx/lib/rndx.lua new file mode 100644 index 0000000..99b83e1 --- /dev/null +++ b/addons/_libnyx/lua/libnyx/lib/rndx.lua @@ -0,0 +1,756 @@ +-- libNyx and LiquidGlass shader by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw + +if SERVER then + AddCSLuaFile() + return +end + +if _G.gSims_RNDX then + return _G.gSims_RNDX +end + +local bit_band = bit.band +local surface_SetDrawColor = surface.SetDrawColor +local surface_SetMaterial = surface.SetMaterial +local surface_DrawTexturedRectUV = surface.DrawTexturedRectUV +local surface_DrawTexturedRect = surface.DrawTexturedRect +local render_CopyRenderTargetToTexture = render.CopyRenderTargetToTexture +local math_min = math.min +local math_max = math.max +local DisableClipping = DisableClipping +local type = type + +local SHADERS_VERSION = "1762169883" +local SHADERS_GMA = [========[R01BRAOHS2tdVNwrABuUCGkAAAAAAFJORFhfMTc2MjE2OTg4MwAAdW5rbm93bgABAAAAAQAAAHNoYWRlcnMvZnhjLzE3NjIxNjk4ODNfcm5keF9saXF1aWRfcHMzMC52Y3MAJQkAAAAAAAAAAAAAAgAAAHNoYWRlcnMvZnhjLzE3NjIxNjk4ODNfcm5keF9yb3VuZGVkX2JsdXJfcHMzMC52Y3MAWwUAAAAAAAAAAAAAAwAAAHNoYWRlcnMvZnhjLzE3NjIxNjk4ODNfcm5keF9yb3VuZGVkX3BzMzAudmNzAD8EAAAAAAAAAAAAAAQAAABzaGFkZXJzL2Z4Yy8xNzYyMTY5ODgzX3JuZHhfc2hhZG93c19ibHVyX3BzMzAudmNzAEAFAAAAAAAAAAAAAAUAAABzaGFkZXJzL2Z4Yy8xNzYyMTY5ODgzX3JuZHhfc2hhZG93c19wczMwLnZjcwDkAwAAAAAAAAAAAAAGAAAAc2hhZGVycy9meGMvMTc2MjE2OTg4M19ybmR4X3ZlcnRleF92czMwLnZjcwAeAQAAAAAAAAAAAAAAAAAABgAAAAEAAAABAAAAAAAAAAAAAAACAAAAP/f8igAAAAAwAAAA/////yUJAAAAAAAA7QgAQExaTUGUHQAA3AgAAF0AAAABAABooWEkeT/sqj/+eMp4JRdm72ukxt5p8rs/zYcfNXUYqyB32i/OWDlIlQhdb2KTLuh0Ov8tic+60q2CI4gWEUPbpQeN7/H1kC3Zv/Eq5uFc4/kZpoNmCKf6MW+va2/4L+g/Nu6iZuE7sCgULuBIIQ305yuErtyGzx32RIfvstsrKR/oy5YhcSTOynbKVIajcmQhtTnjiLHPowpAMiwVur8bbdZsO8R0nwt4XmidN4gZla2MMGUA63gRb5BCcxWHQVkghE1VxnkjkvKgDWnzPeHpzuFfthtETWD1p1JMvUXGwPUG4Zm4Gk/1UgPhdYOtTfvJonK+iuDEJDY4uK8m6AsUvCPZmysIqt1RboTkl4KMV92WzelVV1+HMbsQAQjyOZbm77+l2onNdWlSgXUcPZcSaokmh9P9y2wTUSSieLTuxHHH0x9c8yqLE3qdyzS8hAbqbk6acZNBewk46kBrLTJmi/RDoDYP7Sf3zagp/8AQBrUDrLvCxu2+cLSszVxOLcBlexKrFyD92oriJ1cX2yJgK0YqmMtDg/DLM721YJ8bJ1JYeSR7d3g/XtAhq2YlATrYcdPFCwMUKqOfZYQPzu5PLXFFwCGPZ4yJIrgjerr92VlKC63zqp7NScTwz4F3cKj+g7vv4CG4vVMeIvxq3DWNuSGEYH1NM6UBLYdVgYOERK7kijCUy4MlXIe2W7eYX1AKbFJqVPu45f/y5fQ6peQtLhXi+bXfD1CrILyYGziMmNfxVgsfJdr3wb3E/l3KQBRUZcooQ5GxzhZTrh4GejTeXmbVuba2prAa7qitbUuQGPXwG0XH1BkaBmq+/f3diqQ8jKMjhdw1lXklaLoJPjpIr2ccOxy0NiBrKw/Y6s6UfXV11n6djVP8n7dOt6E5ULeDkEU9hfKnp3dWXPMv1b8P0iHUo3i+fkGbv33hoJ0MvcI0RD+UlIsCcCSzSwBYlCurINz/yKTLavcLy8ejb8n5mxujKqlifykmUvLNf480SAGWcAJ+KPXe9H/3y250T48OA0G7IV3EURPJgVieu3ufzHThOxGYMyKWG3tvQFq9tcyeaRurReeqLhaISCSmFYlndKGmM3tHALxY2u9oLrmD9UfjV7t0kbW9LbJj+bxv9kFpyP2C5exrXyOYw/P3ppRoY897AxuuL3C1FFmXkvgIcOHtAs8v6ReRYnyR3izmnY08qCwwTzEPJwGk8jfRzRxeSM1z3q+Wa9wtwLEHWtDCvCEWy4Y/zeLq+0sQZKLObhrzSazIoxWlhm+H5rYmcY9XzvBxjoqeOndHcQFCXPyT/RsOOsksEyYtbBd18KCD8JLc2KZT5gk34w0yUv3GcFgGFZpSDSWlZN3tLq+uePHC58oFJcQmQ7BePQ49fNMuApIJnNCBaOHRuBvxi37mBsyEwR3EYPxHVMaZev+pRMXc/IUZnA4hDqkTqZjLdyWDwrreczIJ3um+BXqhV7qw464NOlTQq7yez8zuFi7IFow2YFQoUfrFRu1Mw/BhGEkQLuArIgTetBBuEHM5NuDaiIWFDn8Qz6cdhFRptLhLgE3o7ILk5IXUYyh9VzjnqUBF/XEicNvbtbLBUQjlCfdpcC+6XwJJ11kDGoM1AgBoTt6wDhHbNVGbOT84md7wFZz0YI/dLrTE9xj4e/7XXctAB/eTuC4YmbHTIQ1Y94zfjCP5tMDsMDfMahjouL668kHBASYGsiXyTBTPa4FN8dbayuiQGr+Z//TcEEweHfFGW7FAK94sI821BEbJqpCXxBgqumT8Ho/MV5xcKFXCYkcIFlVnb0SNakNoQPpXr7Sb59/WXGqTlUAzOWaAOEG3OJh4ke6O4UGxSJs5Z5z3G2yRbQOH1sVWFJGgyDLjqYP+wDo2N6kwbi8TpZkmA3UdeIiNbxtMNBbM0XuS8PAmLc36Rq5aU66f6Z14Z/fj/Lynlbm12E8TY2oV5CiX4hAL9wqKhV/LEhLOzOJzE0WpE/ZP1/Uy2RW/wMAG+FGdzyVEgBrkkoADzMEM7TRs+6ZwJcyOGZLIz2r04d8jxanc4TBnIcloDUF9J3N6LA/EL0SeFT9ZD5NGnmLrVs7wbqCXwc2QLdtOmJaCrhuZm/Z4eR/FFEjpjymaD5KvuMOC462qPScU9ubCN2NMkqsV04ge1uZ/XT3zN1BwRT50f6dtQyR7Isxw6vhj60AMHBN4bTcIkV4NvxLwacR02bk09Qd0S97R4wDQ9vB+OJKPwED3uEjn9IsTvppPf7d823uWpX70cmkIeMS6dXS1fYR7l6HP41VH11XTNqfuOjieyjE3LgZN4bq3PFcl7HJP8C4X3OlQGo/IoDM+YHHYaAvaKHaechqdLUi0aZWhh8yUNZLJP4WMVZUKHlP8eZtEzvb08XqsdLFRnLY1J9RmlMUOMzHNwjQAI7Ks0056Sa2NWjKtxxoL5IdUce+y81ZTO/TeDRQyMnUIktZk4T7sfu7/olgn1C4d2IePkEb06aT6EW5h4hH4Cs6GbE7XVO1fvO1bZgQzU9mQiZbZDGuY5B29uRPK451rjkBkIvf0hYSsB2Hcv+uOWADHFnWOOEkOYCCiYVSYTxDwJN6GD1Lg2bNWD1PhGlwzbQUo/p7UazsjwQoPMQYDVLbwqgUII690mAfaPPJAEtS1VIzTkzJMRHdELwLkSR6HHLWFDDylQInkGuE/typkniCZo53fmUDPU44F2GgFX5nnoyltzyubq82H3xHD0oLIiV0+mTJkaKXZhAFxHxOJFdJgE/xGk3Ee/ReipgOZ1RIIhdvy7JEdDsOFYrTyyNFCcnNYR/lT1hIfHJXWO34SuvBGK7XfAe/lpRC/bA0qEhaONh5hoai0ucf2oYegGYJS9sPhtovwpEFhpr55HmzjNW1ZztXqN3tqyk8BGVl//civft5kJ79KnJ6dBStXOtc8cSbWTXDGopYKUQ+poO8XCNKGZhGbcBF54BYHPsnRIaHzIHCcpzFfCmQKRiGV02L8NG5XK/6k4i66iI8m3rvM48oQP+BrYL3dKgAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAAKO94CgAAAAAMAAAAP////9bBQAAAAAAACMFAEBMWk1B3A4AABIFAABdAAAAAQAAaLNe/IC/7Kkwiipoar4zBgXLKcwk5qf4ClLgjwknieovgTO0BCmHBVttX0UcPmCxo2+mFl1Nw5cK/FhIc0SmvCsJgv5RTJjuvQhtX6ThXMoKKWec+NA3Sq3J5VWXp+Ezh6z94RCQqCCYMqNm6ICUtN0l1uT0PxvdlaKga+cuBtWNWgxGmd6BfsD2HKMMkoudbOlfWadcwItue9xEd3xat7ppS6Em3678YJZRxgnRKrzqsQaQTHPYlQdrw9hq9nRh8cBYDlLJ3NE2Roes7wkfgYgyIzn7dCdpLbYwFQ5z6yS10zpP4pw3Rmxr7l3HYumELHn5xtYwr4kmdjj0gm1H1bld44I/oQYlV4ZANUTg6XqUxCMfCKe0QYzo3JICooz/pF5b85/ruez2kWDiwjgNy7+u8xzu7sJjPZU9W+1oRYQWVFepcKFDbnuVmYHxo9kxHuMe3LfdGjctkKyGb0XS47gTct2A49iMYv7oYAsAsSKb4w1cJz5cKWH6gIq8CV+lbI1mtlrBzVXVrEix6FHyuJlJz7W9AV1qyY5wz9JoIkPY3KsCpS1clLRfTiRrJJCvRtsqpQgWjj39pKNgusF0QMyGtxrT8xgiMV38nrctUAT3bUgjS/otc3PZkzNv5RDqbdRDcM2xb6A/HsjW6aB9cTFyQY1eWbUl2DzkizhnkiCn5pdBTSJWjveyy6OJQHUFGOg3wSCINvD2M4fcOuUB7a4WZuiJVcEiR5DVgW9NuUjWFWJRyQ9El0hI92qJzdNNeEsT6/khib7KZ72UYZB8VPkXLVuBvQFu9nU22zGdUM4SFURIS0nGKId/RckxO3pvIvmkFG8QlQvRKoSIW5fib/d/P82e2oY72c+KxEqG/7rW0SUUsKhkgqt8xd+ztCcfH+rjn0Mmya5VvdT0X3TwZQgY0tO+rB/b1NVqT8LwI+RlIUcmGVWj1PeSbZOQn5bGIeZbMpLPDoH9oruysieeqfjvvUaAxxoHNlrzVWDGg7GJcrTtKiXdU2r5zEheMss6F/EkhnQFA7nSQv4OLDvG5kdtFXzyyYtWPbuhEPbM7wpUWSoIuX++aXp6zx1R+eJ6yh0ChN7nnigYeFMkEpMKEbCbHCQ7xUUbH6qIZihjw2lErEUu4ZYTVlz/esGkq9735pyTIOvKWOc+NvTsJ94BCmrWPactwMGqD8qNi/uuER3VDG52ziAkGIEaVO9RtwPyZPni2HYlhnhtghtDJYi+wqDOOsupwFr/138zRyIWSMyNyKLy+6UXgNTqy/UDiGHqqA8gDOmZALYKFp+Jy/OyV8zHtpT9moe1nJ23VdPJkv0o3JhvPxALSPEgXUV3tRDn4Vst2HiAM13D54rMrm8080wps1jc667RNLFTc3C630EDU0qeKgwvtuskW5YkinjosDJA0FriwJeRUyPqLjNJe9bEdPxEbQnOwXDtsfBZaV8u+Ne6jUgDSGn14uCr5mMePZPop6fvBkWyw2kiPiQYKJr4DorQAPLitzFm98FaQKq+134/C61tDTtL37XeIrElV7ALzU8SrXKKXkXbzrTG+vG014JsESsxT11hYXDwQh0vbOYO3kItq9fXVWxzP/XG0dvdRMEGOKg/qP3RnMt19FxLEA6hCXhyCSQhfmLWCZcBofUoLervRZZmOnG796nIECA71HiWYTvXbF9I0U5rRAjUhbpaxjfiJn1ZXLyqLfiE0sCnMecbsXIdKSzyVaEA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAABBilVUAAAAAMAAAAP////8/BAAAAAAAAAcEAEBMWk1BnAoAAPYDAABdAAAAAQAAaKNevIK/7Ko//neAPmUWEIFe0uK7chbMV69/+jYSuUyruWr/GifVGJvKSmW0cq6McmhIS65jK8F9cyUZRQpHrEaKWDtGns1w7n68bPn73mhlTK3IVAnZzbtgr7CYjzO25UO772e8uuy6Qz/KTuoeoFdjBV4ieIf+9jeFkEwpAHkZ6aTSMjI8/YSvLPlM2Xp9UBWBDnLS2IRcQp8cRFUD13ie+d8B+cs+Od+RY7qRZUboI3KM6San//H5zrVnz/IUy1kUcMi8gL8MswCCqFKNeO7G/Q58ebpwhYdGtChTI6qW0/rX+Ysqe4uDx5FWZ8exnsgK7ca6sQwqIeEaNsMb2Yi8UWjdBm2BEsdM0+fu/HrHHCapPkwupvr4IRDduahJihg3jV6Cr8zMpn7kg+YPit39UfFlNpf+D5nKaCax6O/SwqcmGTyk+lXHTWS1W/vLa2getZRQ/yaHrkOnQPc7wLCMnoU1tw/H1QQHGN2FWixwPqym2hvmMYBdfv2wla3aiAufgZN8c4zkBjQKSjWLa+ymTBu3br8wAL+ywocSFKepi3rEMAH3x/3x8rOyrDR35Ba/osQuouD7fzpmpzYAAqznUmbKI9oaHnmIepEmWMEvRziKQu9v6CW0mDxaUnoSEKHTAx2biiCN7/gWO5DGuhBw+wVFpsi+awSOMFfLdQUIkhAgKaF0tyw6fObhJ6cqSFvCu/xevdmhnvlGxY1yl5AdftQvwlWQuTaWY6QSVUnjHUcTRv2FtibPx9g+tGOCyATYGJqVAVYPFZkLdQ5YcQgjYxpfYjSNTHegM/hc5W27PaaD4Ezrpzg8+O2xRMAyv53JNttJbdCi6H+0Gzt0CfqDfaK3JQMre9C3ZmDfMc3ONcgQ2r63kshXyhLJyDBP5mL1MCso2I9Jw9tkIHJ/Cdruutl7COqCS0fncGqEOXnLwpGbMbGbvAipOIWfECnzS1pmgAhnO8MWwxHg89ntpV/LOPyReiKVyOnMJwsxuZS0xcV8M/CHVLs6bIJKTlHQI4swml3GxEgX47PPS1amr9JkJgh51LB+xZOuoUcIMY7riMU+/ffiePO66Hrrkch5V4x3eb1BHFNOLrM4FNVf5Hpve78Algc0+nZ44bbKJBhKs0VoAtNhhonWy5/LKbgUeE+4FfygsaJOO7s+g044Mx3183FiE3BrX/TlMee5Rc8z4B/0YOOvt+xcA0UmKkYzeMyEINLHXCPV15dxIIRghHcHwZ+x/CDjWWMJaeQyjYAC70T9YG0/7f8JV84mnshLtOKdOP8d0IzuRV+hZFn3RF36EHIayWVWQj0hOcPk5l9Nu4ykI4ae6BXDxH5G+V7RKk0AAP////8GAAAAAQAAAAEAAAAAAAAAAAAAAAIAAAC6r22kAAAAADAAAAD/////QAUAAAAAAAAIBQBATFpNQTwOAAD3BAAAXQAAAAEAAGiLX5yAv+ypJ8XFRT3O0G6maK90LED0FHc35RuuoSvDh+sGk8dSu823rorz3iORqfOy/v2SMOcmMiED8W8zlPdrxw+3ItWHK0Rk2Hki9DvaRqRhNUYU856wps9aiSXH1z1/lWZPt6PfCRrFevbiGhynoOsuq9sT76qngqvbclKmBqiXMauMqz//ehbra8snnJg9Vd49XCDGgd1gZG+vC6ffs5Sy0oXT5kmQCnk9ILEgrGaQjmbCThlasCyBUzPy/jr4t7PU7gjb9Y9wazN5g0Wk6cqgWnAXiweHUB01aXbBJB0ht0Riy+5y69LBsFrHOB+UieuYCvLokRKuGvLlRIHKT5/L+Rp6arU1eefjbkcLBVlAKGwzbcyXT4XL4O80Tmukvmtsv991/rJWLAHb14qEeqB+LlqhywTnQp3ss1WONYjI/aCdOsYBs+EKZZ4D6XBkiFPuEsht5t5MOUxqJaFpzJ2XJGkwX+Ka94wsOQ42uzDy5pYamktjHMtMP6VG+gr56YUELlH1NzIHtPMji2pfRtkMd6Ho/0zEYX51T0BvbxfeW0y3CBdCbTdg3eEpqv7t6rRNvT/9QbphD8T3zlFfDb385qjPYmRXWrdhoRItB+hkXT2lhRxS9n0HBkuZiL5hkXP+7oIdfO5yF8uqDJzzURLEAeyq+fWpXTGDoaiN7cQyC7aHxOeaiL6f5Ka5vtEcuKWGxqOkxfzIL7DGc1JniPJfOLIPF1egAzRL3/PJa/XmMHEhRVrNoK4CFRZknyzcBoWL+Qx5ZazAXQAgYS8Ir41xxVR+yJWmK14rkDBCubVXGV3z6gmynb0Hrf0mnBj26g4/AioHtH5tJwdOIkhCzLy+mKXkvjmG/BF+l9yTauy41V+JC2AbLY1XfQPcyuvKySpGbYy9XEnU0XFM4MAF+HMfO/S8g7GZUMTCx6Q1azcahAXk1/mqu/zPygg0hGn5jB5NIHT2IF47YsAFuosmKUsnRYuGABly4Q3LTpAXLgtZe9OaZa2FI7lI2EqAUQCpoDJSQek96o9uc53NomlR1WanO+04Eb/UhzHDAtBIdof0zivVxDMwtI7hmd8iqWF9SavZIHBPtpU96ItvcDhJq+hrqo0l2n5ANeRdM+lXoa2gUW1bkwJ28LTzK5ZDqRf6AELMPdHiU9Sy1zDbI88DVdd7kqNp8KEnW4mPdsYxcjrvOLv0+eDSkf8AR1wgbwBIYfxeyf0udK3poBKaZ6oF732bmBj2NvkBxHbpkJn0LOsuGmKUrWtSFZ+XuH0eB1FatHXErAJ3OIkdOqSH2cAVobwiKEAYGkYUBgaaiMu1+UNb2ni22RCcNm8ZTzud8KpCWxLgojd4JrXkUXrdcglHKa7nvpu/5psRvc8soQWbZv2m0eH+0lqi675tGChQByg5Eysypg3KoYH4g1yBfjTJcw3O8PtRPU6eJlZqQWD+7ripKicxyYdGbwdV+GUT6+jTPk5SRZdKG/lvR+sYggqtx5xnzGAIe4uGKV3VzETqinogzGNBHwc1pnPoRUQCAFJaXTDfwk6WbcVjtmwE+XvnHZXHTLsG4REHlv28m2GWkfsynVBOFkpt0uI+yI6OLDNiDNcrhGAowxdbmLyZ/UrZBhoYd1X/7fb+jf/EuHiXtxVpfbBA0sG2qyM/kjJ0bYFKOxdo6lDJciS5VZNX5mgqA5cAAP////8GAAAAAQAAAAEAAAAAAAAAAAAAAAIAAACN8B5UAAAAADAAAAD/////5AMAAAAAAACsAwBATFpNQVwJAACbAwAAXQAAAAEAAGiTXtyDP+ypJ8XER2OOzvX2MjXXN5GNuIng/BU66rcRSXuu6LBGgfsQ6/bIA6o7OV+coaBUP9qqayre5iA/3kR9c4G/AuM+i4ltJsQsqYG2rvVegsSP8n1064I7FjzivFmcU36pfzCPJ7Eube/t6t9PeUBnVOU1A4y5qVeA2iHojf5cJBzD5Ug4rbQnJK8i4P1/ZoccMuBEGiGIp9pg62Gk2o+cKd4pRCzgJlKDXPUW3XifynMjcAAlXtumFiQh+aVg5Y+Swe+11Xpfm7oxo3137Jvg5yI4V5Y2E1Dsb2lx9DSi71k1tnwj1SdvJqs2t4ScKwiqLrKicav1AElQeslSbd3yB6eKiGDEoxu/Bih4ubqPiJaSU0KNGspDm/lnlZmUYOm9SatCBdpQ/ZF0q9iHD4sTGjVwDwm0v4B8t4JXiPRE0usabOddIwrDvBYYoaBq9wtz3N/ECI72cgpLIulvFDho+SXBRtWIcE/93T7YT4l64qlEwpsAZvPjODKd63JOT4xG1GJPeJ1Y4k9nnB8xIBMYhE7lppA7Mu64FgUTUNSfO72R8t6Hx751Eb9shwEi2WJhjpsUAFgD3Fm/KKtKqmdhzgpQIMju8qriUj+DMK6q5lJOnk1swpPyMtzw5EXisJeJqj3/BkE/SeqjIN5dIB7zX82Ck7I1p8CEHEghCoRpcNKPnMWhAPraUDHpiUDJIvq8D/v5YOwf1h5VlnQFlyyR8bzushITP+W2/NVoWBJMtOoN2ilR9/nlpbQgWgBP8s/kQh9AtXuyq1GoCMBLPeqLoyhe/I7uOIgRIVBlN+UIgTTBIKYyM7B1gu+6rp0LRwbNu+S72MD9fSpQjhK64wU1hJqbLrx+pXGPZb99JTbd+xz+aZeyATM7rRROK4K6yPYLIe7iIa2xiDhO1pAkuzpqka5OEPhHz27XZKUyiVA7c58Bi2JKpudMv+YP/DDP+hVQ47QiJ/GCI0WIf2OwnwRUNmBm9EnjE2v6jh9jJLGNZUcCCTOBGE3dRP9hnfUkCGyTYy1Uy8yKlxOIFJiH7/2DMrJoHuJOnVvrTBaPuTTOB0NWDeNg0ljKcWOk9Ic39nQIV11jqLPR2MXCZCWG12Fz9pPgKblVA8hYenP9zUS9roCJNNqbpLkDC8MOcOAA+rzhRMcOYB/RQdFnzuNFxCB6vXgI5kQeaflT93micsKYIxkqV83goiOQVYQ8TGWScC4XtRLAAP////8GAAAAAQAAAAEAAAAAAAAAAAAAAAIAAAB3Q0KZAAAAADAAAAD/////HgEAAAAAAADmAABATFpNQWQBAADVAAAAXQAAAAEAAGiVXdSHP+xjGaphZkpGU+Usm+MtQUH83EbXXMjgea+yS5+C8AjZsriU7FrSa/C3QwfnfNO2E25hgUTRGIDQmsxKx7Q+ggw5O2Hyu6lPnEYPfqt3jvm3cjj6Z1X02PoibeZEF4V28Or5mSkKcqgZk6cbnqeeVgnqfAvD/O3uLu+nT7VAOydRrNBSD1yQVTBZUZtIJLmvDuIE27Eo7GuwHoYCUrVUwgW6q0SbikkxwEeOthaz5bMITbOd2JgjhkHkQV22VJTNinlRW2ADS1E/dJnyAAD/////AAAAAA==]========] + +do + if _G.gSims_RNDX_ShaderMounted ~= SHADERS_VERSION then + local dec = util.Base64Decode(SHADERS_GMA) + if dec and #dec > 0 then + local path = "rndx_shaders_" .. SHADERS_VERSION .. ".gma" + file.Write(path, dec) + game.MountGMA("data/" .. path) + _G.gSims_RNDX_ShaderMounted = SHADERS_VERSION + end + end +end + +local function GET_SHADER(name) + return SHADERS_VERSION:gsub("%.", "_") .. "_" .. name +end + +local BLUR_RT = GetRenderTargetEx( + "RNDX" .. SHADERS_VERSION .. SysTime(), + 1024, + 1024, + RT_SIZE_LITERAL, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(2, 256, 4, 8), + 0, + IMAGE_FORMAT_BGRA8888 +) + +local NEW_FLAG do + local flags_n = -1 + function NEW_FLAG() + flags_n = flags_n + 1 + return 2 ^ flags_n + end +end + +local NO_TL, NO_TR, NO_BL, NO_BR = NEW_FLAG(), NEW_FLAG(), NEW_FLAG(), NEW_FLAG() +local SHAPE_CIRCLE, SHAPE_FIGMA, SHAPE_IOS = NEW_FLAG(), NEW_FLAG(), NEW_FLAG() +local BLUR = NEW_FLAG() + +local RNDX = {} + +local _fb_frame = -1 +function RNDX.EnsureFB() + local f = FrameNumber() + if _fb_frame ~= f then + render.UpdateScreenEffectTexture() + _fb_frame = f + end +end + +local shader_mat = [==[ +screenspace_general +{ + $pixshader "" + $vertexshader "" + + $basetexture "" + $texture1 "" + $texture2 "" + $texture3 "" + + $ignorez 1 + $vertexcolor 1 + $vertextransform 1 + " 1 then + local inv = 1 / k + TL_, TR_, BL_, BR_ = TL_ * inv, TR_ * inv, BL_ * inv, BR_ * inv + end + return clamp0(TL_), clamp0(TR_), clamp0(BL_), clamp0(BR_) + end +end + +local function SetupDraw() + local TL_, TR_, BL_, BR_ = normalize_corner_radii() + local matrix = MATRIXES[MAT] + MATRIX_SetUnpacked( + matrix, + BL_, W, OUTLINE_THICKNESS or -1, END_ANGLE, + BR_, H, SHADOW_INTENSITY, ROTATION, + TR_, SHAPE, BLUR_INTENSITY or 1.0, 0, + TL_, TEXTURE and 1 or 0, START_ANGLE, 0 + ) + MATERIAL_SetMatrix(MAT, "$viewprojmat", matrix) + if COL_R then + surface_SetDrawColor(COL_R, COL_G, COL_B, COL_A) + end + surface_SetMaterial(MAT) +end + +local MANUAL_COLOR = NEW_FLAG() +local DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE + +local function draw_rounded(x, y, w, h, col, flags, tl, tr, bl, br, texture, thickness) + if col and col.a == 0 then + return + end + RESET_PARAMS() + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + local using_blur = bit_band(flags, BLUR) ~= 0 + if using_blur then + return RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + end + MAT = ROUNDED_MAT + if texture then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", texture) + TEXTURE = texture + end + W, H = w, h + TL, TR, BL, BR = + bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + elseif col then + COL_R, COL_G, COL_B, COL_A = col.r, col.g, col.b, col.a + else + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + end + SetupDraw() + return surface_DrawTexturedRectUV(x, y, w, h, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.Draw(r, x, y, w, h, col, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r) +end + +function RNDX.DrawOutlined(r, x, y, w, h, col, thickness, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, nil, thickness or 1) +end + +function RNDX.DrawTexture(r, x, y, w, h, col, texture, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, texture) +end + +function RNDX.DrawMaterial(r, x, y, w, h, col, mat, flags) + local tex = mat:GetTexture("$basetexture") + if tex then + return RNDX.DrawTexture(r, x, y, w, h, col, tex, flags) + end +end + +function RNDX.DrawCircle(x, y, r, col, flags) + return RNDX.Draw(r / 2, x - r / 2, y - r / 2, r, r, col, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleOutlined(x, y, r, col, thickness, flags) + return RNDX.DrawOutlined(r / 2, x - r / 2, y - r / 2, r, r, col, thickness, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleTexture(x, y, r, col, texture, flags) + return RNDX.DrawTexture(r / 2, x - r / 2, y - r / 2, r, r, col, texture, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleMaterial(x, y, r, col, mat, flags) + local tex = mat:GetTexture("$basetexture") + if tex then + return RNDX.DrawTexture(r / 2, x - r / 2, y - r / 2, r, r, col, tex, (flags or 0) + SHAPE_CIRCLE) + end +end + +local USE_SHADOWS_BLUR = false + +local function draw_blur() + if USE_SHADOWS_BLUR then + MAT = SHADOWS_BLUR_MAT + else + MAT = ROUNDED_BLUR_MAT + end + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + SetupDraw() + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 0) + surface_DrawTexturedRect(X, Y, W, H) + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 1) + surface_DrawTexturedRect(X, Y, W, H) +end + +function RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + RESET_PARAMS() + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + X, Y = x, y + W, H = w, h + TL, TR, BL, BR = + bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + draw_blur() +end + +local function setup_shadows() + X = X - SHADOW_SPREAD + Y = Y - SHADOW_SPREAD + W = W + SHADOW_SPREAD * 2 + H = H + SHADOW_SPREAD * 2 + TL = TL + SHADOW_SPREAD * 2 + TR = TR + SHADOW_SPREAD * 2 + BL = BL + SHADOW_SPREAD * 2 + BR = BR + SHADOW_SPREAD * 2 +end + +local function draw_shadows(r, g, b, a) + if USING_BLUR then + USE_SHADOWS_BLUR = true + draw_blur() + USE_SHADOWS_BLUR = false + end + MAT = SHADOWS_MAT + if r == false then + COL_R = nil + else + COL_R, COL_G, COL_B, COL_A = r, g, b, a + end + SetupDraw() + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.DrawShadowsEx(x, y, w, h, col, flags, tl, tr, bl, br, spread, intensity, thickness) + if col and col.a == 0 then + return + end + local OLD = DisableClipping(true) + RESET_PARAMS() + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + X, Y = x, y + W, H = w, h + SHADOW_SPREAD = spread or 30 + SHADOW_INTENSITY = intensity or SHADOW_SPREAD * 1.2 + TL, TR, BL, BR = + bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + setup_shadows() + USING_BLUR = bit_band(flags, BLUR) ~= 0 + if bit_band(flags, MANUAL_COLOR) ~= 0 then + draw_shadows(false, nil, nil, nil) + elseif col then + draw_shadows(col.r, col.g, col.b, col.a) + else + draw_shadows(0, 0, 0, 255) + end + DisableClipping(OLD) +end + +function RNDX.DrawShadows(r, x, y, w, h, col, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity) +end + +function RNDX.DrawShadowsOutlined(r, x, y, w, h, col, thickness, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity, thickness or 1) +end + +local BASE_FUNCS do + BASE_FUNCS = { + Rad = function(self, rad) + TL, TR, BL, BR = rad, rad, rad, rad + return self + end, + Radii = function(self, tl, tr, bl, br) + TL, TR, BL, BR = tl or 0, tr or 0, bl or 0, br or 0 + return self + end, + Texture = function(self, texture) + TEXTURE = texture + return self + end, + Material = function(self, mat) + local tex = mat:GetTexture("$basetexture") + if tex then + TEXTURE = tex + end + return self + end, + Outline = function(self, thickness) + OUTLINE_THICKNESS = thickness + return self + end, + Shape = function(self, shape) + SHAPE = SHAPES[shape] or 2.2 + return self + end, + Color = function(self, cr, g, b, a) + if type(cr) == "number" then + COL_R, COL_G, COL_B, COL_A = cr, g or 255, b or 255, a or 255 + else + COL_R, COL_G, COL_B, COL_A = cr.r, cr.g, cr.b, cr.a + end + return self + end, + Blur = function(self, intensity) + intensity = intensity or 1.0 + intensity = math_max(intensity, 0) + USING_BLUR, BLUR_INTENSITY = true, intensity + return self + end, + Rotation = function(self, ang) + ROTATION = math.rad(ang or 0) + return self + end, + StartAngle = function(self, a) + START_ANGLE = a or 0 + return self + end, + EndAngle = function(self, a) + END_ANGLE = a or 360 + return self + end, + Shadow = function(self, spread, intensity) + SHADOW_ENABLED, SHADOW_SPREAD, SHADOW_INTENSITY = + true, spread or 30, intensity or (spread or 30) * 1.2 + return self + end, + Clip = function(self, pnl) + CLIP_PANEL = pnl + return self + end, + Flags = function(self, flags) + flags = flags or 0 + if bit_band(flags, NO_TL) ~= 0 then TL = 0 end + if bit_band(flags, NO_TR) ~= 0 then TR = 0 end + if bit_band(flags, NO_BL) ~= 0 then BL = 0 end + if bit_band(flags, NO_BR) ~= 0 then BR = 0 end + local shape_flag = bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS) + if shape_flag ~= 0 then + SHAPE = SHAPES[shape_flag] or SHAPES[DEFAULT_SHAPE] + end + if bit_band(flags, BLUR) ~= 0 then + BASE_FUNCS.Blur(self) + end + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + end + return self + end + } +end + +local RECT = { + Rad = BASE_FUNCS.Rad, + Radii = BASE_FUNCS.Radii, + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Shape = BASE_FUNCS.Shape, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + Draw = function(self) + if START_ANGLE == END_ANGLE then + return + end + local OLD + if SHADOW_ENABLED or CLIP_PANEL then + OLD = DisableClipping(true) + end + if CLIP_PANEL then + local sx, sy = CLIP_PANEL:LocalToScreen(0, 0) + local sw, sh = CLIP_PANEL:GetSize() + render.SetScissorRect(sx, sy, sx + sw, sy + sh, true) + end + if SHADOW_ENABLED then + setup_shadows() + draw_shadows(COL_R, COL_G, COL_B, COL_A) + elseif USING_BLUR then + draw_blur() + else + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + else + MAT = ROUNDED_MAT + end + SetupDraw() + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) + end + if CLIP_PANEL then + render.SetScissorRect(0, 0, 0, 0, false) + end + if SHADOW_ENABLED or CLIP_PANEL then + DisableClipping(OLD) + end + end, + GetMaterial = function(self) + if SHADOW_ENABLED or USING_BLUR then + error("You can't get the material of a shadowed or blurred rectangle!") + end + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + else + MAT = ROUNDED_MAT + end + SetupDraw() + return MAT + end +} + +local CIRCLE = { + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + Draw = RECT.Draw, + GetMaterial = RECT.GetMaterial +} + +local L_STRENGTH, L_SPEED, L_SAT = 0.012, 1.0, 1.06 +local L_TINTR, L_TINTG, L_TINTB, L_TINTS = 1.0, 1.0, 1.0, 0.06 +local L_SHIM, L_GRAIN, L_ALPHA = 0.9, 0.02, 0.95 +local L_BLURALL, L_BLURRAD, L_SMOOTHK = 0.0, 0.0, 2.0 + +local LRECT = { + Rad = BASE_FUNCS.Rad, + Radii = BASE_FUNCS.Radii, + Outline = BASE_FUNCS.Outline, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + Color = BASE_FUNCS.Color, + Strength = function(self, v) + L_STRENGTH = math_max(v or 0, 0) + return self + end, + Speed = function(self, v) + L_SPEED = v or 1.0 + return self + end, + Saturation = function(self, v) + L_SAT = v or 1.0 + return self + end, + Tint = function(self, r, g, b) + if IsColor and IsColor(r) then + L_TINTR, L_TINTG, L_TINTB = r.r / 255, r.g / 255, r.b / 255 + else + L_TINTR, L_TINTG, L_TINTB = + (r or 255) / 255, + (g or 255) / 255, + (b or 255) / 255 + end + return self + end, + TintStrength = function(self, v) + L_TINTS = math_max(v or 0, 0) + return self + end, + Shimmer = function(self, v) + L_SHIM = math_max(v or 0, 0) + return self + end, + Grain = function(self, v) + L_GRAIN = math_max(v or 0, 0) + return self + end, + Alpha = function(self, v) + L_ALPHA = math_max(v or 0, 0) + return self + end, + GlassBlur = function(self, strength, radius) + L_BLURALL = math_max(strength or 0, 0) + L_BLURRAD = math_max(radius or 0, 0) + return self + end, + EdgeSmooth = function(self, pixels) + L_SMOOTHK = math_max(pixels or 0, 0) + return self + end, + Draw = function(self) + if START_ANGLE == END_ANGLE then + return + end + local OLD + if SHADOW_ENABLED or CLIP_PANEL then + OLD = DisableClipping(true) + end + if CLIP_PANEL then + local sx, sy = CLIP_PANEL:LocalToScreen(0, 0) + local sw, sh = CLIP_PANEL:GetSize() + render.SetScissorRect(sx, sy, sx + sw, sy + sh, true) + end + MAT = LIQUID_MAT + MATERIAL_SetFloat(MAT, LIQ_TIME, RealTime() * L_SPEED) + MATERIAL_SetFloat(MAT, LIQ_STR, L_STRENGTH) + MATERIAL_SetFloat(MAT, LIQ_ALPHA, L_ALPHA) + MATERIAL_SetFloat(MAT, LIQ_SHIM, L_SHIM) + MATERIAL_SetFloat(MAT, LIQ_SAT, L_SAT) + MATERIAL_SetFloat(MAT, LIQ_TINTS, L_TINTS) + MATERIAL_SetFloat(MAT, LIQ_GRAIN, L_GRAIN) + MATERIAL_SetFloat(MAT, LIQ_TR, L_TINTR) + MATERIAL_SetFloat(MAT, LIQ_TG, L_TINTG) + MATERIAL_SetFloat(MAT, LIQ_TB, L_TINTB) + MATERIAL_SetFloat(MAT, LIQ_BLUR_ALL, L_BLURALL) + MATERIAL_SetFloat(MAT, LIQ_BLUR_RAD, L_BLURRAD) + MATERIAL_SetFloat(MAT, LIQ_SMOOTHK, L_SMOOTHK) + SetupDraw() + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) + if CLIP_PANEL then + render.SetScissorRect(0, 0, 0, 0, false) + end + if SHADOW_ENABLED or CLIP_PANEL then + DisableClipping(OLD) + end + end +} + +local TYPES = { + Rect = function(x, y, w, h) + RESET_PARAMS() + MAT = ROUNDED_MAT + X, Y, W, H = x, y, w, h + return RECT + end, + Circle = function(x, y, r) + RESET_PARAMS() + MAT = ROUNDED_MAT + SHAPE = SHAPES[SHAPE_CIRCLE] + X, Y, W, H = x - r / 2, y - r / 2, r, r + r = r / 2 + TL, TR, BL, BR = r, r, r, r + return CIRCLE + end, + Liquid = function(x, y, w, h) + RESET_PARAMS() + MAT = LIQUID_MAT + X, Y, W, H = x, y, w, h + return LRECT + end +} + +setmetatable(RNDX, { __call = function() return TYPES end }) + +RNDX.NO_TL = NO_TL +RNDX.NO_TR = NO_TR +RNDX.NO_BL = NO_BL +RNDX.NO_BR = NO_BR +RNDX.SHAPE_CIRCLE = SHAPE_CIRCLE +RNDX.SHAPE_FIGMA = SHAPE_FIGMA +RNDX.SHAPE_IOS = SHAPE_IOS +RNDX.BLUR = BLUR +RNDX.MANUAL_COLOR = MANUAL_COLOR + +function RNDX.SetFlag(flags, flag, bool) + flag = RNDX[flag] or flag + if tobool(bool) then + return bit.bor(flags, flag) + else + return bit.band(flags, bit.bnot(flag)) + end +end + +function RNDX.SetDefaultShape(shape) + DEFAULT_SHAPE = shape or SHAPE_FIGMA + DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE +end + +_G.gSims_RNDX = RNDX +return RNDX + +-- libNyx and LiquidGlass shader by MaryBlackfild +-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw diff --git a/addons/_libnyx/resource/fonts/Manrope-Bold.ttf b/addons/_libnyx/resource/fonts/Manrope-Bold.ttf new file mode 100644 index 0000000..98c1c3d Binary files /dev/null and b/addons/_libnyx/resource/fonts/Manrope-Bold.ttf differ diff --git a/addons/_libnyx/resource/fonts/Manrope-ExtraBold.ttf b/addons/_libnyx/resource/fonts/Manrope-ExtraBold.ttf new file mode 100644 index 0000000..369d719 Binary files /dev/null and b/addons/_libnyx/resource/fonts/Manrope-ExtraBold.ttf differ diff --git a/addons/_libnyx/resource/fonts/Manrope-ExtraLight.ttf b/addons/_libnyx/resource/fonts/Manrope-ExtraLight.ttf new file mode 100644 index 0000000..8915d96 Binary files /dev/null and b/addons/_libnyx/resource/fonts/Manrope-ExtraLight.ttf differ diff --git a/addons/_libnyx/resource/fonts/Manrope-Light.ttf b/addons/_libnyx/resource/fonts/Manrope-Light.ttf new file mode 100644 index 0000000..4942924 Binary files /dev/null and b/addons/_libnyx/resource/fonts/Manrope-Light.ttf differ diff --git a/addons/_libnyx/resource/fonts/Manrope-Medium.ttf b/addons/_libnyx/resource/fonts/Manrope-Medium.ttf new file mode 100644 index 0000000..5eda9ec Binary files /dev/null and b/addons/_libnyx/resource/fonts/Manrope-Medium.ttf differ diff --git a/addons/_libnyx/resource/fonts/Manrope-Regular.ttf b/addons/_libnyx/resource/fonts/Manrope-Regular.ttf new file mode 100644 index 0000000..1a07233 Binary files /dev/null and b/addons/_libnyx/resource/fonts/Manrope-Regular.ttf differ diff --git a/addons/_libnyx/resource/fonts/Manrope-SemiBold.ttf b/addons/_libnyx/resource/fonts/Manrope-SemiBold.ttf new file mode 100644 index 0000000..b6e9c20 Binary files /dev/null and b/addons/_libnyx/resource/fonts/Manrope-SemiBold.ttf differ diff --git a/addons/_libnyx/sound/nyx_uniqueui/nyxclick_2.mp3 b/addons/_libnyx/sound/nyx_uniqueui/nyxclick_2.mp3 new file mode 100644 index 0000000..d9593a6 Binary files /dev/null and b/addons/_libnyx/sound/nyx_uniqueui/nyxclick_2.mp3 differ diff --git a/addons/_libnyx/sound/nyx_uniqueui/nyxclick_3.mp3 b/addons/_libnyx/sound/nyx_uniqueui/nyxclick_3.mp3 new file mode 100644 index 0000000..19605f3 Binary files /dev/null and b/addons/_libnyx/sound/nyx_uniqueui/nyxclick_3.mp3 differ diff --git a/addons/air_selector/lua/autorun/air_weaponselector_init.lua b/addons/air_selector/lua/autorun/air_weaponselector_init.lua new file mode 100644 index 0000000..821f5f5 --- /dev/null +++ b/addons/air_selector/lua/autorun/air_weaponselector_init.lua @@ -0,0 +1,14 @@ +-- Addon rendu publique part Heing Michel. + +local add = AddCSLuaFile +local load = include + +if SERVER then + add("selector/client.lua") +end + +if CLIENT then + load("selector/client.lua") +end + +-- Addon rendu publique part Heing Michel. \ No newline at end of file diff --git a/addons/air_selector/lua/selector/client.lua b/addons/air_selector/lua/selector/client.lua new file mode 100644 index 0000000..916b47f --- /dev/null +++ b/addons/air_selector/lua/selector/client.lua @@ -0,0 +1,389 @@ + +-- Addon rendu publique part Heing Michel. + +air_selector = air_selector or {} + +air_selector.MAX_COLUMNS = 6 +air_selector.MAX_CELLS = 32 + +air_selector.columns = air_selector.columns or {} + +-- here i fill all columns with empty cells... +function air_selector.clear_columns(initial) + for i = 1, air_selector.MAX_COLUMNS do + air_selector.columns[i] = air_selector.columns[i] or {} + + for slot_id = 1, air_selector.MAX_CELLS do + if initial then + air_selector.columns[i][slot_id] = { + x = 0, y = 0, + w = 0, h = 0, + weapon = nil, + } + else + air_selector.columns[i][slot_id].weapon = nil + end + end + end +end + +air_selector.clear_columns(true) + +local last_update = 0 +function air_selector.update_weapons() + if last_update > CurTime() then + return + end + + last_update = CurTime() + 0.25 + + air_selector.clear_columns() + + local weapons = LocalPlayer():GetWeapons() + local weapons_count = #weapons + + for i = 1, weapons_count do + local weapon = weapons[i] + local slot = weapon:GetSlot() + 1 + --local pos = weapon:GetSlotPos() + 1 + + if slot > air_selector.MAX_COLUMNS then + slot = air_selector.MAX_COLUMNS + end + + --if pos > air_selector.MAX_CELLS then + -- pos = air_selector.MAX_CELLS + --end + + local pos + for cell = 1, air_selector.MAX_CELLS do + if not air_selector.columns[slot][cell].weapon then + pos = cell + break + end + end + + if pos then + air_selector.columns[slot][pos].weapon = weapon + end + end +end + +air_selector.selected_column = 1 +air_selector.selected_cell = 0 + +function air_selector.is_valid_cell(column, cell) + if not column then + column = air_selector.selected_column + end + + if not cell then + cell = air_selector.selected_cell + end + + if not air_selector.columns[column] then + return false + end + + if not air_selector.columns[column][cell] then + return false + end + + if not air_selector.columns[column][cell].weapon then + return false + end + + return true +end +-- Addon rendu publique part Heing Michel. +---@param column number +---@return number filled_cells +function air_selector.get_filled_cells(column) + local filled_cells = 0 + + if not air_selector.columns[column] then + return 1 + end + + for cell = 1, air_selector.MAX_CELLS do + if air_selector.columns[column][cell].weapon then + filled_cells = filled_cells + 1 + end + end + + if filled_cells == 0 then + filled_cells = 1, false + end + + return filled_cells +end + +function air_selector.increment_column() + air_selector.selected_column = air_selector.selected_column + 1 + air_selector.selected_cell = 1 + + if air_selector.selected_column > air_selector.MAX_COLUMNS then + air_selector.selected_column = 1 + elseif not air_selector.is_valid_cell() then + air_selector.increment_column() + return + end + + air_selector.last_change = CurTime() +end + +function air_selector.decrement_column() + air_selector.selected_column = air_selector.selected_column - 1 + air_selector.selected_cell = air_selector.get_filled_cells(air_selector.selected_column) + + if air_selector.selected_column < 1 then + air_selector.selected_column = air_selector.MAX_COLUMNS + elseif not air_selector.is_valid_cell() then + air_selector.decrement_column() + return + end + + + air_selector.last_change = CurTime() +end + +function air_selector.increment_cell(increment_column) + air_selector.selected_cell = air_selector.selected_cell + 1 + + local filled_cells = air_selector.get_filled_cells(air_selector.selected_column) + + if air_selector.selected_cell > filled_cells then + if increment_column == false then + air_selector.selected_cell = 1 + else + air_selector.increment_column() + end + end + + air_selector.last_change = CurTime() +end + +function air_selector.decrement_cell() + air_selector.selected_cell = air_selector.selected_cell - 1 + + if air_selector.selected_cell < 1 then + air_selector.decrement_column() + end + + air_selector.last_change = CurTime() +end + +---@param column number +function air_selector.select_column(column) + if air_selector.selected_column == column then + air_selector.increment_cell(false) + return + end + + air_selector.selected_column = column + air_selector.selected_cell = 1 + + air_selector.last_change = CurTime() +end +-- Addon rendu publique part Heing Michel. +-- selector controller i think... +hook.Add("PlayerBindPress", "air_selector.binds", function(ply, bind, pressed, key) + if not pressed then + return + end + + if IsValid(LocalPlayer():GetVehicle()) then + return + end + + if bind == "invprev" then + air_selector.decrement_cell() + return true + end + + if bind == "invnext" then + air_selector.increment_cell() + return true + end + + if key >= 2 and key <= air_selector.MAX_COLUMNS + 1 then + air_selector.select_column(key - 1) + return true + end + + if air_selector.lerp <= 1 then + return + end + + if bind == "+attack" then + local wep = air_selector.columns[air_selector.selected_column][air_selector.selected_cell].weapon + if wep and LocalPlayer():GetActiveWeapon() ~= wep then + input.SelectWeapon(wep) + LocalPlayer():EmitSound("common/wpn_hudoff.wav", 25, 90) + air_selector.last_change = CurTime() - 5 + air_selector.lerp = 0 + return true + end + end +end) + +air_selector.start_x = 470 +air_selector.start_y = 40 + +air_selector.scale = ScrH() == 2160 and 2 or ScrH() / 1080 +air_selector.s = air_selector.scale + +air_selector.visible = false +air_selector.lerp = 0 +air_selector.last_change = 0 + +surface.CreateFont("air_selector.font", { + font = "Montserrat Medium", + size = air_selector.s * 26, + weight = 700, + extended = true, +}) + +surface.CreateFont("air_selector.weapon_icons", { + font = "HalfLife2", + size = air_selector.s * 100, + weight = 550, +}) + +local long_box = 240 * air_selector.s +local small_box = 100 * air_selector.s + +air_selector.icons = { + ["weapon_smg1"] = "a", + ["weapon_shotgun"] = "b", + ["weapon_crowbar"] = "c", + ["weapon_pistol"] = "d", + ["weapon_357"] = "e", + ["weapon_crossbow"] = "g", + ["weapon_physgun"] = "h", + ["weapon_rpg"] = "i", + ["weapon_bugbait"] = "j", + ["weapon_frag"] = "k", + ["weapon_ar2"] = "l", + ["weapon_physcannon"] = "m", + ["weapon_stunstick"] = "n", + ["weapon_slam"] = "o", +} + +---@param x number +---@param y number +---@param column number +---@param cell boolean +---@return number @y +function air_selector.draw_cell(x, y, column, cell) + local w, h = small_box, small_box + local col = Color(255, 255, 255, 150) + local text_col = color_black + + if air_selector.selected_column == column then + w = long_box + h = 50 * air_selector.s + + if air_selector.selected_cell == cell then + h = 138 * air_selector.s + col = Color(3, 179, 252, 150) + text_col = Color(255, 255, 255, 255) + end + end +-- Addon rendu publique part Heing Michel. + local cell_table = air_selector.columns[column][cell] + cell_table.x = Lerp(FrameTime() * 10, cell_table.x, x) + cell_table.y = Lerp(FrameTime() * 10, cell_table.y, y) + cell_table.w = Lerp(FrameTime() * 10, cell_table.w, w) + cell_table.h = Lerp(FrameTime() * 10, cell_table.h, h) + + local real_y = y + local real_h = h + + local x = cell_table.x + local y = cell_table.y + local w = cell_table.w + local h = cell_table.h + + local wep = air_selector.columns[column][cell].weapon + local name + if wep then + name = wep:GetPrintName() + --print(wep:GetClass()) + end + + col.a = math.Clamp(air_selector.lerp, 0, 150) + text_col.a = air_selector.lerp + + draw.RoundedBox(8, x, y, w, h, col) + + if cell == 1 then + draw.SimpleText(column, "air_selector.font", x + 15 * air_selector.s, y + 10 * air_selector.s, text_col) + end + + if column == air_selector.selected_column and name then + draw.SimpleText(name, "air_selector.font", x + w / 2, y + h - 10 * air_selector.s, text_col, 1, 4) + + if air_selector.selected_cell == cell then + draw.RoundedBox(8, x + w / 2 - 56 * air_selector.s, y, 112 * air_selector.s, 4 * air_selector.s, Color(5, 179, 252, air_selector.lerp)) + + if wep and air_selector.icons[wep:GetClass()] then + local x = x + w / 2 + local y = y + h / 2 + + draw.SimpleText(air_selector.icons[wep:GetClass()], "air_selector.weapon_icons", x, y, text_col, 1, 1) + end + else + draw.RoundedBox(8, x, y + h / 2 - 15 * air_selector.s, 4 * air_selector.s, 30 * air_selector.s,col) + end + end + + return real_y + real_h + 10 * air_selector.s +end + +function air_selector.draw_columns() + local x = air_selector.start_x + + for column = 1, air_selector.MAX_COLUMNS do + local cells = air_selector.selected_column == column and 6 or 1 + local y = air_selector.start_y + y = air_selector.draw_cell(x, y, column, 1) + + if air_selector.selected_column == column then + for cell = 2, air_selector.MAX_CELLS do + if air_selector.columns[column][cell].weapon then + y = air_selector.draw_cell(x, y, column, cell) + end + end + end + + if air_selector.selected_column == column then + x = x + long_box + 10 * air_selector.s + else + x = x + small_box + 10 * air_selector.s + end + end +end +-- Addon rendu publique part Heing Michel. +local last_cell = 1 +local last_column = 1 +hook.Add("DrawOverlay", "air_selector.paint", function() + local to = (air_selector.last_change > CurTime() - 1) and 255 or 0 + air_selector.lerp = Lerp(FrameTime() * 10, air_selector.lerp, to) + + if air_selector.lerp <= 1 then + air_selector.selected_column = 1 + air_selector.selected_cell = 0 + return + end + + if last_cell ~= air_selector.selected_cell or last_column ~= air_selector.selected_column then + LocalPlayer():EmitSound("common/wpn_select.wav", 20, 100) + + last_cell = air_selector.selected_cell + last_column = air_selector.selected_column + end + + air_selector.update_weapons() + air_selector.draw_columns() +end) \ No newline at end of file diff --git a/addons/apg/.travis.yml b/addons/apg/.travis.yml new file mode 100644 index 0000000..7e82587 --- /dev/null +++ b/addons/apg/.travis.yml @@ -0,0 +1,22 @@ +sudo: false + +language: c + +addons: + apt: + packages: + - libc6:i386 + - libstdc++6:i386 + +before_install: + # Download the lua + - wget https://github.com/Metastruct/gtravis/releases/download/travisbins/gluac.tar.xz + - tar -xf gluac.tar.xz + - export LD_LIBRARY_PATH=`pwd`/gluac${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH:-} + + # Set the $PATH so gluac can be executed + - export PATH=$PATH:`pwd`/gluac + + - echo $PWD + +script: find lua/ -iname '*.lua' -print0 | xargs -0 -- gluac -p -- diff --git a/addons/apg/CONTRIBUTING.md b/addons/apg/CONTRIBUTING.md new file mode 100644 index 0000000..fa71080 --- /dev/null +++ b/addons/apg/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Commit Rules + +- [Commit Rules](#commit-rules) + - [Squash](#squash) + - [Here's what you do](#heres-what-you-do) + - [Oh no I messed up](#oh-no-i-messed-up) + - [Be Descriptive](#be-descriptive) + - [Test and Check](#test-and-check) + - [Be Considerate](#be-considerate) + +## Squash + +[Back to Top](#commit-rules) + +If you have lots of commits like "Oops", "Reverted X", "Fixed bug", squash them. + +### Here's what you do + +1. Backup your current repo by copying it into another folder + 1. Be sure to remove the `.git` folder as you don't need that in your backup. +2. Go back to the working folder, aka the one with the `.git` folder. +3. `git log --pretty=oneline --abbrev-commit` + 1. The displayed list will be shown top (newest) to bottom (oldest). + 2. Find the main commit that you made (before the small fixes). + 3. Copy the code under it, it will look something like this... `742c3ac` +4. `git rebase -i ` + 1. Git should guide you through this. + 2. If you need to fix conflicts your files will be edited, and you will get an error. + 3. [Don't panic](https://help.github.com/en/articles/resolving-a-merge-conflict-using-the-command-line) +5. Review your changes! +6. Make sure to **review your changes** sometimes conflict resolution can get undesirable code back into your repo. +7. When you're ready use `git push --force` to push your rebase. + +### Oh no I messed up + +1. Don't panic! You made a backup. +2. Delete everything besides the `.git` folder. +3. Copy and paste everything from your backup. +4. Now just push the change as a new commit. + - Be sure to be very descriptive in your commit message/comment. + +--- + +You may also want to look into [this stackoverflow question](https://stackoverflow.com/q/134882) and [the answer](https://stackoverflow.com/a/135614) provided. + +## Be Descriptive + +[Back to Top](#commit-rules) + +You don't have to write a paragraph about all your changes, but describe what you're trying to do in a clear way. + +## Test and Check + +[Back to Top](#commit-rules) + +Always test and check your code, and don't just test once. Test multiple times in multiple environements. If your code isn't tested don't commit. + +## Be Considerate + +[Back to Top](#commit-rules) + +This addon will be runnning with other addons, please make sure it plays nice. Specify any detours, try not to [error](https://wiki.garrysmod.com/page/Global/error), and be sure to make all your codes behaviour expected. diff --git a/addons/apg/README.md b/addons/apg/README.md new file mode 100644 index 0000000..92963ac --- /dev/null +++ b/addons/apg/README.md @@ -0,0 +1,68 @@ +Due to seemingly consistent issues with the code base of APG I've made PUG. +However I doubt PUG will get any updates because I simply no longer play Garry's mod, +but if you'd like to check my alternative to APG, check out [__PUG__ on Gitlab](https://gitlab.com/NanoAi/gm_pug) or [__PUG__ Github Mirror](https://github.com/NanoAi/gm_pug) + +------------------ + +This will no longer be updated as I can no longer support it, if you are looking for a good alternative please check out PUG linked above. It's also free, and can do almost everything APG can do. + +Also please for the love of god, don't pay for stuff like this. + +------------------ + +# APG - Anti Prop Griefing & Crash Protection + +![- APG - Light Weight, Easy to Use, Stops Crashes](https://i.imgur.com/DrbZOgk.png "APG - Light Weight, Easy to Use, Stops Crashes") + +We are dedicated and focused on providing servers with the best answer to prop +griefers, killers, and exploiters. We saw all the currently available addons and +felt that neither one of them alone could really do everything that we wanted it +to do, so naturally we made our own and decided to share it with you. + +## This does still require a prop protection addon + +### Compatible prop protection addons (with CPPI) + +* [Falco's Prop Protection (FPP)](https://github.com/FPtje/Falcos-Prop-protection/) +* [PatchProtect](https://github.com/Patcher56/PatchProtect) +* [Simple Prop Protection (SPP)](https://github.com/Donkie/SimplePropProtection) + +### ⚠ Don't use more then one prop protection/anti prop griefing addon at a time! + +### ( You have been warned! ) + +## Features & Notes + +* ✅ Easy install and configuration ( Just say !apg ) +* ✅ Customizable blacklist of entities to protect ( props, wire, etc ) +* ✅ Props ghosting/unghosting on physgun +* ✅ Disables prop damage to players +* ✅ Controls prop pushing against players +* ✅ Controls prop pushing vehicles +* ✅ Controls prop surfing +* ✅ Blocks many types of exploits +* ✅ Blocks stacker exploit +* ✅ Blocks fading door exploit +* ✅ Blocks Advanced Duplicator exploit +* ✅ Blocks tool gun spamming +* ✅ Allows to toggle the use of the toolgun on the world +* ✅ Ability to check entities around the prop for stack's +* ✅ Ability to block vehicles damages against players +* ✅ Ability to make vehicles not collide with players +* ✅ Allows to block physgun reload +* ✅ Allows to block moving contraptions (props that are welded together) +* ✅ Supports anti-trapping for fading doors. +* ✅ Send a message to admins when a large stack of props is detected +* ⚠ Detours [PhysgunPickup](https://wiki.garrysmod.com/page/GM/PhysgunPickup) for better confirmations +* ⚠ Detours [SetColor](https://wiki.garrysmod.com/page/Entity/SetColor) to prevent stuff from turning purple/pink unexpectedly +* ⚠ Detours [SetCollisionGroup](https://wiki.garrysmod.com/page/Entity/SetCollisionGroup) to prevent overrides + +Lag triggers are based on fancy algorithms and timers, if you are getting false positives try messing around with the values. + +If you find any issue, exploit, possible improvement, suggestions, feel free to make an issue! + +Credits: + +* This project is currently updated and maintained by [NanoAi](http://steamcommunity.com/profiles/76561198096713277) +* This addon was originally created by [WhileTrue](http://steamcommunity.com/profiles/76561197972967270) +* Special thanks to [AvarianKnight](http://steamcommunity.com/profiles/76561198174460202) for reviving this project! diff --git a/addons/apg/addon.json b/addons/apg/addon.json new file mode 100644 index 0000000..a3bf2af --- /dev/null +++ b/addons/apg/addon.json @@ -0,0 +1,17 @@ +{ + "title": "APG - Anti Prop Griefing & Crash Detection", + "type": "ServerContent", + "tags": [ + "roleplay", + "build" + ], + "ignore": [ + ".git", + ".travis.yml", + "*.psd", + "*.vcproj", + "*.svn*", + "*.git*", + "*.md" + ] +} \ No newline at end of file diff --git a/addons/apg/lua/apg/cl_menu.lua b/addons/apg/lua/apg/cl_menu.lua new file mode 100644 index 0000000..23442c3 --- /dev/null +++ b/addons/apg/lua/apg/cl_menu.lua @@ -0,0 +1,629 @@ +APG_panels = APG_panels or {} + +local pull = include("cl_utils.lua") +local utils = pull.utils or {} +local menu = pull.menu or {} + +local function showNotice(notifyLevel, notifyMessage) + if string.Trim(notifyMessage) == "" then return end + icon = notifyLevel == 0 and NOTIFY_GENERIC or notifyLevel == 1 and NOTIFY_CLEANUP or notifyLevel == 2 and NOTIFY_ERROR + + notification.AddLegacy(notifyMessage, icon, 3 + (notifyLevel * 3)) + + if APG.cfg[ "notifySounds" ].value then + surface.PlaySound(notifyLevel == 1 and "buttons/button10.wav" or notifyLevel == 2 and "ambient/alarms/klaxon1.wav" or "buttons/lightswitch2.wav") -- Maybe let the player choose the sound? + end + + MsgC( notifyLevel == 0 and Color( 0, 255, 0 ) or Color( 255, 191, 0 ), "[APG] ", Color( 255, 255, 255 ), notifyMessage,"\n") +end + +net.Receive( "apg_notice_s2c", function() + local notifyLevel = net.ReadUInt( 3 ) + local notifyMessage = net.ReadString() + showNotice(notifyLevel, notifyMessage) +end) + +local function APGBuildHomePanel() + local panel = APG_panels[ "home" ] + panel.Paint = function( i, w, h ) end + + local github = "https://github.com/NanoAi/gm_apg" + + menu:initPanel( panel, 0, 40, 0, 35 ) + menu:switch( 568, 20, "Welcome to APG! ( https://git.io/fjCQK )") + menu:switch( 568, 20, "Remember to check the github for updates! (Click to Copy)", function() + SetClipboardText( github ) + showNotice(0, "Github URL copied to clipboard!") + end) + menu:switch( 568, 20, "<-- Select a Module to Configure!") + menu:switch( 568, 20, "To see this menu again just say \"!apg\"") + menu:switch( 568, 20, "For more help the wiki is available on the Github! (Click to Copy)", function() + SetClipboardText( github .. "/wiki" ) + showNotice(0, "Github URL copied to clipboard!") + end) + menu:switch( 568, 20, "Sorry for the bad home page, this is hopefully a placeholder. : )") + menu:panelDone() +end + +local function APGBuildStackPanel() + local panel = APG_panels[ "stack_detection" ] + panel.Paint = function( i, w, h ) end + + menu:initPanel( panel, 0, 40, 0, 35 ) + menu:numSlider( 568, 20, "Maximum stacked ents", "stackMax", 3, 50, 0 ) + menu:numSlider( 568, 20, "Stack distance (gmod units)", "stackArea", 5, 50, 0 ) + menu:numSlider( 568, 20, "Maximum stacked fading doors", "fadingDoorStackMax", 5, 50, 0 ) + menu:switch( 568, 20, "Notify player when their fading door is removed.", "fadingDoorStackNotify" ) + menu:panelDone() +end + +local function APGBuildToolsPanel() + local panel = APG_panels[ "tools" ] + panel.Paint = function( i, w, h ) end + + menu:initPanel( panel, 0, 40, 0, 35 ) + menu:switch( 568, 20, "Should tools be blocked on APG_CantPickup", "checkCanTool" ) + menu:switch( 568, 20, "Block players from spamming the toolgun", "blockToolSpam" ) + menu:numSlider( 568, 20, "Max click's per second(s)", "blockToolRate", 1, 15, 0 ) -- It's really hard to click more then 15 times a second. + menu:numSlider( 568, 20, "The aforementioned second(s)", "blockToolDelay", 1, 5, 0 ) + menu:switch( 568, 20, "Prevent using the toolgun on the world", "blockToolWorld" ) + menu:switch( 568, 20, "Prevent the toolgun from unfreezing props", "blockToolUnfreeze" ) + menu:switch( 568, 20, "Block the Creator Tool? (Requires OSS)", "blockCreatorTool" ) + menu:switch( 568, 20, "Review entities near tool use", "checkTooledEnts" ) + menu:panelDone() +end + +local function APGBuildMiscPanel() + local panel = APG_panels[ "misc" ] + panel.Paint = function( i, w, h ) end + + menu:initPanel( panel, 0, 40, 0, 35 ) + menu:switch( 568, 20, "Override Server Settings? (OSS)", "touchServerSettings" ) + menu:switch( 568, 20, "Auto freeze over time", "autoFreeze" ) + menu:numSlider( 568, 20, "Auto freeze delay(seconds)", "autoFreezeTime", 5, 600, 0 ) + menu:switch( 568, 20, "Disable vehicle damages", "vehDamage" ) + menu:switch( 568, 20, "Disable vehicle collisions (with players)", "vehNoCollide" ) + menu:numSlider(575, 20, "Physgun maxrange (how far they can reach in gmod units)", "physGunMaxRange", 128, 8192, 0) + menu:switch( 568, 20, "Block GravGun throwing", "blockGravGunThrow" ) + menu:switch( 568, 20, "Block Physgun Reload", "blockPhysgunReload" ) + menu:switch( 568, 20, "Block players from moving contraptions", "blockContraptionMove" ) + menu:switch( 568, 20, "Inject custom hooks into Fading Doors", "fadingDoorHook" ) + menu:switch( 568, 20, "Activate FRZR9K (Sleepy Physics)", "sleepyPhys" ) + menu:switch( 568, 20, "Hook FRZR9K into collision (Experimental)", "sleepyPhysHook" ) + menu:switch( 568, 20, "Allow prop killing", "allowPK" ) + menu:switch( 568, 20, "Activate Turbo Physics (Requires OSS)", "setTurboPhysics" ) + menu:panelDone() +end + +local function APGBuildLagPanel() + local panel = APG_panels[ "lag_detection" ] + panel.Paint = function( i, w, h ) end + + menu:initPanel( panel, 0, 40, 0, 35 ) + menu:numSlider( 568, 20, "Lag threshold (%)", "lagTrigger", 5, 200, 0 ) + menu:numSlider( 568, 20, "Frames lost", "lagsCount", 1, 20, 0 ) + menu:numSlider( 568, 20, "Heavy lag trigger (seconds)", "bigLag", 1, 5, 1 ) + menu:comboBox( 568, 20, "Lag fix function", "lagFunc", APG_lagFuncs ) + menu:numSlider( 568, 20, "Lag func. delay (seconds)", "lagFuncTime", 1, 300, 0 ) + menu:panelDone() +end + +local function APGBuildNotificationPanel() + local panel = APG_panels[ "notification" ] + panel.Paint = function( i, w, h ) end + + menu:initPanel( panel, 0, 40, 0, 35 ) + menu:switch( 568, 20, "Notification Sounds", "notifySounds" ) + menu:comboBox( 568, 20, "Notification Level", "notifyLevel", APG_notifyLevels ) + menu:switch( 570, 20, "Do you want to show what lag function ran?", "notifyLagFunc" ) + menu:switch( 568, 20, "Developer logs (shows a notification, is spammy)", "developerDebug" ) + menu:panelDone() +end + +local function APGBuildLogsPanel() + local panel = APG_panels[ "logs" ] + panel.Paint = function( i, w, h ) end + + menu:initPanel( panel, 0, 40, 0, 35 ) + menu:switch( 568, 20, "Should we log when there is lag detected?", "logLagDetected" ) + menu:switch( 568, 20, "Should we log when a player attempts to crash the server", "logStackCrashAttempt" ) + menu:panelDone() +end + +local function APGBuildGhostPanel() + local panel = APG_panels[ "ghosting" ] + + panel.Paint = function( i, w, h) + draw.RoundedBox( 0, 0, 37, 170, 135, Color( 38, 38, 38, 255 ) ) + draw.DrawText( "Ghosting color:", "APG_element_font", 5, 37, Color( 189, 189, 189 ), 3 ) + --draw.RoundedBox(cornerRadius, x, y, width, height, color) + draw.RoundedBox( 0, 175, 37, 500, 300, Color( 38, 38, 38, 255) ) + draw.DrawText( "Bad entities:", "APG_element_font", 180, 37, Color( 189, 189, 189), 3 ) + draw.DrawText( "(Right-Click to Toggle)", "APG_title2_font", 280, 38, Color( 189, 189, 189), 3 ) + --draw.DrawText(text, font="DermaDefault", x=0, y=0, color=Color(255,255,255,255), xAlign=TEXT_ALIGN_LEFT) + draw.DrawText( "Good entities:", "APG_element_font", 180, 230, Color( 189, 189, 189), 3 ) + draw.DrawText( "(Right-Click to Toggle)", "APG_title2_font", 285, 232, Color( 189, 189, 189), 3 ) + end + + menu:initPanel( panel, 0, 180, 0, 35 ) + menu:switch( 170, 20, "Always frozen", "alwaysFrozen" ) + menu:switch( 170, 20, "Fading Doors", "fadingDoorGhosting" ) + menu:switch( 170, 20, "Ignore Vehicles", "vehAntiGhost" ) + menu:switch( 170, 20, "Enable Color", "ghostColorToggle") + local offsets = menu:panelDone() + + local Mixer = vgui.Create( "CtrlColor", panel ) + Mixer:SetPos( 5, 55 ) + Mixer:SetSize( 160, 110 ) + Mixer.Mixer.ValueChanged = function( self, color ) + APG.cfg[ "ghostColor" ].value = Color( color.r, color.g, color.b, color.a) + end + + local badList = vgui.Create( "DListView", panel ) + badList:Clear() + badList:SetPos( 180, 55 ) + badList:SetSize( panel:GetWide() - 185, panel:GetTall() / 2.5 ) + badList:SetMultiSelect( false ) + badList:SetHideHeaders( false ) + badList:AddColumn( "Class" ) + badList:AddColumn( "Exact" ) + + function badList:OnRowRightClick( id, line ) + local key = line:GetColumnText(1) + local value = not tobool(line:GetColumnText(2)) + line:SetColumnText( 2, value ) + APG.cfg[ "badEnts" ].value[key] = value + end + + local goodList = vgui.Create( "DListView", panel ) + goodList:Clear() + goodList:SetPos( 180, 250 ) + goodList:SetSize( panel:GetWide() - 185, panel:GetTall() / 2.5 ) + goodList:SetMultiSelect( false ) + goodList:SetHideHeaders( false ) + goodList:AddColumn( "Class" ) + goodList:AddColumn( "Exact" ) + + function goodList:OnRowRightClick( id, line ) + local key = line:GetColumnText(1) + local value = not tobool(line:GetColumnText(2)) + line:SetColumnText( 2, value ) + APG.cfg[ "unGhostingWhitelist" ].value[key] = value + end + + + local function updateTab() + + badList:Clear() + for class,complete in pairs(APG.cfg[ "badEnts" ].value) do + badList:AddLine(class, complete) + end + + goodList:Clear() + for class,complete in pairs(APG.cfg[ "unGhostingWhitelist" ].value) do + goodList:AddLine(class, complete) + end + + end + updateTab() + + badList.Paint = function(i,w,h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 150, 150, 150, 255 ) ) + end + + badList.VBar.Paint = function(i,w,h) + surface.SetDrawColor( 88, 110, 110, 240 ) + surface.DrawRect( 0, 0, w, h ) + end + + badList.VBar.btnGrip.Paint = function(i,w,h) + surface.SetDrawColor( 255, 83, 13, 50 ) + surface.DrawRect( 0, 0, w, h ) + draw.RoundedBox( 0, 1, 1, w - 2, h / 2, Color( 72, 89, 89, 255 ) ) + end + + badList.VBar.btnUp.Paint = function(i,w,h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) ) + end + + badList.VBar.btnDown.Paint = function(i,w,h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) ) + end + + goodList.Paint = function(i,w,h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 150, 150, 150, 255 ) ) + end + + goodList.VBar.Paint = function(i,w,h) + surface.SetDrawColor( 88, 110, 110, 240 ) + surface.DrawRect( 0, 0, w, h ) + end + + goodList.VBar.btnGrip.Paint = function(i,w,h) + surface.SetDrawColor( 255, 83, 13, 50 ) + surface.DrawRect( 0, 0, w, h ) + draw.RoundedBox( 0, 1, 1, w - 2, h / 2, Color( 72, 89, 89, 255 ) ) + end + + goodList.VBar.btnUp.Paint = function(i,w,h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) ) + end + + goodList.VBar.btnDown.Paint = function(i,w,h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) ) + end + + local BadTextEntry = vgui.Create( "DTextEntry", panel ) + BadTextEntry:SetPos( offsets.x, panel:GetTall() - 100 ) + BadTextEntry:SetSize( 100, 20 ) + BadTextEntry:SetText( "Bad Entity class" ) + BadTextEntry.OnEnter = function( self ) + chat.AddText( self:GetValue() ) + end + + local BadAdd = vgui.Create( "DButton" , panel) + BadAdd:SetPos( offsets.x + 100, panel:GetTall() - 100 ) + BadAdd:SetSize( 75,20 ) + BadAdd:SetText( "Add" ) + BadAdd.DoClick = function() + if BadTextEntry:GetValue() == "Bad Entity class" then return end + utils.addBadEntity( BadTextEntry:GetValue() ) + updateTab() + end + + BadAdd:SetTextColor( Color(255, 255, 255) ) + BadAdd.Paint = function( i, w, h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 44, 55, 55, 255 ) ) + draw.RoundedBox( 0, 1, 1, w-2, h-2, Color( 58, 58, 58, 255 ) ) + end + + local BadRemove = vgui.Create( "DButton" , panel) + BadRemove:SetPos( offsets.x, panel:GetTall() - 80 ) + BadRemove:SetSize( 175, 20 ) + BadRemove:SetText( "Remove selected" ) + BadRemove.DoClick = function() + for k, v in pairs(badList:GetSelected()) do + local key = v:GetValue(1) + APG.cfg[ "badEnts" ].value[key] = nil + updateTab() + end + end + + BadRemove:SetTextColor( Color( 255, 255, 255 ) ) + BadRemove.Paint = function( i, w, h ) + draw.RoundedBox( 0, 0, 0, w, h, Color( 58, 58, 58, 255 ) ) + draw.RoundedBox( 0, 0, 0, w, 1, Color( 30, 30, 30, 125 ) ) + end + + local GoodTextEntry = vgui.Create( "DTextEntry", panel ) + GoodTextEntry:SetPos( offsets.x, panel:GetTall() - 45 ) + GoodTextEntry:SetSize( 100, 20 ) + GoodTextEntry:SetText( "Good Entity class" ) + GoodTextEntry.OnEnter = function( self ) + chat.AddText( self:GetValue() ) + end + + local GoodAdd = vgui.Create( "DButton" , panel) + GoodAdd:SetPos( offsets.x + 100, panel:GetTall() - 45 ) + GoodAdd:SetSize( 75,20 ) + GoodAdd:SetText( "Add" ) + GoodAdd.DoClick = function() + if GoodTextEntry:GetValue() == "Good Entity class" then return end + utils.addGoodEntity( GoodTextEntry:GetValue() ) + updateTab() + end + + local GoodRemove = vgui.Create( "DButton" , panel) + GoodRemove:SetPos( offsets.x, panel:GetTall() - 25 ) + GoodRemove:SetSize( 175, 20 ) + GoodRemove:SetText( "Remove selected" ) + GoodRemove.DoClick = function() + for k, v in pairs(goodList:GetSelected()) do + local key = v:GetValue(1) + APG.cfg[ "unGhostingWhitelist" ].value[key] = nil + updateTab() + end + end + + GoodRemove:SetTextColor( Color( 255, 255, 255 ) ) + GoodRemove.Paint = function( i, w, h ) + draw.RoundedBox( 0, 0, 0, w, h, Color( 58, 58, 58, 255 ) ) + draw.RoundedBox( 0, 0, 0, w, 1, Color( 30, 30, 30, 125 ) ) + end +end + +local main_color = Color( 32, 255, 0, 255 ) +local main_color_red = Color( 96, 0, 0, 255 ) +local main_color_darker = Color( 51, 91, 51, 255 ) + +local function setScrollerTheme( scroller ) + scroller:SetSize(1, 0) + scroller:SetHideButtons(true) + + function scroller:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, 1, h, main_color_darker ) + end + + function scroller.btnGrip:Paint( w, h ) + draw.RoundedBox( 0, 0, 0, 1, h, main_color ) + end +end + +local function openMenu( len ) + len = net.ReadUInt( 32 ) + if len == 0 then return end + local settings = net.ReadData( len ) + settings = util.Decompress( settings ) + settings = util.JSONToTable( settings ) + + APG.cfg = settings.cfg + table.Merge(APG, settings) + + local APG_Main = vgui.Create( "DFrame" ) + APG_Main:SetSize( 800, 450 ) + APG_Main:SetPos( 800 - APG_Main:GetWide() / 2, 450 - APG_Main:GetTall() / 2) + APG_Main:SetTitle( "" ) + APG_Main:SetVisible( true ) + APG_Main:SetDraggable( true ) + APG_Main:MakePopup() + APG_Main:ShowCloseButton( false ) + APG_Main.Paint = function(i,w,h) + draw.RoundedBox(4,0,0,w,h,Color(34, 34, 34, 255)) + draw.RoundedBox(0,0,23,w,1,main_color) + + local name = "A.P.G. - Anti Prop Griefing Solution" + draw.DrawText( name, "APG_title_font",8, 5, Color( 204, 204, 204, 255 ), 3 ) + end + + local closeButton = vgui.Create( "DButton",APG_Main ) + closeButton:SetPos( APG_Main:GetWide() - 20, 4 ) + closeButton:SetSize( 18, 18 ) + closeButton:SetText(" ") + closeButton.DoClick = function() + APG_Main:Hide() + APG_Main:Remove() + end + + closeButton.Paint = function(i,w,h) + draw.RoundedBox( 0,0,0,w,h, Color( 91, 51, 51, 255 ) ) + draw.DrawText( "✕", "APG_sideBar_font", 1, -1, Color( 204, 204, 204, 255 ), TEXT_ALIGN_TOP ) + end + + local saveButton = vgui.Create( "DButton", APG_Main ) + saveButton:SetPos( APG_Main:GetWide() - 117, 4 ) + saveButton:SetSize( 77, 18 ) + saveButton:SetText(" ") + + saveButton.DoClick = function() + if not LocalPlayer():IsSuperAdmin() then return end + local settings = APG + settings = util.TableToJSON( settings ) + settings = util.Compress( settings ) + net.Start( "apg_settings_c2s") + net.WriteUInt( settings:len(), 32 ) -- Write the length of the data (up to {{ user_id | 76561197972967270 }}) + net.WriteData( settings, settings:len() ) -- Write the data + net.SendToServer() + showNotice(1, "APG Settings saved!") + end + + saveButton.Paint = function(i,w,h) + draw.RoundedBox( 0, 0, 0, w, h, Color( 51, 91, 51, 255 ) ) + draw.DrawText( "Save Settings", "APG_title2_font",w/2, 1, Color( 204, 204, 204, 255 ), 1 ) + end + + -- Side bar + local sidebar = vgui.Create( "DScrollPanel", APG_Main ) + setScrollerTheme( sidebar:GetVBar() ) + + sidebar:SetSize( APG_Main:GetWide() / 4 , APG_Main:GetTall() - 35) + sidebar:SetPos( 0, 30 ) + sidebar.Paint = function( i, w, h ) + draw.RoundedBox( 0, 0, 0, w, h, Color( 33, 33, 33, 255 ) ) + draw.RoundedBox( 0, w-1, 0, 1, h, main_color_darker) + end + + local x, y = (APG_Main:GetWide() - sidebar:GetWide()) - 19, APG_Main:GetTall() - 35 + local px, py = sidebar:GetWide() + 15, 30 + local first = true + + local modules = APG.modules + + -- Attempt to force essential modules to be enabled. + modules["home"] = true + modules["canphysgun"] = true + + for k, v in next, APG.modules do + if k == "canphysgun" then continue end -- This module doesn't have UI, so it doesn't need a UI button. + + local panel = vgui.Create( "DScrollPanel", APG_Main ) + setScrollerTheme( panel:GetVBar() ) + + panel:SetSize( x, y ) + panel:SetPos( px, py ) + panel:SetVisible( first ) + + panel.Paint = function() end + APG_panels[k] = panel + first = false + + local button = vgui.Create( "DButton", panel ) + button:SetPos( 0, 0 ) + button:SetSize( panel:GetWide(), 35 ) + button:SetText("") + + button.UpdateColours = function( label, skin ) + label:SetTextStyleColor( Color( 189, 189, 189 ) ) + end + + button.Paint = function( slf, w, h ) + local enabled = APG.modules[k] + draw.RoundedBox( 0, 0, h * 0.85, w-5, 1, enabled and main_color or main_color_red ) + + local text = utils.getNiceName(k) .. " module " + draw.DrawText( text, "APG_mainPanel_font", 5, 8, Color( 189, 189, 189 ), 3 ) + menu:mainSwitch( w * 0.90, (h * 0.5) - 16, enabled ) + end + + button.DoClick = function() + APG.modules[k] = not APG.modules[k] + end + end + + local i = 0 + local height = ( sidebar:GetTall()/5 ) + + for k, v in next, APG.modules do + if k == "canphysgun" then continue end + + local button = sidebar:Add( "DButton" ) + button:SetPos( 5, (height + 5) * i) + button:SetSize( sidebar:GetWide() - 10 , height ) + button:SetText("") + + button.DoClick = function() + for l,m in next, APG_panels do + if k ~= l then + APG_panels[l]:SetVisible( false ) + else + APG_panels[l]:SetVisible( true ) + end + end + end + + local size = sidebar:GetWide() + button.Paint = function( _, w, h ) + local name = utils.getNiceName( k ) + if button.Hovered then + draw.RoundedBox( 5, 0, 0, w, h, Color( 48, 48, 48, 255 ) ) + draw.RoundedBox( 0, 2, 2, w - 4, h - 4, Color( 36, 36, 36, 255 ) ) + end + if APG_panels[k]:IsVisible() then + draw.RoundedBox( 0, 0, 0, w, h, Color( 51, 51, 51, 255 ) ) + draw.RoundedBox( 0, w * 0.10, h * 0.60, w * 0.8, 2, main_color_darker ) + end + + draw.DrawText( name, "APG_sideBar_font", ( size - name:len() ) / 2, h * 0.35, Color( 189, 189, 189 ), 1) + end + + if k == "home" then + button:DoClick() + end + + i = i + 1 + end + + -- Build all the expected panels. + + APGBuildHomePanel() + APGBuildMiscPanel() + APGBuildToolsPanel() + APGBuildGhostPanel() + APGBuildLagPanel() + APGBuildStackPanel() + APGBuildLogsPanel() + APGBuildNotificationPanel() +end + +net.Receive( "apg_menu_s2c", openMenu ) + +properties.Add( "apgoptions", { + MenuLabel = "APG Options", -- Name to display on the context menu + Order = 9999, -- The order to display this property relative to other properties + MenuIcon = "icon16/fire.png", -- The icon to display next to the property + + Filter = function( self, ent, ply ) -- A function that determines whether an entity is valid for this property + if not ply:IsSuperAdmin() then return false end + if not IsValid(ent) then return false end + if not ent:GetClass() then return false end + if ent:EntIndex() < 0 then return false end + + return true + end, + MenuOpen = function( self, option, ent, tr ) + local submenu = option:AddSubMenu() + local function addoption( str, data ) + local menu = submenu:AddOption( str, data.callback ) + + if data.icon then + menu:SetImage( data.icon ) + end + + return menu + end + + addoption( "Sleep entities of this Class", { + icon = "icon16/clock.png", + callback = function() self:APGcmd( ent, "sleepclass" ) end, + }) + + addoption( "Freeze entities of this Class", { + icon = "icon16/bell_delete.png", + callback = function() self:APGcmd( ent, "freezeclass" ) end, + }) + + submenu:AddSpacer() + + addoption( "Cleanup Owner - Unfrozens", { + icon = "icon16/cog_delete.png", + callback = function() self:APGcmd( ent, "clearunfrozen" ) end, + }) + + addoption( "Cleanup Owner", { + icon = "icon16/bin_closed.png", + callback = function() self:APGcmd( ent, "clearowner" ) end, + }) + + submenu:AddSpacer() + + addoption( "Get Owner SteamID", { + icon = "icon16/user.png", + callback = function() self:APGcmd( ent, "getownerid" ) end, + }) + + addoption( "Get Owner Entity Count", { + icon = "icon16/brick.png", + callback = function() self:APGcmd( ent, "getownercount" ) end, + }) + + submenu:AddSpacer() + + addoption( "Add this entity class to the Ghosting List", { + icon = "icon16/cross.png", + callback = function() self:APGcmd( ent, "addghost" ) end, + }) + + addoption( "Remove this entity class from the Ghosting List", { + icon = "icon16/tick.png", + callback = function() self:APGcmd( ent, "remghost" ) end, + }) + + submenu:AddSpacer() + + addoption( "Ghost this entity", { + icon = "icon16/tick.png", + callback = function() self:APGcmd( ent, "ghost" ) end, + }) + + end, + Action = function( self, ent ) end, + APGcmd = function( self, ent, cmd ) + if cmd == "getownerid" then + local owner, _ = ent:CPPIGetOwner() + if IsValid( owner ) then + local id = tostring( owner:SteamID() ) + local name = tostring( owner:Nick() ) + SetClipboardText( id ) + showNotice(0, name .. " [ " .. id .. " ]" .. " has been copied to your clipboard.") + else + showNotice(0, "\nOops, that's not a Player!") + end + elseif cmd == "getentname" then + showNotice(0, ent:GetClass()) + elseif IsValid( ent ) and ent.EntIndex() then + net.Start( "apg_context_c2s" ) + net.WriteString( cmd ) + net.WriteEntity( ent ) + net.SendToServer() + end + end, +}) \ No newline at end of file diff --git a/addons/apg/lua/apg/cl_utils.lua b/addons/apg/lua/apg/cl_utils.lua new file mode 100644 index 0000000..ff40dcd --- /dev/null +++ b/addons/apg/lua/apg/cl_utils.lua @@ -0,0 +1,281 @@ +surface.CreateFont( "APG_title_font", { + font = "Arial", + size = 14, + weight = 700, +} ) + +surface.CreateFont( "APG_title2_font", { + font = "Arial", + size = 13, + weight = 700, +} ) + +surface.CreateFont( "APG_sideBar_font", { + font = "Arial", + size = 18, + weight = 1500, +} ) + +surface.CreateFont( "APG_mainPanel_font", { + font = "Arial", + size = 19, + weight = 8500, +} ) + +surface.CreateFont( "APG_tick_font", { + font = "Arial", + size = 29, + weight = 1900, +} ) + +surface.CreateFont( "APG_element_font", { + font = "Arial", + size = 17, + weight = 1300, +} ) + +surface.CreateFont( "APG_element2_font", { + font = "Arial", + size = 17, + weight = 2900, +} ) + +local utils = {} +local menu = {} + +function utils.addBadEntity( class ) + local found = false + for k, v in pairs ( ents.GetAll() ) do + if class == v:GetClass() then + found = true + break + end + end + if not found then + for k in pairs (scripted_ents.GetList()) do + if class == k then + found = true + break + end + end + end + APG.cfg["badEnts"].value[ class ] = found +end + +function utils.addGoodEntity( class ) + local found = false + for k, v in pairs ( ents.GetAll() ) do + if class == v:GetClass() then + found = true + break + end + end + if not found then + for k in pairs (scripted_ents.GetList()) do + if class == k then + found = true + break + end + end + end + APG.cfg["unGhostingWhitelist"].value[ class ] = found +end + +function utils.addInvalidWhitelist( model ) + local found = false + for k, v in pairs ( ents.GetAll() ) do + if model == v:GetModel() then + found = true + break + end + end + APG.cfg["invalidPhysicsWhitelist"].value[ model ] = found +end + +function utils.getNiceName( str ) + local nName = string.gsub(str, "^%l", string.upper) + nName = string.gsub(nName, "_", " " ) + return nName +end + +function menu:mainSwitch( x, y, on ) + draw.RoundedBox(10, x, y, 45, 18, Color( 58, 58, 58, 255)) + if on then + draw.RoundedBox(10, x + 1, y + 1, 45 - 2, 18 - 2, Color( 11, 70, 30, 255)) + draw.DrawText( "ON", "APG_title_font", x + 8, y + 2, Color( 189, 189, 189 ), 3 ) + draw.RoundedBox(10, x + 27, y, 18, 18, Color( 88, 88, 88, 255)) + else + --draw.RoundedBox(10, x, y, 45, 18, Color( 110, 28, 38, 255)) + draw.RoundedBox(10, x + 1, y + 1, 43, 16, Color( 34, 34, 34, 255)) + draw.DrawText( "OFF", "APG_title_font", x + 21, y + 2, Color( 189, 189, 189), 3 ) + draw.RoundedBox(10, x, y, 18, 18, Color( 88, 88, 88, 255)) + end + --draw.RoundedBox(0, x+20, y, 1, 18, Color( 88, 88, 88, 255)) +end + + +function menu:initPanel( panel, x, y, ix, iy ) + self.panel = panel + self.vars = {x = x, y = y, ix = ix, iy = iy} +end + +function menu:panelDone() + local old = self.vars + + self.panel = {} + self.vars = {} + + return old +end + +function menu:grabVars() + local v = self.vars + return self.panel, v.x, v.y, v.ix, v.iy +end + +function menu:switch( w, h, text, var ) + local panel, x, y, ix, iy = menu:grabVars() + local button = vgui.Create("DButton", panel) + + local isKey = ( type(var) == "string" ) + local isFunction = ( type(var) == "function" ) + + button:SetPos(x, y) + button:SetSize(w, h) + button:SetText("") + + button.Paint = function(slf, w, h) + local enabled = isKey and APG.cfg[ var ].value or isFunction + draw.RoundedBox(0, 0, h * 0.95, w - 5, 1, Color(250, 250, 250, 1)) + draw.DrawText( text, "APG_element2_font", 0, 0, Color( 189, 189, 189), 3 ) + menu:mainSwitch( w-45, 0, enabled ) + end + + if isKey then + button.DoClick = function() + APG.cfg[ var ].value = not APG.cfg[ var ].value + end + else + if isFunction then + button.DoClick = var + else + button:SetEnabled( false ) + end + end + + self.vars.x = x + ix + self.vars.y = y + iy +end + +function menu:numSlider( w, h, text, var, minSlider, maxSlider, decimal ) + local panel, x, y, ix, iy = menu:grabVars() + + local slider = panel:Add( "DNumSlider" ) + + slider:SetPos( x, y ) + slider:SetSize( w, h ) + slider:SetText( "" ) + slider:SetMin( minSlider ) + slider:SetMax( maxSlider ) + slider:SetDecimals( decimal ) + slider:SetValue( APG.cfg[ var ].value ) + slider.OnValueChanged = function( self, newValue ) + APG.cfg[ var ].value = newValue + end + + slider.Paint = function(slf, w, h) + draw.RoundedBox( 0, 0, h * 0.97, w - 5, 1, Color(250, 250, 250, 1 ) ) + draw.DrawText( text, "APG_element2_font", 0, 0, Color( 189, 189, 189), 3 ) + end + + slider.Slider.Paint = function( slf, w, h) + --draw.RoundedBox(cornerRadius, x, y, width, height, color) + draw.RoundedBox( 0, 8, 9 - 1, w - 16, 1 + 2, Color( 250, 250, 250, 1)) + end + + slider.Slider.Knob.Paint = function(slf, w, h) + draw.RoundedBox(6, 0, 4, 10, 10, Color( 11, 70, 30, 255)) + end + + slider.Slider:Dock( NODOCK ) + slider.Slider:SetPos( panel:GetWide() - 110, 0 ) + slider.Slider:SetWide( 100 ) + + slider.TextArea:Dock( NODOCK ) + slider.TextArea:SetPos( panel:GetWide() - 145, - 3 ) + slider.TextArea.m_colText = Color(189, 189, 189) + slider.TextArea.Paint = function( self, w, h) + draw.RoundedBox(10, 0, 1, w-15, h, Color( 58, 58, 58, 255)) + derma.SkinHook( "Paint", "TextEntry", self, w, h ) + end + + self.vars.x = x + ix + self.vars.y = y + iy +end + +function menu:textEntry( w, h, text, var ) + local panel, x, y, ix, iy = menu:grabVars() + + local label = panel:Add( "DLabel" ) + + label:SetPos( x, y ) + label:SetSize( w, h ) + label:SetText( text ) + label:SetFont("APG_element2_font") + label:SetColor( Color( 189, 189, 189) ) + label.Paint = function(self, w, h) + draw.RoundedBox(0, 0, h * 0.97, w, 1, Color(250, 250, 250, 1)) + end + + local txtEntry = vgui.Create( "DTextEntry", panel ) -- create the form as a child of frame + txtEntry:SetPos( panel:GetWide() - 110, y-1 ) + txtEntry:SetSize( 125, 20 ) + txtEntry:SetText( "custom" ) + txtEntry.OnEnter = function( self ) + end + + self.vars.x = x + ix + self.vars.y = y + iy +end + +function menu:comboBox( w, h, text, var, content ) + local panel, x, y, ix, iy = menu:grabVars() + + local label = panel:Add( "DLabel" ) + + label:SetPos( x, y ) + label:SetSize( w, h ) + label:SetText( text ) + label:SetFont("APG_element2_font") + label:SetColor( Color( 189, 189, 189) ) + label.Paint = function(self, w, h) + draw.RoundedBox(0, 0, h * 0.97, w, 1, Color(250, 250, 250, 1)) + end + + local comboBox = vgui.Create( "DComboBox", panel ) + comboBox:SetPos( panel:GetWide() - 145, y-2 ) + comboBox:SetSize( 125, 20 ) + comboBox:SetValue( APG.cfg[var].value ) + for k, v in pairs ( content ) do + comboBox:AddChoice(v) + end + comboBox.OnSelect = function( panel, index, value ) + APG.cfg[var].value = value + end + comboBox.Paint = function(i, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(58, 58, 58, 240)) + end + comboBox:SetTextColor(Color( 189, 189, 189)) + local o_OpenMenu = comboBox.OpenMenu + comboBox.OpenMenu = function( pControlOpener ) + o_OpenMenu(pControlOpener) + comboBox.Menu.Paint = function (i, w, h) + draw.RoundedBox(0, 0, 0, w, h, Color(58, 58, 58, 240)) + end + end + + self.vars.x = x + ix + self.vars.y = y + iy +end + +return {utils = utils, menu = menu} diff --git a/addons/apg/lua/apg/modules/canphysgun.lua b/addons/apg/lua/apg/modules/canphysgun.lua new file mode 100644 index 0000000..81dbbc9 --- /dev/null +++ b/addons/apg/lua/apg/modules/canphysgun.lua @@ -0,0 +1,97 @@ +--[[------------------------------------------ + + ============================ + Physgun Permissions Module + ============================ + + Developer informations : + --------------------------------- + Used variables : + +]]-------------------------------------------- +local mod = "canphysgun" + +--[[ Entity pickup part ]] +local GM = GM or GAMEMODE +APG._PhysgunPickup = APG._PhysgunPickup or GM.PhysgunPickup + +APG.hookAdd(mod, "PhysgunPickup","APG_PhysgunPickup", function(ply, ent) + if not APG.isBadEnt( ent ) then return end + if not APG.canPhysGun( ent, ply ) then return false end +end) + +function GM:PhysgunPickup( ply, ent ) + local canPickup = APG._PhysgunPickup( self, ply, ent ) + hook.Run("APG_PostPhysgunPickup", ply, ent, canPickup) + + if not canPickup then return canPickup end -- Assumed as `false` but returning just incase. + + ent.APG_HeldBy = ent.APG_HeldBy or {} + ent.APG_HeldBy.plys = ent.APG_HeldBy.plys or {} + ent.APG_Picked = true + ent.APG_Frozen = false + + if ent.APG_HeldBy and ent.APG_HeldBy.plys and not ent.APG_HeldBy.plys[sid] then + local HasHolder = (#ent.APG_HeldBy.plys > 0) + local HeldByLast = ent.APG_HeldBy.last + + if HasHolder then + if HeldByLast then + for _, ply in next, ent.APG_HeldBy.plys do + APG.ForcePlayerDrop(ply, ent) + end + else + return false + end + end + end + + ent.APG_HeldBy.plys[ply:SteamID()] = ply + ent.APG_HeldBy.last = {ply = ply, id = ply:SteamID()} + ply.APG_CurrentlyHolding = ent + + if APG.cfg["blockContraptionMove"].value then + local count = 0 + local noFrozen = true + + for _,v in next, constraint.GetAllConstrainedEntities(ent) do + count = count + 1 + if v.APG_Frozen then + noFrozen = false + break + end + end + + if noFrozen and ( count > 1 ) then + timer.Simple(0, function() + APG.freezeIt(ent, true) + end) + end + end + + return canPickup -- Assumed as `true` +end + +--[[ PhysGun Drop and Anti Throw Props ]] +APG.hookAdd(mod, "PhysgunDrop", "APG_physGunDrop", function( ply, ent ) + ent.APG_HeldBy = ent.APG_HeldBy or {} + + if ent.APG_HeldBy.plys then + ent.APG_HeldBy.plys[ply:SteamID()] = nil -- Remove the holder. + end + + ply.APG_CurrentlyHolding = nil + + if #ent.APG_HeldBy > 0 then return end + + ent.APG_Picked = false + + if APG.isBadEnt( ent ) and not APG.cfg["allowPK"].value then + APG.killVelocity(ent,true,false,true) -- Extend to constrained props, and wake target. + end +end) + +--[[ Load hooks and timers ]] + +APG.updateHooks(mod) +APG.updateTimers(mod) diff --git a/addons/apg/lua/apg/modules/ghosting.lua b/addons/apg/lua/apg/modules/ghosting.lua new file mode 100644 index 0000000..2165f90 --- /dev/null +++ b/addons/apg/lua/apg/modules/ghosting.lua @@ -0,0 +1,403 @@ +--[[------------------------------------------ + + ============================ + GHOSTING/UNGHOSTING MODULE + ============================ + + Developer informations : + --------------------------------- + Used variables : + ghostColor = { value = Color(34, 34, 34, 220), desc = "Color set on ghosted props" } + badEnts = { + value = { + ["prop_physics"] = true, + ["wire_"] = false, + ["gmod_"] = false }, + desc = "Entities to ghost/control/secure"} + alwaysFrozen = { value = false, desc = "Set to true to auto freeze props on physgun drop" } + +]]-------------------------------------------- + +local mod = "ghosting" + +--[[ Override base functions ]] +local ENT = FindMetaTable( "Entity" ) +APG._SetCollisionGroup = APG._SetCollisionGroup or ENT.SetCollisionGroup + +function ENT:SetCollisionGroup( group ) + local group = group + + local isBadEnt = APG.isBadEnt( self ) + local hasValidOwner = APG.getOwner( self ) + local groupIsNone = group == COLLISION_GROUP_NONE + local isNotFrozen = not self.APG_Frozen + local isWhitelistedEnt = APG.isWhitelistedEnt(self) + + local shouldMakeInteractable = isBadEnt and hasValidOwner and groupIsNone and isNotFrozen and not isWhitelistedEnt + + if shouldMakeInteractable then + group = COLLISION_GROUP_INTERACTIVE + end + + APG._SetCollisionGroup( self, group ) +end + +APG._SetColor = APG._SetColor or ENT.SetColor + +function ENT:SetColor( color, ... ) + local color = color + local r, g, b, a + + if type(color) == "number" then + color = Color(color, select(1, ...) or 255, select(2, ...) or 255, select(3, ...) or 255) + elseif type(color) == "table" and not IsColor(color) then + r = color.r or 255 + g = color.g or 255 + b = color.b or 255 + a = color.a or 255 + color = Color(r, g, b, a) + end + + if not IsColor(color) then + ErrorNoHalt( "Invalid color passed to SetColor!\nThis error prevents stuff from turning purple/pink." ) + else + APG._SetColor( self, color ) + end +end + +local PhysObj = FindMetaTable( "PhysObj" ) +APG._EnableMotion = APG._EnableMotion or PhysObj.EnableMotion +function PhysObj:EnableMotion( bool ) + local ent = self:GetEntity() + if APG.isBadEnt( ent ) and APG.getOwner( ent ) then + ent.APG_Frozen = not bool + if not ent.APG_Frozen then + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + end + end + return APG._EnableMotion( self, bool ) +end + +function APG.isTrap( ent, fullscan ) + local check = false + local center = ent:LocalToWorld( ent:OBBCenter() ) + local bRadius = ent:BoundingRadius() + local cache = {} + + for _,v in next, ents.FindInSphere( center, bRadius ) do + if v:IsPlayer() and v:Alive() then + local pos = v:GetPos() + local trace = { start = pos, endpos = pos, filter = v } + local tr = util.TraceEntity( trace, v ) + + if tr.Entity == ent then + if fullscan then + table.insert( cache, v ) + else + check = v + end + end + elseif APG.IsVehicle(v) then + -- Check if the distance between the spheres centers is less than the sum of their radius. + local vCenter = v:LocalToWorld( v:OBBCenter() ) + if center:Distance( vCenter ) < v:BoundingRadius() then + check = v + end + end + + if check then break end + end + + if fullscan and ( #cache > 0 ) then + return cache + else + return check or false + end +end + +function APG.entGhost( ent, noCollide, enforce ) + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if APG.cfg["vehAntiGhost"].value and APG.IsVehicle( ent ) then return end + if ent.jailWall then return end + + if not ent.APG_Ghosted then + ent.FPPAntiSpamIsGhosted = nil -- Override FPP Ghosting. + + DropEntityIfHeld( ent ) + ent:ForcePlayerDrop() + + ent.APG_oldCollisionGroup = ent:GetCollisionGroup() + + if not enforce then + -- If and old collision group was set get it. + if ent.OldCollisionGroup then ent.APG_oldCollisionGroup = ent.OldCollisionGroup end -- For FPP + if ent.DPP_oldCollision then ent.APG_oldCollisionGroup = ent.DPP_oldCollision end -- For DPP + + ent.OldCollisionGroup = nil + ent.DPP_oldCollision = nil + end + + ent.APG_Ghosted = true + if APG.cfg["ghostColorToggle"].value then + timer.Simple(0, function() + if not IsValid( ent ) then return end + + if not ent.APG_oldColor then + ent.APG_oldColor = ent:GetColor() + + if not enforce then + if ent.OldColor then ent.APG_oldColor = ent.OldColor end -- For FPP + if ent.__DPPColor then ent.APG_oldColor = ent.__DPPColor end -- For DPP + + ent.OldColor = nil + ent.__DPPColor = nil + end + end + + ent:SetColor( APG.cfg[ "ghostColor" ].value ) + end) + end + + ent.APG_oldRenderMode = ent:GetRenderMode() + ent:SetRenderMode( RENDERMODE_TRANSALPHA ) + ent:DrawShadow( false ) + + if noCollide then + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + else + ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER ) + end + + do -- Fix magic surfing + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:EnableCollisions( false ) + timer.Simple(0, function() + phys:EnableCollisions( true ) + end) + end + end + + ent:CollisionRulesChanged() + end +end + +function APG.entUnGhost( ent, ply, failmsg ) + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if ent.APG_Picked or (ent.APG_HeldBy and #ent.APG_HeldBy > 1) then return end + + if ent.APG_Ghosted == true then + ent.APG_isTrap = APG.isTrap(ent) + if not ent.APG_isTrap then + ent.APG_Ghosted = false + ent:DrawShadow( true ) + + ent:SetRenderMode( ent.APG_oldRenderMode or RENDERMODE_NORMAL ) + if APG.cfg["ghostColorToggle"].value then + ent:SetColor( ent.APG_oldColor or Color( 255, 255, 255, 255) ) + end + ent.APG_oldColor = false + + local newCollisionGroup = COLLISION_GROUP_INTERACTIVE + if APG.isWhitelistedEnt(ent) then + newCollisionGroup = ent.APG_spawnedCollisionGroup + elseif ent.APG_oldCollisionGroup == COLLISION_GROUP_WORLD then + newCollisionGroup = ent.APG_oldCollisionGroup + elseif ent.APG_Frozen then + newCollisionGroup = COLLISION_GROUP_NONE + end + + ent:SetCollisionGroup( newCollisionGroup ) + + ent:CollisionRulesChanged() + return true + else + APG.notify( false, 1, ply, failmsg or "There is something in this prop!" ) + ent:SetCollisionGroup( COLLISION_GROUP_WORLD ) + + ent:CollisionRulesChanged() + + return false + end + end +end + +function APG.ConstraintApply( ent, callback ) + local constrained = constraint.GetAllConstrainedEntities(ent) + for _,v in next, constrained do + if IsValid( v ) and v ~= ent then + callback( v ) + end + end +end + +--[[------------------------------------------ + Hooks/Timers +]]-------------------------------------------- + +APG.hookAdd( mod, "APG_PostPhysgunPickup", "APG_makeGhost", function( ply, ent, canPickup ) + if not canPickup then return end + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + + ent.APG_Picked = true + + if not APG.cfg[ "allowPK" ].value then + APG.entGhost( ent ) + APG.ConstraintApply( ent, function( _ent ) + if not _ent.APG_Frozen then + _ent.APG_Picked = true + APG.entGhost( _ent ) + end + end) -- Apply ghost to all constrained ents + end +end) + +APG.hookAdd( mod, "PlayerUnfrozeObject", "APG_unFreezeInteract", function (ply, ent, pObj) + if not APG.canPhysGun( ent, ply, "APG_unFreezeInteract" ) then return end + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if APG.cfg[ "alwaysFrozen" ].value then + return false + end -- Do not unfreeze if Always Frozen is enabled ! + if ent:GetCollisionGroup( ) ~= COLLISION_GROUP_WORLD then + ent:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE ) + end +end) + +APG.dJobRegister( "unghost", 0.1, 50, function( ent ) + if IsValid(ent) then + APG.entUnGhost( ent ) + end +end) + +APG.hookAdd( mod, "PhysgunDrop", "APG_pGunDropUnghost", function( ply, ent ) + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + ent.APG_Picked = false + + if APG.cfg[ "alwaysFrozen" ].value then + APG.freezeIt( ent ) + end + + APG.entUnGhost( ent, ply ) + + APG.ConstraintApply( ent, function( _ent ) + _ent.APG_Picked = false + APG.startDJob( "unghost", _ent ) + end) -- Apply unghost to all constrained ents +end) + +local function SafeSetCollisionGroup( ent, colgroup, pObj ) + -- If the entity is being held by a player or is ghosted abort. + if ent:IsPlayerHolding() then return end + if ent.APG_Ghosted then return end + + if pObj then pObj:Sleep() end + ent:SetCollisionGroup(colgroup) + ent:CollisionRulesChanged() +end + +APG.hookAdd( mod, "OnEntityCreated", "APG_noCollideOnCreate", function( ent ) + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + if not IsValid( ent ) then return end + if ent:GetClass() == "gmod_hands" then return end -- Fix shadow glitch + + ent.APG_spawnedCollisionGroup = ent:GetCollisionGroup() -- have to set it before it's ghosted + + timer.Simple( 0, function() + if not ent then return end + if not ent:IsSolid() then return end -- Don't ghost ghosts. + + local spawnedEnt = tostring(ent) + local owner = "" + if APG.getOwner(ent) ~= nil then -- incase getowner return's nil (like on reloading) + owner = APG.getOwner(ent):Nick() + else + owner = "console" + end + + APG.entGhost( ent ) + end) + + timer.Simple( 0, function() + if not ent then return end + if not ent:IsSolid() then return end -- Don't ghost ghosts. + + local owner = APG.getOwner( ent ) + DropEntityIfHeld( ent ) + ent:ForcePlayerDrop() + + if IsValid( owner ) and owner:IsPlayer() then + local pObj = ent:GetPhysicsObject() + if IsValid(pObj) then + if APG.cfg[ "alwaysFrozen" ].value then + ent.APG_Frozen = true + pObj:EnableMotion( false ) + elseif pObj:IsMoveable() then + ent.APG_Frozen = false + SafeSetCollisionGroup( ent, COLLISION_GROUP_INTERACTIVE ) + end + end + end + + APG.startDJob( "unghost", ent ) + end) +end) + +local BlockedProperties = { "collision", "persist", "editentity", "drive", "ignite", "statue" } +APG.hookAdd( mod, "CanProperty", "APG_canProperty", function(ply, property, ent) + local property = tostring( property ) + if ( table.HasValue(BlockedProperties, property) and ent.APG_Ghosted ) then + APG.notify( false, 1, ply, "Cannot set", property, "properties on ghosted entities!" ) + return false + end +end) + +-- Custom Hooks -- +local function checkDoor(ply, ent) + local isTrap = APG.isTrap(ent, true) + + if isTrap and istable(isTrap) then + ent.APG_Ghosted = true + ent:SetCollisionGroup(COLLISION_GROUP_WORLD) + + for _,v in next, isTrap do + if v:IsPlayer() then + local push = v:GetForward() + push = push * 1200 + push.z = 60 + + v:SetVelocity(push) + end + end + + timer.Simple(1, function() + if IsValid(ply) and IsValid(ent) then + ent.APG_Ghosted = false + ent:oldFadeDeactivate() + ent:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE ) + + if IsValid(isTrap) then + APG.notify( false, 1, ply, "Unable to unstuck objects from fading door!" ) + APG.entGhost(ent) + end + end + end) + end +end + +APG.hookAdd(mod, "APG.FadingDoorToggle", "APG_FadingDoor", function(ent, isFading) + if APG.isBadEnt(ent) and APG.cfg["fadingDoorGhosting"].value then + local ply = APG.getOwner( ent ) + + if (IsValid(ply) and ply:IsPlayer() and not isFading) then + -- Delay slightly, this is needed to wait for other things happen before it works. + timer.Simple(0.001, function() + checkDoor(ply, ent) + end) + end + end +end) + +--[[ Load hooks and timers ]] + +APG.updateHooks(mod) +APG.updateTimers(mod) diff --git a/addons/apg/lua/apg/modules/home.lua b/addons/apg/lua/apg/modules/home.lua new file mode 100644 index 0000000..750ac88 --- /dev/null +++ b/addons/apg/lua/apg/modules/home.lua @@ -0,0 +1 @@ +-- This is a dummy file, for APGs home page! (As the home page is built like a module.) \ No newline at end of file diff --git a/addons/apg/lua/apg/modules/lag_detection.lua b/addons/apg/lua/apg/modules/lag_detection.lua new file mode 100644 index 0000000..ae8d448 --- /dev/null +++ b/addons/apg/lua/apg/modules/lag_detection.lua @@ -0,0 +1,184 @@ +--[[------------------------------------------ + + ============================ + LAG DETECTION MODULE + ============================ + + Developer informations : + --------------------------------- + Used variables : + lagTrigger = { value = 75, desc = "% difference between current lag and average lag."} + lagsCount = { value = 8, desc = "Number of consectuives laggy frames in order to run a cleanup."} + bigLag = { value = 2, desc = "Time (seconds) between 2 frames to trigger a cleanup"} + lagFunc = { value = "cleanUp_unfrozen", desc = "Function ran on lag detected" } + lagFuncTime = { value = 20, desc = "Time (seconds) between 2 cleanup (avoid spam)"} + + Ready to hook : + APG_lagDetected = Ran on lag detected by the server. + Example : hook.Add( "APG_lagDetected", "myLagDetectHook", function() print("[APG] Lag detected (printed from my very own hook)") end) + +]]-------------------------------------------- +local mod = "lag_detection" +local table = table + +--[[ Lag fixing functions ]] + +local lagFix = { + cleanup_all = function( ) APG.cleanUp( "all" ) end, + cleanup_unfrozen = function( ) APG.cleanUp( "unfrozen" ) end, + ghost_unfrozen = APG.ghostThemAll, + freeze_unfrozen = APG.freezeProps, + smart_cleanup = APG.smartCleanup, + custom_function = APG.customFunc, +} + +--[[ Lag detection vars ]] + +local lastTick = 0 +local tickDelta = 0 +local tickRate = 0 + +local lagCount = 0 +local lagThreshold = math.huge + +local processHault = false +local processFunc = false +local processExecs = 0 + +local sampleData = {} +local sampleCount = 0 + +local function addSample( data ) + local index = (sampleCount%66)+1 + local data = tonumber(data) + + sampleCount = sampleCount + 1 + if sampleCount >= 66 then + sampleCount = 0 + end + + table.insert(sampleData, index, data) +end + +function APG.resetLag(dontResetData) + lastTick = 0 + tickDelta = 0 + + lagCount = 0 + lagThreshold = tickRate + + processHault = false + processFunc = false + processExecs = 0 +end + +function APG.calculateLagAverage() + local count = 0 + local total = 0 + local sampleData = sampleData + + for _, v in next, sampleData do + total = total + v + count = count + 1 + end + + if count < 12 then + return false -- Not enough data, yet. + end + + return (total/count) +end + +hook.Add("Think", "APG_initLagDetection", function() + tickRate = FrameTime() + lagThreshold = tickRate + hook.Remove("Think", "APG_initLagDetection") +end) + +APG.timerAdd( mod, "APG_process", 3, 0, function() + if not APG.modules[ mod ] then return end + + if sampleCount < 12 or tickDelta < lagThreshold then + addSample(tickDelta) + end + + local average = APG.calculateLagAverage() + + if average then + lagThreshold = average * ( 1 + ( APG.cfg[ "lagTrigger" ].value / 100 ) ) + end + + processExecs = 0 +end) + +APG.hookAdd( mod, "Tick", "APG_lagDetection", function() + if not APG.modules[ mod ] then return end + + local sysTime = SysTime() + tickDelta = sysTime - lastTick + + if lagThreshold > tickRate and tickDelta >= lagThreshold then + + lagCount = lagCount + 1 + + if (lagCount >= APG.cfg[ "lagsCount" ].value) or ( tickDelta > APG.cfg[ "bigLag" ].value ) then + + lagCount = 0 + + if ( not processHault ) and ( not processFunc ) then + + processHault = true + + timer.Simple(APG.cfg["lagFuncTime"].value, function() + processHault = false + end) + + hook.Run( "APG_lagDetected" ) + + end + + end + + else + lagCount = lagCount - 0.5 + if lagCount < 0 then + lagCount = 0 + end + end + + lastTick = sysTime +end) + +--[[ Utils ]] + +hook.Remove( "APG_lagDetected", "main") -- Sometimes, I dream about cheese. +hook.Add( "APG_lagDetected", "main", function() + if not APG then return end + + APG.notify( true, 2, APG.cfg["notifyLevel"].value, "!WARNING LAG DETECTED!" ) + + local funcName = APG.cfg[ "lagFunc" ].value + local func = lagFix[ funcName ] + + if not func then return end + + hook.Run("APG_logsLagDetected") -- put it here so it doesn't spam + + processFunc = true + + func(false, function() + processFunc = false + processExecs = processExecs + 1 + end) + + if processExecs > 3 then + -- If the lag cleanup process runs more then 3 times in 3 seconds, then + -- reset our data. + APG.resetLag() + end +end) + +--[[ Load hooks and timers ]] + +APG.updateHooks(mod) +APG.updateTimers(mod) diff --git a/addons/apg/lua/apg/modules/logs.lua b/addons/apg/lua/apg/modules/logs.lua new file mode 100644 index 0000000..a53c300 --- /dev/null +++ b/addons/apg/lua/apg/modules/logs.lua @@ -0,0 +1,64 @@ +local mod = "logs" + +if GAS then + if APG.modules[ mod ] and GAS.Logging then + local MODULE = GAS.Logging:MODULE() + + MODULE.Category = "APG" + MODULE.Name = "Lag Detection" + MODULE.Colour = Color(255,0,0) + + MODULE:Setup(function() + + MODULE:Hook("APG_logsLagDetected", "APG.logs.lagDetected", function() + + if not APG.cfg["logLagDetected"].value then return end + + MODULE:Log("Lag detected, running lag function {1} to prevent further lag.", GAS.Logging:Highlight(APG.cfg["lagFunc"].value)) + + end) + + end) + + GAS.Logging:AddModule(MODULE) + + local MODULE = GAS.Logging:MODULE() + + MODULE.Category = "APG" + MODULE.Name = "Crash Attempts" + MODULE.Colour = Color(255,0,0) + + MODULE:Setup(function() + + MODULE:Hook("APG_stackCrashAttempt", "APG.logs.stackCrashAttempt", function(ply, count) + + if not APG.cfg["logStackCrashAttempt"].value then return end + MODULE:Log("{1} stacked {2} props and triggered a detection.", GAS.Logging:FormatPlayer(ply), GAS.Logging:Highlight(count)) + + end) + + end) + + GAS.Logging:AddModule(MODULE) + end +end + +if plogs then + if APG.modules[ mod ] and plogs.Register then + plogs.Register("APG", true, Color(255,100,0)) + + plogs.AddHook("APG_logsLagDetected", function() + if not APG.cfg["logLagDetected"].value then return end + plogs.PlayerLog("console", "APG", "Lag detected, running lag fix function " .. APG.cfg["lagFunc"].value .. " to prevent further lag.") + end) + + + plogs.AddHook("APG_stackCrashAttempt", function(ply, count) + if not APG.cfg["logStackCrashAttempt"].value then return end + plogs.PlayerLog(ply, "APG", ply:NameID() .. " stacked " .. count .. " props and triggered a detection.", { + ["Name"] = ply:Name(), + ["SteamID"] = ply:SteamID() + }) + end) + end +end diff --git a/addons/apg/lua/apg/modules/misc.lua b/addons/apg/lua/apg/modules/misc.lua new file mode 100644 index 0000000..4896522 --- /dev/null +++ b/addons/apg/lua/apg/modules/misc.lua @@ -0,0 +1,261 @@ +--[[------------------------------------------ + + ============================ + MISCELLANEOUS MODULE + ============================ + + Developer informations : + --------------------------------- + Used variables : + vehDamage = { value = true, desc = "True to enable vehicles damages, false to disable." } + vehNoCollide = { value = false, desc = "True to disable collisions between vehicles and players"} + autoFreeze = { value = false, desc = "Freeze every unfrozen prop each X seconds" } + autoFreezeTime = { value = 120, desc = "Auto freeze timer (seconds)"} + +]]-------------------------------------------- +local mod = "misc" + +--[[ Helper functions ]] +local timerSimple = timer.Simple + +local function isVehDamage( dmg, atk, ent ) + if not IsValid( ent ) then return false end + if dmg:GetDamageType() == DMG_VEHICLE or APG.IsVehicle( atk ) or APG.IsVehicle( ent ) then + return true + end + return false +end + +local function getPhys(ent) + local phys = ent.GetPhysicsObject and ent:GetPhysicsObject() or false + return ( phys and IsValid(phys) ) and phys or false +end + +local function wait(callback) + timerSimple(0.003, callback) +end + +--[[ No Collide vehicles on spawn ]] +APG.hookAdd( mod,"OnEntityCreated", "APG_noCollideVeh", function( ent ) + timerSimple(0.03, function() + if APG.cfg[ "vehNoCollide" ].value and APG.IsVehicle( ent ) then + ent:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + end + end) +end) + +--[[ Disable prop damage ]] +APG.hookAdd( mod, "EntityTakeDamage","APG_noPropDmg", function( target, dmg ) + if ( not APG.cfg[ "allowPK" ].value ) then -- Check if prop kill is allowed, before checking anything else. + local atk, ent = dmg:GetAttacker(), dmg:GetInflictor() + if APG.isBadEnt( ent ) or dmg:GetDamageType() == DMG_CRUSH or ( APG.cfg[ "vehDamage" ].value and isVehDamage( dmg, atk, ent ) ) then + dmg:SetDamage(0) + return true + -- ^ Returning true overrides and blocks all related damage, it also prevents the hook from running any further preventing unintentional damage from other addons. + end + end +end) + +--[[ Remove Invalid Physics ]] +APG.hookAdd( mod, "OnEntityCreated", "APG_removeInvalidPhysics", function( ent ) + if ( not APG.cfg[ "removeInvalidPhysics" ].value ) then return end + + timerSimple(0, function() + if not IsValid( ent ) then return end + + local model = ent:GetModel() + local owner = APG.getOwner( ent ) + local phys = ent:GetPhysicsObject() + + if IsValid( owner ) and owner:IsPlayer() then + if ( not model ) or string.lower(string.sub(model, 1, 6)) ~= "models" then + SafeRemoveEntity( ent ) + return + end + if ( not IsValid( physObj ) ) and ( not APG.cfg["invalidPhysicsWhitelist"].value[model] ) then + SafeRemoveEntity( ent ) + end + end + end) +end) + +--[[ Block Physgun Reload ]] +APG.hookAdd( mod, "OnPhysgunReload", "APG_blockPhysgunReload", function( _, ply ) + if APG.cfg[ "blockPhysgunReload" ].value then + return false + end +end) + +--[[ Block Gravitygun Throwing ]] +APG.hookAdd( mod, "GravGunOnDropped", "APG_blockGravGunThrow", function(ply, ent) + if ( not APG.cfg["blockGravGunThrow"].value ) then return end + APG.killVelocity(ent, false, false, true) +end) + +--[[ Auto prop freeze ]] +APG.timerAdd( mod, "APG_autoFreeze", APG.cfg[ "autoFreezeTime" ].value, 0, function() + if APG.cfg[ "autoFreeze" ].value then + APG.freezeProps() + end +end) + +--[[ Fading door management ]] +APG.hookAdd(mod, "CanTool", "APG_fadingDoorTool", function(ply, tr, tool) + if IsValid(tr.Entity) and tr.Entity.APG_Ghosted then + APG.notify(false, 1, ply, "Cannot use tool on ghosted entity!") + return false + end + + if APG.cfg["fadingDoorHook"].value and tool == "fading_door" then + timerSimple(0, function() + if IsValid(tr.Entity) and not tr.Entity:IsPlayer() then + local ent = tr.Entity + + + + if not IsValid(ent) then return end + if not ent.isFadingDoor then return end + + local state = ent.fadeActive + + if state then + ent:fadeDeactivate() + end + + ent.oldFadeActivate = ent.oldFadeActivate or ent.fadeActivate + ent.oldFadeDeactivate = ent.oldFadeDeactivate or ent.fadeDeactivate + + function ent:fadeActivate() + if hook.Run("APG.FadingDoorToggle", self, true, ply) then return end + ent:oldFadeActivate() + end + + function ent:fadeDeactivate() + if hook.Run("APG.FadingDoorToggle", self, false, ply) then return end + ent:oldFadeDeactivate() + ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE) + end + + if state then + ent:fadeActivate() + end + end + end) + end +end) + +APG.hookAdd(mod, "APG.FadingDoorToggle", "init", function(ent, state, ply) + if ent.APG_Ghosted then + APG.entUnGhost(ent, ply, "Your fading door is ghosted! (" .. ( ent.GetModel and ent:GetModel() or "???" ) .. ")") + return true + end + + ent:ForcePlayerDrop() + + local phys = getPhys(ent) + if phys then + phys:EnableMotion(false) + end +end) + +--[[ Set sv_turbophysics? ]]-- +if APG.cfg[ "touchServerSettings" ].value then + if APG.cfg[ "setTurboPhysics" ].value then + RunConsoleCommand('sv_turbophysics', '1') + else + RunConsoleCommand('sv_turbophysics', '0') + end +end + +--[[ FRZR9K ]]-- + +local zero = Vector(0,0,0) + +local function sleepyPhys(phys) + if not phys:IsAsleep() then + local vel = phys:GetVelocity() + if vel:Distance(zero) <= 23 then + phys:Sleep() + end + end +end + +APG.timerAdd(mod, "frzr9k-p1", 5, 0, function(ent) + if APG.cfg["sleepyPhys"].value then + for _, v in next, ents.GetAll() do + if v.APG_Frozen == false then + local phys = getPhys( v ) + if APG.isBadEnt( v ) and phys then + sleepyPhys( phys ) + end + end + end + end +end) + +-- Collision Monitoring -- +local function collcall(ent, data) + local hit = data.HitObject + local mep = data.PhysObject + + if IsValid(ent) and IsValid(hit) and IsValid(mep) then + ent["frzr9k"] = ent["frzr9k"] or {} + + local obj = ent["frzr9k"] + + obj.Collisions = (obj.Collisions or 0) + 1 + + obj.CollisionTime = obj.CollisionTime or (CurTime() + 5) + obj.LastCollision = CurTime() + + if obj.Collisions > 23 then + obj.Collisions = 0 + for _,e in next, {mep, hit} do + e:SetVelocityInstantaneous(Vector(0,0,0)) + e:Sleep() + end + end + + if obj.CollisionTime < obj.LastCollision then + local subtract = 1 + local mem = obj.CollisionTime + + while true do + mem = mem + 5 + subtract = subtract + 1 + if mem >= obj.LastCollision then + break + end + end + + obj.Collisions = (obj.Collisions - subtract) + obj.Collisions = (obj.Collisions > 1) and obj.Collisions or 1 + + obj.CollisionTime = (CurTime() + 5) + end + + ent["frzr9k"] = obj + end +end + +APG.hookAdd(mod, "OnEntityCreated", "frzr9k-p2", function(ent) + if APG.cfg["sleepyPhys"].value and APG.cfg["sleepyPhysHook"].value then + wait(function() + if APG.isBadEnt( ent ) and getPhys( ent ) then + ent:AddCallback("PhysicsCollide", collcall) + end + end) + end +end) + + +if APG.cfg[ "physGunMaxRange" ].value then + RunConsoleCommand("physgun_maxrange", APG.cfg["physGunMaxRange"].value) -- Can't run SetInt on a convar that wasn't made in lua +end + +--[[------------------------------------------ + Load hooks and timers +]]-------------------------------------------- + +APG.updateHooks(mod) +APG.updateTimers(mod) diff --git a/addons/apg/lua/apg/modules/notification.lua b/addons/apg/lua/apg/modules/notification.lua new file mode 100644 index 0000000..2834686 --- /dev/null +++ b/addons/apg/lua/apg/modules/notification.lua @@ -0,0 +1,95 @@ +--[[------------------------------------------ + + ============================ + NOTIFICATION MODULE + ============================ + +]]-------------------------------------------- + +local mod = "notification" + +function APG.notify(log, level, target, ... ) -- The most advanced notification function in the world. + + local log = log -- Should the message be logged? + local level = level -- The level of the error. + local target = target -- Whos ist this message meant for? + local msg = {...} -- Pack the arguments in a table. + + local outMsg = "" -- Concat the message into this variable. + local isConsole = ( target == "console" ) -- Is this message meant for the console + + if type(level) == "string" then + level = string.lower( level ) + level = ( level == "notice" and 0 ) or ( level == "warning" and 1 ) or ( level == "alert" and 2 ) + end + + if target then + if type(target) == "string" then + (({ + ["all"] = function() + target = player.GetHumans() + end, + ["admin"] = function() + local data = player.GetHumans() + for k, v in next, data do + if not v:IsAdmin() then + data[k] = nil + end + end + target = data + end, + ["superadmin"] = function() + local data = player.GetHumans() + for k, v in next, data do + if not v:IsSuperAdmin() then + data[k] = nil + end + end + target = data + end, + ["console"] = function() + -- Just send it to the logs without actually giving out a message. + end, + })[target])() + else + if IsEntity( target ) and IsValid( target ) and target:IsPlayer() then + target = { target } + end + end + end + + local outMsg = "" + + for _, v in next, msg do + local data = v and tostring(v) or "" + if string.len( outMsg ) == 0 then + outMsg = data + else + outMsg = outMsg .. " " .. data + end + end + + outMsg = string.Trim( outMsg ) + + if string.len( outMsg ) > 0 and ( log or isConsole ) then + ServerLog("[APG] " .. outMsg .. "\n") + if isConsole then + MsgC( Color( 72, 216, 41 ), "[APG]", Color( 255, 255, 255 ), outMsg) + return true + end + end + + if type(target) ~= "table" then return false end + + for _, v in next, target do + if not isentity(v) then continue end + if not IsValid(v) then continue end + net.Start("apg_notice_s2c") + net.WriteUInt(level, 3) + net.WriteString(outMsg) + net.Send(v) + end + + return true + +end diff --git a/addons/apg/lua/apg/modules/stack_detection.lua b/addons/apg/lua/apg/modules/stack_detection.lua new file mode 100644 index 0000000..c6a78c8 --- /dev/null +++ b/addons/apg/lua/apg/modules/stack_detection.lua @@ -0,0 +1,112 @@ +--[[------------------------------------------ + + ============================ + STACK DETECTION MODULE + ============================ + + Developer informations : + --------------------------------- + Used variables : + stackMax = { value = 15, desc = "Max amount of entities stacked on a small area"} + stackArea = { value = 15, desc = "Sphere radius for stack detection (gmod units)"} + fading + +]]-------------------------------------------- +local mod = "stack_detection" +local SafeRemoveEntity = SafeRemoveEntity + +function APG.checkStack( ent, pcount ) + if not APG.isBadEnt( ent ) then return end + + local efound = ents.FindInSphere(ent:GetPos(), APG.cfg["stackArea"].value ) + local count = 0 + local max_count = APG.cfg["stackMax"].value + for k, v in pairs (efound) do + if APG.isBadEnt( v ) and APG.getOwner( v ) then + count = count + 1 + end + end + if count >= (pcount or max_count) then + local owner, _ = ent:CPPIGetOwner() + ent:Remove() + if not owner.APG_CantPickup then + APG.blockPickup( owner, 10 ) + + APG.notify( false, 2, owner, "You tried to unfreeze a stack of " .. count .. " props! >:(" ) + hook.Run("APG_stackCrashAttempt", owner, count) + APG.notify( true, 2, APG.cfg["notifyLevel"].value, owner:Nick() .. " [" .. owner:SteamID() .. "]" .. " tried to unfreeze a stack of " .. count .. " props!" ) + end + end +end + +APG.hookAdd(mod, "PhysgunPickup", "APG_stackCheck",function(ply, ent) + if not APG.canPhysGun( ent, ply, "APG_stackCheck" ) then return end + if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end + APG.checkStack( ent ) +end) + +-- Requires Fading Door Hooks -- +local notify = false +local curTime = 0 +local lastCall = 0 + +APG.hookAdd(mod, "APG.FadingDoorToggle", "APG_fadingDoorStackCheck", function(ent, faded) + curTime = CurTime() + + if IsValid(ent) and faded then + local ply = APG.getOwner(ent) + local pos = ent:GetPos() + local doors = {} + local count = 1 -- Start at 1 to include the original fading door + + for _,v in next, ents.FindInSphere(pos, APG.cfg["stackArea"].value) do + if v ~= ent and IsValid(v) and v.isFadingDoor and APG.getOwner(v) == ply then + table.insert(doors, v) + count = count + 1 + end + end + + if count >= APG.cfg["fadingDoorStackMax"].value then + notify = true + for _,v in next, doors do + SafeRemoveEntity(v) + end + end + + if curTime > lastCall then + if notify and APG.cfg["fadingDoorStackNotify"].value then + notify = false + APG.notification(ply:Nick() .. " had a stack of " .. count .. " fading doors that were removed.", APG.cfg["notifyLevel"].value, 2) + APG.notification("Some of your fading doors were removed.", ply) + end + end + end + + lastCall = curTime + 0.001 +end) + +--[[ Stacker Exploit Quick Fix ]] +hook.Add( "InitPostEntity", "APG_InitStackFix", function() + timer.Simple(60, function() + local TOOL = weapons.GetStored("gmod_tool")["Tool"][ "stacker" ] or weapons.GetStored("gmod_tool")["Tool"][ "stacker_v2" ] + if not TOOL then return end + + -- Stacker improved (beta) fixed this by setting a maximum number of constraints + -- See : https://git.io/vPvJK + + APG.dJobRegister( "weld", 0.3, 20, function( sents ) + if not IsValid( sents[1] ) or not IsValid( sents[2]) then return end + constraint.Weld( sents[1], sents[2], 0, 0, 0 ) + end) + + function TOOL:ApplyWeld( lastEnt, newEnt ) + if ( not self:ShouldForceWeld() and not self:ShouldApplyWeld() ) then return end + APG.startDJob( "weld", {lastEnt, newEnt} ) + end + end) +end) + +--[[ Load hooks and timers ]] + +APG.updateHooks(mod) +APG.updateTimers(mod) diff --git a/addons/apg/lua/apg/modules/tools.lua b/addons/apg/lua/apg/modules/tools.lua new file mode 100644 index 0000000..b6e3d4f --- /dev/null +++ b/addons/apg/lua/apg/modules/tools.lua @@ -0,0 +1,128 @@ +--[[------------------------------------------ + + ============================ + TOOLS MODULE + ============================ + + Developer informations : + --------------------------------- + Used variables : + +]]-------------------------------------------- +local mod = "tools" + +function APG.canTool( ply, tool, ent ) + if IsValid(ent) then + if ent.ToolDisabled == false then + return false + end + + if ent.CPPICanTool then + return ent:CPPICanTool(ply, tool) + end -- Let CPPI handle things from here. + end + + if APG.cfg[ "checkCanTool" ].value and ply.APG_CantPickup == true then-- If we can't pickup we can't tool either. + return false + end + + return 0 -- return 0 so if all of the check's don't return anything then it doesn't default to disabling toolgun. +end + +--[[ APG CanTool Check ]] + +APG.hookAdd(mod, "CanTool", "APG_ToolMain", function(ply, tr, tool) + if not APG.canTool(ply, tool, tr.Entity) then + return false + end +end) + +--[[ Tool Spam Control ]] + +APG.hookAdd(mod, "CanTool", "APG_ToolSpamControl", function(ply) + if not APG.cfg[ "blockToolSpam" ].value then return end + + ply.APG_ToolCTRL = ply.APG_ToolCTRL or {} + + local ply = ply + local data = ply.APG_ToolCTRL + local delay = 0 + local diff = 0 + + data.curTime = CurTime() + data.toolDelay = data.toolDelay or 0 + data.toolUseTimes = data.toolUseTimes or 0 + + diff = data.curTime - data.toolDelay + delay = APG.cfg[ "blockToolDelay" ].value + + if data.toolUseTimes <= 0 or diff > delay then + data.toolUseTimes = 0 + data.toolDelay = 0 + data.wasNotified = false + end + + if diff > 0 then + data.toolUseTimes = math.max( data.toolUseTimes - 1, 0 ) + else + data.toolUseTimes = math.min( data.toolUseTimes + 1, APG.cfg[ "blockToolRate" ].value ) + if data.toolUseTimes >= APG.cfg[ "blockToolRate" ].value then + data.toolDelay = data.curTime + delay + if not data.wasNotified then + data.wasNotified = true + APG.notify( false, 1, ply, "You are using the toolgun too fast, slow down!" ) + end + return false + end + end + + if data.toolDelay == 0 then + data.toolDelay = data.curTime + delay + end +end) + +--[[ Block Tool World ]] + +APG.hookAdd(mod, "CanTool", "APG_ToolWorldControl", function(ply, tr) + if not APG.cfg[ "blockToolWorld" ].value then return end + if tr.HitWorld then + if not timer.Exists("APG-" .. ply:UniqueID() .. "-Notify") then + APG.notify( false, 1, ply, "You may not use the toolgun on the world." ) + timer.Create("APG-" .. ply:UniqueID() .. "-Notify", 5, 1, function() end) + end + return false + end +end) + +--[[ Block Tool Unfreeze ]] + +APG.hookAdd(mod, "CanTool", "APG_ToolUnfreezeControl", function(ply, tr) + if not APG.cfg[ "blockToolUnfreeze" ].value then return end + timer.Simple(0.003, function() + local ent = tr.Entity + local phys = NULL + + if IsValid(ent) then + phys = ent:GetPhysicsObject() + if IsValid(phys) and phys:IsMotionEnabled() then + phys:EnableMotion( false ) + end + end + end) +end) + +if APG.cfg[ "touchServerSettings" ].value then + local conVar = GetConVar("toolmode_allow_creator") + if conVar then + if APG.cfg[ "blockCreatorTool" ].value then + conVar:SetBool(false) + else + conVar:SetBool(true) + end + end +end + +--[[ Load hooks and timers ]] + +APG.updateHooks(mod) +APG.updateTimers(mod) diff --git a/addons/apg/lua/apg/sh_config.lua b/addons/apg/lua/apg/sh_config.lua new file mode 100644 index 0000000..bed53c7 --- /dev/null +++ b/addons/apg/lua/apg/sh_config.lua @@ -0,0 +1,378 @@ +--[[------------------------------------------ + ==================================================================================== + /!\ READ ME /!\ /!\ READ ME /!\ /!\ READ ME /!\ + ==================================================================================== + + This file is the default config file. + If you want to configure APG to fit your server needs, you can edit the config + ingame using the chat command : !apg or apg in console. + +]]-------------------------------------------- +APG.cfg = APG.cfg or {} +APG.modules = APG.modules or {} + +--[[---------- + Your very own custom function + This function will run whenever lag is detected on your server! +]]------------ +function APG.customFunc( notification ) + -- Do something +end + +--[[---------- + Avalaible premade functions - THIS IS INFORMATIVE PURPOSE ONLY ! +]]------------ +if CLIENT then + APG_lagFuncs = { -- THIS IS INFORMATIVE PURPOSE ONLY ! + "cleanup_all", -- Cleanup every props/ents protected by APG (not worldprops nor vehicles) + "cleanup_unfrozen", -- Cleanup only unfrozen stuff + "ghost_unfrozen", -- Ghost unfrozen stuff + "freeze_unfrozen", -- Freeze unfrozen stuff + "smart_cleanup", -- Cleanup unfrozen fading doors, freeze unfrozens, remove large stacks + "custom_function" -- Your custom function (see APG.customFunc) + } -- THIS IS INFORMATIVE PURPOSE ONLY ! + + APG_notifyLevels = { + "everyone", + "admin", + "superadmin" + } +end + +--[[------------------------------------------ + DEFAULT SETTINGS -- You CAN edit this part, but you SHOULDN'T +]]-------------------------------------------- + +local defaultSettings = {} +defaultSettings.modules = { -- Set to true to enable and false to disable module. + ["home"] = true, -- Essential, do not disable. + ["ghosting"] = true, + ["stack_detection"] = true, + ["lag_detection"] = true, + ["notification"] = true, + ["canphysgun"] = true, -- Essential, do not disable. + ["misc"] = true, + ["tools"] = true, + ["logs"] = true, +} + +defaultSettings.cfg = { + --[[---------- + Ghosting module + ]]------------ + ghostColor = { + value = Color(34, 34, 34, 220), + desc = "Color set on ghosted props" + }, + + ghostColorToggle = { + value = true, + desc = "Toggle ghosting color." + }, + + badEnts = { + value = { + ["prop_physics"] = true, + ["wire_"] = false, + ["gmod_"] = false, + ["keypad"] = false, + }, + desc = "Entities to ghost/control/secure (true if exact name, false if it is a pattern" + }, + + unGhostingWhitelist = { + value = { + ["zmlab_"] = false, + }, + desc = "Entities that should be set back to their original (spawned) collision group when frozen/dropped." + }, + + removeInvalidPhysics = { + value = false, + desc = "Should we attempt to detect and remove invalid physics objects? (Entities with bad or no physics objects/Physics objects without models.)" + }, + + invalidPhysicsWhitelist = { + value = {}, + desc = "Entities that shouldn't be removed if they don't have proper physics" + }, + + alwaysFrozen = { + value = true, + desc = "Set to true to auto freeze props on physgun drop (aka APG_FreezeOnDrop)" + }, + + --[[---------- + Stack detection module + ]]------------ + stackMax = { + value = 15, + desc = "Max amount of entities stacked in a small area" + }, + + stackArea = { + value = 15, + desc = "Sphere radius for stack detection (gmod units)" + }, + fadingDoorStackMax = { + value = 5, + desc = "Maximum amount of fading doors that can be stacked in stackArea." + }, + fadingDoorStackNotify = { + value = false, + desc = "Notify the players when their fading doors were removed" + }, + + --[[---------- + Lag detection module + ]]------------ + lagTrigger = { + value = 75, + desc = "[Default: 75%] Differential threshold between current lag and average lag." + }, + + lagsCount = { + value = 8, + desc = "Number of consectuives laggy frames in order to run a cleanup." + }, + + bigLag = { + value = 2, + desc = "Maximum time (seconds) between 2 frames to trigger a cleanup" + }, + + lagFunc = { + value = "ghost_unfrozen", + desc = "Function ran on lag detected, see APG_lagFuncs." + }, + + lagFuncTime = { + value = 8, + desc = "Time (seconds) between 2 anti lag function (avoid spam)" + }, + + + + --[[ Notifications ]] -- + + notifySounds = { + value = false, -- Might make it where certain ones run sound + desc = "When notifications run do you want the sounds to play?" + }, + + notifyLevel = { + value = "admin", + desc = "Notification levels, refer to APG_notifyLevels" + }, + + notifyLagFunc = { + value = false, + desc = "Do you want the notifyLevel to see the lagFunc that ran? (refer to APG_lagFuncs)" + }, + + -- TODO: Make a ULX/ULIB module + -- notifyULibInheritance = { + -- value = true, + -- desc = "Do you want to use inheritance for notifyRanks? (only works with ULIB/ULX)" + -- }, + + -- notifyRanks = { + -- value = { "trialmod", "moderator", "admin", "superadmin", "owner" }, + -- desc = "The ranks that you want to see the notification" -- If you have notifyULibInheritance you only need to do the lowest rank(s) + -- }, + + --[[ Override Server Settings? ]] + touchServerSettings = { + value = false, + desc = "Should we override Server Settings? (Used for setting ConVars)" + }, + + --[[ Vehicles ]]-- + + vehDamage = { + value = false, + desc = "True to disable vehicles damages, false to enable." + }, + + vehNoCollide = { + value = false, + desc = "True to disable collisions between vehicles and players" + }, + + vehIncludeWAC = { + value = true, + desc = "Check for WAC vehicles." + }, + + vehAntiGhost = { + value = false, + desc = "Toggle vehicle ghosting" + }, + + blockGravGunThrow = { + value = true, + desc = "Toggle GravGun throwing." + }, + + setTurboPhysics = { + value = false, + desc = "Toggle sv_turbophysics." + }, + + --[[ Tool Control ]]-- + + checkCanTool = { + value = true, + desc = "Should tools be blocked on APG_CantPickup?" + }, + + blockToolSpam = { + value = true, + desc = "Block players from spamming the toolgun." + }, + + blockToolRate = { + value = 5, + desc = "How fast can we use the toolgun before it gets blocked? (Clicks per second(s))" + }, + + blockToolDelay = { + value = 1, + desc = "How many seconds should we wait after we were stopped? (The aforementioned second(s))" + }, + + blockToolWorld = { + value = false, + desc = "Prevent using the toolgun on the world." + }, + + blockToolUnfreeze = { + value = true, + desc = "Prevent the toolgun from unfreezing props." + }, + + blockCreatorTool = { + value = true, + desc = "Should we block the creator tool?" + }, + + checkTooledEnts = { + value = true, + desc = "Review entities near tool use." + }, + + physGunMaxRange = { + value = 700, + desc = "Max range a physics gun can go" + }, + + --[[ Logs ]]-- + + logStackCrashAttempt = { + value = true, + desc = "Log when someone tries to lag/crash the server with stacker" + }, + + logLagDetected = { + value = true, + desc = "Log when the server lags" + }, + + --[[ Props related ]]-- + + blockPhysgunReload = { + value = true, + desc = "Block players from using physgun reload" + }, + + blockContraptionMove = { + value = true, + desc = "Block players from moving contraptions" + }, + + autoFreeze = { + value = false, + desc = "Freeze every unfrozen prop each X seconds" + }, + + autoFreezeTime = { + value = 120, + desc = "Auto freeze timer (seconds)" + }, + + fadingDoorHook = { + value = true, + desc = "Inject custom hooks into Fading Doors" + }, + + fadingDoorGhosting = { + value = true, + desc = "Activate fading door ghosting" + }, + + sleepyPhys = { + value = false, + desc = "Activate FRZR9K (Sleepy Physics)" + }, + + sleepyPhysHook = { + value = false, + desc = "Hook FRZR9K into collision (Experimental)" + }, + + allowPK = { + value = false, + desc = "Allow prop killing" + }, + + developerDebug = { + value = false, + desc = "Dev Logs (prints stuff)" + } +} + +--[[------------------------------------------ + LOADING SAVED SETTINGS -- DO NOT EDIT THIS PART +]]-------------------------------------------- +if SERVER and file.Exists( "apg/settings.txt", "DATA" ) then + table.Merge( APG, defaultSettings ) -- Load the default settings first! + + local settings = file.Read( "apg/settings.txt", "DATA" ) + settings = util.JSONToTable( settings ) + + if not settings.modules or not settings.cfg then + ErrorNoHalt("Your custom settings have not been loaded because you have a misconfigured settings file! The default settings were used instead!") + return + end + + local removedSetting = {} + + for k, v in next, settings.modules do + if defaultSettings.modules[k] == nil then + settings.modules[k] = nil + table.insert(removedSetting, k) + end + end + + for k, v in next, settings.cfg do + if defaultSettings.cfg[k] == nil then + settings.cfg[k] = nil + table.insert(removedSetting, k) + end + end + + if next(removedSetting) then + print("[APG] Settings File Updated. (Conflicts Resolved)") + print("[APG] The Following Settings Have Been Removed: ") + for _,v in next, removedSetting do + print("\t> \"" .. tostring(v) .. "\" has been removed.") + end + + removedSetting = nil + file.Write("apg/settings.txt", util.TableToJSON(settings)) + end + + table.Merge( APG, settings ) +else + table.Merge( APG, defaultSettings ) +end diff --git a/addons/apg/lua/apg/sv_apg.lua b/addons/apg/lua/apg/sv_apg.lua new file mode 100644 index 0000000..42ccf52 --- /dev/null +++ b/addons/apg/lua/apg/sv_apg.lua @@ -0,0 +1,420 @@ +util.AddNetworkString("apg_notice_s2c") +APG = APG or {} + +local IsValid = IsValid +local table = table +local isentity = isentity + +--[[ ENTITY Related ]] + + --[[ + Check if the player can pick up the entity + @param {entity} ent + @param {player} ply + @returns {boolean} + ]] + +function APG.canPhysGun( ent, ply ) + -- Predict if the player can pickup an entity. + if not IsValid(ent) then return false end -- The entity isn't valid, don't pickup. + + if ent:GetPersistent() then + return false + end + + if ent.PhysgunDisabled then + return false + end -- Check if the entity is physgun disabled. + + ent.APG_HeldBy = ent.APG_HeldBy or {} + ent.APG_HeldBy.plys = ent.APG_HeldBy.plys or {} + + if ply.APG_CantPickup == true or next( ent.APG_HeldBy.plys ) then + ply:ConCommand("-attack") -- Tell the player to stop physgunning. + return false + end -- Is APG blocking the pickup? + + if ent.CPPICanPhysgun then + return ent:CPPICanPhysgun(ply) + end -- Let CPPI handle things from here. + + return false -- If everything fails we probably shouldn't be picking this up. +end + +function APG.isWhitelistedEnt( ent ) + + local class = ent:GetClass() + for k, v in pairs (APG.cfg["unGhostingWhitelist"].value) do + if ( v and k == class ) or (not v and string.find( class, k ) ) then + return true + end + end + + return false +end + + --[[ + Check if the entity is a bad entity, as defined in badEnts + @param {entity} ent + @returns {boolean} + ]] + +function APG.isBadEnt( ent ) + if ent and not ent.GetClass then return false end -- Ignore if we can't read the class. + if not IsValid(ent) then return false end -- Ignore invalid entities. + if ent.jailWall == true then return false end -- Ignore ULX jails. + if Entity(0) == ent or ent:IsWorld() then return false end -- Ignore worldspawn. + if ent:IsWeapon() then return false end -- Ignore weapons. + if ent:IsPlayer() then return false end -- Ignore players. + if ent:IsNPC() then return false end + if ent.ARCBank_MapEntity then return false end --Ignore ARCBank ents + + local h = hook.Run("APGisBadEnt", ent) + if isbool(h) then return h end + + local class = ent:GetClass() + for k, v in pairs (APG.cfg["badEnts"].value) do + if ( v and k == class ) or (not v and string.find( class, k ) ) then + return true + end + end + + return false +end + + --[[ + Check the props owner + @return {player} or nil + ]] + +function APG.getOwner( ent ) + local owner, _ = ent:CPPIGetOwner() or ent.FPPOwner or nil + return owner +end + + --[[ + Add's a timer to the module table + @param {string} module + @param {string} identifier + @param {number} delay + @param {number} repetitions + @param {function} function + @void + ]] + +local function killVel(phys, freeze) + local vec = Vector() + if not IsValid(phys) then return end + if freeze then phys:EnableMotion(false) return end + + phys:SetVelocity(vec) + phys:SetVelocityInstantaneous(vec) + phys:AddAngleVelocity(phys:GetAngleVelocity() * -1) + + phys:Sleep() +end + +function APG.killVelocity(ent, extend, freeze, wake_target) + local vec = Vector() + if ent.GetClass and ent:GetClass() == "player" then ent:SetVelocity(ent:GetVelocity() * -1) return end + ent:SetVelocity(vec) + + for i = 0, ent:GetPhysicsObjectCount() do killVel(ent:GetPhysicsObjectNum(i), freeze) end -- Includes self? + + if extend then + for _,v in next, constraint.GetAllConstrainedEntities(ent) do killVel(v:GetPhysicsObject(), freeze) end + end + + if wake_target then + timer.Simple(0, function() + if not IsValid(ent) then return end + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + phys:Wake() + end + end) + end +end + +function APG.freezeIt( ent, extend ) + local obj = ent:GetPhysicsObject() + if extend then + for _,v in next, constraint.GetAllConstrainedEntities(ent) do + killVel(v:GetPhysicsObject(), true) + v.APG_Frozen = true + end + else + if IsValid(obj) then + killVel(obj, true) + ent.APG_Frozen = true + end + end +end + +function APG.FindWAC(ent) + if not APG.cfg["vehIncludeWAC"].value then return false end + + local e + local i = 0 + if ent.wac_seatswitch or ent.wac_ignore then return true end + for _,v in next, constraint.GetAllConstrainedEntities(ent) do + if v.wac_seatswitch or v.wac_ignore then e = v break end + if i > 12 then break end -- Only check up to 12. + i = i + 1 + end + + return IsValid(e) +end + +function APG.IsVehicle(v, basic) + if not IsValid(v) then return false end + + if v:IsVehicle() then return true end + if string.find(v:GetClass(), "vehicle") then return true end + if basic then return false end + + if APG.FindWAC(v) then return true end + + local parent = v:GetParent() + return APG.IsVehicle(parent, true) +end + +function APG.cleanUp( mode, notify, specific ) + mode = mode or "unfrozen" + for _, v in next, specific or ents.GetAll() do + APG.killVelocity(v,false) + if not APG.isBadEnt(v) or not APG.getOwner( v ) or APG.IsVehicle(v) then continue end + if mode == "unfrozen" and v.APG_Frozen then -- Whether to clean only not frozen ents or all ents + continue + else + v:Remove() + end + end + + if notify or APG.cfg["notifyLagFunc"].value then + APG.notify( false, 2, "all", APG.cfg["notifyLevel"].value, "Cleaned up (mode: ", mode, ")" ) + end +end + +function APG.ghostThemAll( notify, callback ) + if not APG.modules[ "ghosting" ] then + return APG.notify( false, 0, "admins", "[APG] Warning: Tried to ghost props but ghosting is disabled!" ) + end + for _, v in next, ents.GetAll() do + if ( not APG.isBadEnt(v) ) or ( not APG.getOwner( v ) ) or APG.IsVehicle(v) or v.APG_Frozen then continue end + APG.entGhost( v, true, false ) + end + + if notify or APG.cfg["notifyLagFunc"].value then + APG.notify( false, APG.cfg["notifyLevel"].value, "admins", "Some unfrozen entities were ghosted!" ) + end + + if isfunction(callback) then + callback() + end +end + +function APG.freezeProps( notify, callback ) + for _, v in next, ents.GetAll() do + if not APG.isBadEnt(v) or not APG.getOwner( v ) then continue end + APG.freezeIt( v ) + end + + if notify or APG.cfg["notifyLagFunc"].value then + APG.notify(false, APG.cfg["notifyLevel"].value, "all", "Some entities were frozen!") + end + + if isfunction(callback) then + callback() + end +end + +local function GetPhysenv() + local env = physenv.GetPerformanceSettings() + local con = {} + local vars = { + "phys_upimpactforcescale", + "phys_impactforcescale", + "phys_pushscale", + "sv_turbophysics", + } + + for _,v in next, vars do + local var = GetConVar(v) + con[v] = var and var:GetString() or nil + end + + return {con = con, env = env} +end + +function APG.smartCleanup( notify, callback ) + local defaults = GetPhysenv() + local phys = table.Copy(defaults.env) + + hook.Add("PlayerSpawnObject", "APG_smartCleanup", function() return false end) + + RunConsoleCommand("phys_upimpactforcescale","0") + RunConsoleCommand("phys_impactforcescale", "0") + RunConsoleCommand("phys_pushscale", "0") + RunConsoleCommand("sv_turbophysics", "1") + + phys.MaxCollisionChecksPerTimestep = 0 + phys.MaxAngularVelocity = 0 + phys.MaxVelocity = 0 + physenv.SetPerformanceSettings(phys) + + local sphere = ents.FindInSphere + local all = ents.GetAll() + local bad = {} + + for _, v in next, all do + if IsValid(v) and v.GetPhysicsObject then + local phys = v:GetPhysicsObject() + if IsValid(phys) and phys:IsMotionEnabled() then + if v.isFadingDoor and APG.isBadEnt(ent) then + SafeRemoveEntity(v) + else + table.insert(bad, {ent = v, phys = phys}) + end + end + end + end + + APG.freezeProps() + + for _, v in next, bad do + local count = 0 + + local owner = APG.getOwner(v.ent) + local space = sphere(v.ent:GetPos(), 7) + local cache = {} + + for _, ent in next, space do + if owner == APG.getOwner(ent) then + count = count + 1 + table.insert(cache, ent) + end + end + + if count > 4 then + for _, ent in next, cache do + if APG.isBadEnt(ent) then + SafeRemoveEntity(ent) + end + end + end + end + + timer.Simple(1.5, function() -- Give a few seconds for the engine to catch up. + for k,v in next, defaults.con do + RunConsoleCommand(k, v) + end + + physenv.SetPerformanceSettings(defaults.env) + hook.Remove("PlayerSpawnObject", "APG_smartCleanup") + + if isfunction(callback) then + callback() + end + end) +end + +function APG.ForcePlayerDrop(ply, ent) + if IsValid(ply) then + ply:ConCommand("-attack") + end + if IsValid(ent) then + DropEntityIfHeld( ent ) + ent:ForcePlayerDrop() + end +end + +function APG.blockPickup( ply ) + if not IsValid(ply) or ply.APG_CantPickup then return end + ply.APG_CantPickup = true + timer.Simple(10, function() + if IsValid(ply) then + ply.APG_CantPickup = false + end + end) +end + +--[[-------------------- + Set when a prop is unfrozen. +]]---------------------- +hook.Add("PlayerUnfrozeObject", "APG_PlayerUnfrozeObject", function(ply, ent, object) + if not APG.isBadEnt( ent ) then return end + ent.APG_Frozen = false +end) + +--[[-------------------- + Physgun Drop & Freeze +]]---------------------- +hook.Add( "OnPhysgunFreeze", "APG_OnPhysgunFreeze", function( weap, phys, ent, ply ) + if not APG.isBadEnt( ent ) then return end + ent.APG_Frozen = true +end) + +--[[-------------------- + APG job manager +--]]---------------------- +local toProcess = toProcess or {} + +function APG.dJobRegister( job, delay, limit, func, onBegin, onEnd ) + local tab = { + content = {}, + delay = delay, + limit = limit, + func = func, + onBegin = onBegin or nil, + onEnd = onEnd or nil + } + toProcess[job] = tab +end + +local function APG_delayedTick( job ) + if toProcess[job].processing and toProcess[job].processing == true then return end + toProcess[job].processing = true + if toProcess[job].onBegin then toProcess[job].onBegin() end + local delay, pLimit = toProcess[job].delay, toProcess[job].limit + local total = #toProcess[job].content + local count = math.Clamp(total,0,pLimit) + for i = 1, count do + local cur = toProcess[job].content[1] + timer.Create( "delay_" .. job .. "_" .. i , ( i - 1 ) * delay , 1, function() + toProcess[job].func( cur ) + end) + table.remove(toProcess[job].content, 1) + end + timer.Create("dJob_" .. job .. "_process", ( count * delay ) + 0.1 , 1, function() toProcess[job].processing = false + if #toProcess[job].content < 1 and toProcess[job].onEnd then toProcess[job].onEnd() end + end) +end + +function APG.startDJob( job, content ) + if not job or not isstring(job) or not content then return end + if not toProcess or not toProcess[job] then + ErrorNoHalt("[APG] No Process Found, Attempting Reload!\n---\nThis Shouldn't Happen Concider Restarting!\n") + APG.reload() + return + end + + if table.HasValue(toProcess[job].content, content) then return end + + -- Is it a problem if there is a same ent being unghosted twice ? + table.insert( toProcess[job].content, content ) + hook.Add("Tick", "APG_delayed_" .. job, function() + if #toProcess[job].content > 0 then + APG_delayedTick( job ) + else + hook.Remove("Tick", "APG_delayed_" .. job) + end + end) +end + +hook.Add("InitPostEntity", "APG_Load", function() + hook.Add("Think", "APG_Load", function() + APG.initialize() + hook.Remove("Think", "APG_Load") + end) +end) diff --git a/addons/apg/lua/apg/sv_menu.lua b/addons/apg/lua/apg/sv_menu.lua new file mode 100644 index 0000000..ac862c1 --- /dev/null +++ b/addons/apg/lua/apg/sv_menu.lua @@ -0,0 +1,150 @@ +util.AddNetworkString("apg_settings_c2s") +util.AddNetworkString("apg_menu_s2c") +util.AddNetworkString("apg_context_c2s") + +local function saveSettings( json ) + if not file.Exists("apg", "DATA") then file.CreateDir( "apg" ) end + file.Write("apg/settings.txt", json) +end + +local function recSettings( len, ply) + if not ply:IsSuperAdmin() then return end + + len = net.ReadUInt( 32 ) + if len == 0 then return end + + local settings = net.ReadData( len ) + settings = util.Decompress( settings ) + + saveSettings( settings ) + + settings = util.JSONToTable( settings ) + APG.cfg = settings.cfg + table.Merge(APG, settings) + APG.reload() +end +net.Receive( "apg_settings_c2s", recSettings) + +local function sendToClient( ply ) +local settings = {} + settings.cfg = APG.cfg or {} + settings.modules = APG.modules or {} + + settings = util.TableToJSON( settings ) + settings = util.Compress( settings ) + net.Start("apg_menu_s2c") + net.WriteUInt( settings:len(), 32 ) -- Write the length of the data + net.WriteData( settings, settings:len() ) -- Write the data + net.Send(ply) +end + +hook.Add( "PlayerSay", "openAPGmenu", function( ply, text, public ) + text = string.lower( text ) + if ply:IsSuperAdmin() and text == "!apg" then + sendToClient( ply ) + return "" + end +end) + +local function checkOwner(owner, ply) + if ( IsValid(owner) and owner:IsPlayer() ) then + return true + else + APG.notification("The owner of this entity is NOT a Player. (Owner: " .. type(owner) .. ")", ply) + return false + end +end + + +-- TODO: Revamp this, really don't like how it looks, would rather have a function for each +-- it's too clustered. + +local function contextCMD(_,ply) + if not ply:IsSuperAdmin() then return end + + local cmd = net.ReadString() + local ent = net.ReadEntity() + + ent = IsValid(ent) and ent or ply:GetEyeTraceNoCursor().Entity or nil + + local class = IsValid(ent) and ent.GetClass and ent:GetClass() or nil + if not class then return end + + local owner = APG.getOwner(ent) + + if cmd == "addghost" then + if not APG.cfg.badEnts.value[class] then + APG.cfg.badEnts.value[class] = true + APG.notify( false, 0, ply, "\"", class, "\" added to Ghost List!" ) + else + APG.notify( false, 0, ply, "This class is already listed!" ) + end + elseif cmd == "remghost" then + APG.cfg.badEnts.value[class] = nil + APG.notify( false, 0, ply, "\"", class, "\" removed from Ghost List!" ) + elseif cmd == "clearowner" then + if not checkOwner(owner, ply) then return end + cleanup.CC_Cleanup(owner,"gmod_cleanup",{}) + elseif cmd == "clearunfrozen" then + if not checkOwner(owner, ply) then return end + + local count = 0 + for _,v in next, ents.GetAll() do + if not (IsValid(v) and APG.getOwner(v) == owner) then continue end + if not APG.isBadEnt(v) then continue end + if not v.APG_Frozen then + SafeRemoveEntity(v) + count = count + 1 + end + end + + APG.notify( false, 0, ply, count, "entities have been removed!" ) + elseif cmd == "getownercount" then + if not checkOwner(owner, ply) then return end + + local count = 0 + + for _,v in next, ents.GetAll() do + if IsValid(v) and APG.getOwner(v) == owner then + count = count + 1 + end + end + + APG.notify( false, 0, ply, owner:Nick(), "[", owner:SteamID(), "]", "has", count, (count == 1 and "entity." or "entities.") ) + elseif cmd == "freezeclass" then + local count = 0 + + for _,v in next, ents.FindByClass(class) do + if IsValid(v) and not v.APG_Frozen then + count = count + 1 + APG.killVelocity(v, false, true, false) + end + end + + APG.notify( false, 0, ply, (count or 0), (count == 1 and "Entity" or "Entities"), "Frozen" ) + elseif cmd == "sleepclass" then + local count = 0 + + for _,v in next, ents.FindByClass(class) do + if IsValid(v) and not v.APG_Frozen then + count = count + 1 + APG.killVelocity(v, false, false, false) + end + end + + APG.notify( false, 0, ply, (count or 0), (count == 1 and "Entity is" or "Entities are"), "now Sleeping!" ) + elseif cmd == "ghost" then + APG.entGhost(ent) + APG.notify( false, 0, ply, ent, " was ghosted." ) + end + + if cmd == "addghost" or cmd == "remghost" then + local settings = {} + settings.cfg = APG.cfg or {} + settings.modules = APG.modules or {} + + saveSettings( util.TableToJSON( settings ) ) + APG.reload() + end +end +net.Receive("apg_context_c2s", contextCMD) diff --git a/addons/apg/lua/autorun/client/cl_apg_init.lua b/addons/apg/lua/autorun/client/cl_apg_init.lua new file mode 100644 index 0000000..426de14 --- /dev/null +++ b/addons/apg/lua/autorun/client/cl_apg_init.lua @@ -0,0 +1,3 @@ +APG = {} +include( "apg/sh_config.lua" ) +include( "apg/cl_menu.lua" ) diff --git a/addons/apg/lua/autorun/server/sv_apg_init.lua b/addons/apg/lua/autorun/server/sv_apg_init.lua new file mode 100644 index 0000000..1ac4b81 --- /dev/null +++ b/addons/apg/lua/autorun/server/sv_apg_init.lua @@ -0,0 +1,176 @@ +--[[ INITIALIZE APG ]] +APG = {} +APG.modules = APG.modules or {} + +--[[ Micro Optimization ]] +local timer = timer +local table = table + +--[[ CLIENT related ]] +AddCSLuaFile("apg/sh_config.lua") +AddCSLuaFile("apg/cl_utils.lua") +AddCSLuaFile("apg/cl_menu.lua") + +--[[ REGISTER Modules ]] +local modules, _ = file.Find("apg/modules/*.lua","LUA") +for _,v in next, modules do + if v then + niceName = string.gsub(tostring(v),"%.lua","") + APG.modules[ niceName ] = false + APG[ niceName ] = { hooks = {}, timers = {}} + end +end + + --[[ + Add's a hook to the module table + @param {string} module + @param {string} event + @param {string} identifier + @param {function} function + @void + ]] + + +function APG.hookAdd( module, event, identifier, func ) + table.insert( APG[ module ][ "hooks"], { event = event, identifier = identifier, func = func }) +end + +--[[ + Adds all the hooks that the module needs + @param {string} module + @void +]] + +function APG.updateHooks( module ) + for k, v in next, APG[module]["hooks"] do + hook.Add( v.event, v.identifier, v.func ) + end +end + +--[[ + Add's a timer to the module table + @param {string} module + @param {string} identifier + @param {number} delay + @param {number} repetitions + @param {function} function + @void +]] + +function APG.timerAdd( module, identifier, delay, repetitions, func ) + table.insert( APG[ module ][ "timers"], { identifier = identifier, delay = delay, repetitions = repetitions, func = func } ) +end + +--[[ + Add's a the timers a module needs. + @param {string} module + @void +]] +function APG.updateTimers(module) + for k, v in next, APG[module]["timers"] do + timer.Create( v.identifier, v.delay, v.repetitions, v.func ) + end +end + +--[[ + Load's a APG module + @param {string} module + @void +]] + +function APG.load( module ) + APG.modules[ module ] = true + include( "apg/modules/" .. module .. ".lua" ) + print("[APG] " .. module .. " loaded.") +end + +--[[ + Unload's a APG module + @param {string} module + @void +]] + +function APG.unLoad( module ) + APG.modules[module] = false + + if not (istable(APG[module]) and next(APG[module])) then return end + + local hooks = APG[ module ]["hooks"] + for k, v in next, hooks do + hook.Remove(v.event, v.identifier) + end + + local timers = APG[ module ]["timers"] + for k, v in next, timers do + timer.Remove(v.identifier) + end + + print("[APG] " .. module .. " unloaded.") +end + +function APG.reload() + for k, v in next, APG.modules do + if APG.modules[ k ] == true then + APG.unLoad( k ) + APG.load( k ) + else + APG.unLoad( k ) + end + end +end + +--[[ local settings = {} +function APG.sampleServerSettings() + +end + +function APG.getServerSettings() + +end ]] + +function APG.initialize() + for k, v in next, APG.modules do + if APG.modules[k] == true then + APG.load(k) + end + end +end + +--[[ LOADING ]] +-- Loading config first +include( "apg/sh_config.lua" ) +-- Loading APG main functions +include( "apg/sv_apg.lua") -- Modules loaded at the bottom +-- Loading APG menu +include( "apg/sv_menu.lua" ) + +--[[ CVars INIT ]] + +concommand.Add("apg_set", function( ply, cmd, args, argStr ) + if not ply:IsSuperAdmin() then return end + + if args[1] == "module" then + local _module = APG.modules[ args[2] ] + if _module != nil then + if _module == true then + APG.unLoad( args[2] ) + APG.notification( "[APG] Module " .. args[2] .. " disabled.", ply) + else + APG.load( args[2] ) + APG.notification( "[APG] Module " .. args[2] .. " enabled.", ply) + end + else + APG.notification( "[APG] This module does not exist", ply) + end + + elseif args[1] == "help" then + local cfg = APG.cfg[ args[2] ] + if cfg then + APG.notification( cfg.desc, ply) + else + APG.notification( "[APG] Help: This setting does not exist", ply) + end + else + APG.notification( ply, "Error: unknown setting") + end +end) diff --git a/addons/apg/lua/autorun/sh_apg.lua b/addons/apg/lua/autorun/sh_apg.lua new file mode 100644 index 0000000..ff08b91 --- /dev/null +++ b/addons/apg/lua/autorun/sh_apg.lua @@ -0,0 +1,2 @@ +-- ULX Admin Commands Coming Soon! +--- PLanned Commands for Prop Management, and Server Cleanup. diff --git a/addons/cmenu_darkfated_ultracode/.gitattributes b/addons/cmenu_darkfated_ultracode/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/addons/cmenu_darkfated_ultracode/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/addons/cmenu_darkfated_ultracode/LICENSE b/addons/cmenu_darkfated_ultracode/LICENSE new file mode 100644 index 0000000..e62ec04 --- /dev/null +++ b/addons/cmenu_darkfated_ultracode/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/addons/cmenu_darkfated_ultracode/README.md b/addons/cmenu_darkfated_ultracode/README.md new file mode 100644 index 0000000..5670d7b --- /dev/null +++ b/addons/cmenu_darkfated_ultracode/README.md @@ -0,0 +1,14 @@ +# MoonContextMenu +ContextMenu for Interaction in Garry's Mod + +🔧 [Mantle](https://github.com/darkfated/mantle): in order for the system to work, you need to use this Gmod library + +## Screenshots 📷 +### Appearance +Appearance + +### You can move it +Moving + +### And of course to reset the position using the left mouse button +Reset Btn diff --git a/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/config.lua b/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/config.lua new file mode 100644 index 0000000..5479ac9 --- /dev/null +++ b/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/config.lua @@ -0,0 +1,150 @@ +// Конфиг сделан под ШколуРП. Но можете запроста изменить под DarkRP + +MoonContextMenu.config_cmds = { + { + items = { + { + name = 'Админка', + func = function() + RunConsoleCommand('sam', 'menu') + end, + icon = 'wrench' + }, + { + name = 'Логи', + func = function() + RunConsoleCommand('say', '!plogs') + end, + icon = 'logs' + } + }, + check = function() + return LocalPlayer():getJobTable().command == 'job_admin' + end + }, + { + items = { + { + name = 'Выкинуть оружие', + func = function() + RunConsoleCommand('darkrp', 'dropweapon') + end, + icon = 'gun' + }, + { + name = 'Купить патроны', + func = function() + if not LocalPlayer():GetActiveWeapon().Primary then return end + RunConsoleCommand("darkrp", "buyammo", string.lower(LocalPlayer():GetActiveWeapon().Primary.Ammo)) + end, + icon = 'gun' + }, + { + name = 'Выбросить деньги', + func = function() + Mantle.ui.text_box('Выбросить деньги', 'Сколько желаете?', function(s) + RunConsoleCommand('darkrp', 'dropmoney', s) + end) + end, + icon = 'drop_money' + }, + { + name = 'Передать игроку деньги', + func = function() + Mantle.ui.text_box('Передать деньги', 'Сколько желаете?', function(s) + RunConsoleCommand('darkrp', 'give', s) + end) + end, + icon = 'give_money' + }, + { + name = 'Сменить ник', + func = function() + Mantle.ui.text_box('Сменить ник', 'Какой хотите поставить?', function(s) + RunConsoleCommand('darkrp', 'rpname', s) + end) + end, + icon = 'nick' + }, + { + name = 'Продать все двери', + func = function() + RunConsoleCommand('darkrp', 'unownalldoors') + end, + icon = 'doors' + }, + { + name = 'Написать объявление', + func = function() + Mantle.ui.text_box('Написать объявление', 'Что планируете рекламировать?', function(s) + RunConsoleCommand('say', '/advert ' .. s) + end) + end, + icon = 'advert' + }, + { + name = 'Кинуть ролл', + func = function() + RunConsoleCommand('say', '/roll') + end, + icon = 'roll' + } + } + }, + { + items = { + { + name = 'Назначить розыск', + func = function() + Mantle.ui.player_selector(function(pl) + Mantle.ui.text_box('Назначить розыск', 'Какова причина?', function(s) + RunConsoleCommand('darkrp', 'wanted', pl:Name(), s) + end) + end) + end, + icon = 'wanted' + }, + { + name = 'Снять розыск', + func = function() + Mantle.ui.player_selector(function(pl) + RunConsoleCommand('darkrp', 'unwanted', pl:Name()) + end) + end, + icon = 'unwanted' + }, + { + name = 'Получить ордер', + func = function() + Mantle.ui.player_selector(function(pl) + Mantle.ui.text_box('Получить ордер', 'Какова причина?', function(s) + RunConsoleCommand('darkrp', 'warrant', pl:Name(), s) + end) + end) + end, + icon = 'warrant' + }, + }, + check = function() + return LocalPlayer():getJobTable().adult + end + }, + { + items = { + -- { + -- name = 'Настройки 3 лица', + -- func = function() + -- RunConsoleCommand('say','!third') + -- end, + -- icon = 'thirdperson_toggle' + -- }, + { + name = 'Остановить все звуки', + func = function() + RunConsoleCommand('stopsound') + end, + icon = 'sounds' + } + } + } +} diff --git a/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/init.lua b/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/init.lua new file mode 100644 index 0000000..0743a57 --- /dev/null +++ b/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/init.lua @@ -0,0 +1,29 @@ +--[[ + * MoonContextmenu * + GitHub: https://github.com/darkfated/mooncontextmenu + Author's discord: darkfated +]] + +local function run_scripts() + Mantle.run_cl('config.lua') + Mantle.run_cl('menu.lua') +end + +local function init() + if SERVER then + local folderPath = 'materials/mooncontextmenu' + local files = file.Find(folderPath .. '/*.png', 'GAME') + + for _, fileName in ipairs(files) do + local filePath = folderPath .. '/' .. fileName + + resource.AddFile(filePath) + end + end + + MoonContextMenu = MoonContextMenu or {} + + run_scripts() +end + +init() diff --git a/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/menu.lua b/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/menu.lua new file mode 100644 index 0000000..3cb6693 --- /dev/null +++ b/addons/cmenu_darkfated_ultracode/lua/mantle_addons/mooncontextmenu/menu.lua @@ -0,0 +1,85 @@ +local scrw, scrh = ScrW(), ScrH() + +local function Create() + MoonContextMenu.menu = vgui.Create('DFrame') + Mantle.ui.frame(MoonContextMenu.menu, '', 300, 500, false) + + if MoonContextMenu.pos_save then + MoonContextMenu.menu:SetPos(MoonContextMenu.pos_save[1], MoonContextMenu.pos_save[2]) + else + MoonContextMenu.menu:SetPos(10, 0) + MoonContextMenu.menu:CenterVertical() + end + + MoonContextMenu.menu:MakePopup() + MoonContextMenu.menu.center_title = 'Список команд' + + MoonContextMenu.menu.sp = vgui.Create('DScrollPanel', MoonContextMenu.menu) + Mantle.ui.sp(MoonContextMenu.menu.sp) + MoonContextMenu.menu.sp:Dock(FILL) + + for _, cat in ipairs(MoonContextMenu.config_cmds) do + if cat.check and !cat.check() then + continue + end + + for _, cmd in ipairs(cat.items) do + local btn_cmd = vgui.Create('DButton', MoonContextMenu.menu.sp) + Mantle.ui.btn(btn_cmd) + btn_cmd:Dock(TOP) + btn_cmd:DockMargin(0, 0, 0, 4) + btn_cmd:SetTall(24) + btn_cmd:SetText(cmd.name) + btn_cmd.DoClick = function() + Mantle.func.sound() + + cmd.func() + end + btn_cmd.DoRightClick = function() + local DM = Mantle.ui.derma_menu() + DM:AddOption('Сбросить позицию', function() + MoonContextMenu.pos_save = nil + MoonContextMenu.menu:Remove() + end, 'icon16/arrow_out.png') + end + + if cmd.icon then + btn_cmd.mat = Material('materials/mooncontextmenu/' .. cmd.icon .. '.png') + + btn_cmd.PaintOver = function(self, w, h) + surface.SetDrawColor(color_white) + surface.SetMaterial(self.mat) + surface.DrawTexturedRect(4, 4, 16, 16) + end + end + end + + local panel_split = vgui.Create('DPanel', MoonContextMenu.menu.sp) + panel_split:Dock(TOP) + panel_split:DockMargin(0, 0, 0, 4) + panel_split:SetTall(8) + panel_split.Paint = function(_, w, h) + draw.RoundedBox(4, 0, 0, w, h, Mantle.color.panel_alpha[2]) + end + end +end + +local function Close() + if !IsValid(MoonContextMenu.menu) then + return + end + + MoonContextMenu.pos_save = {} + MoonContextMenu.pos_save[1], MoonContextMenu.pos_save[2] = MoonContextMenu.menu:GetPos() + MoonContextMenu.menu:Remove() +end + +hook.Add('OnContextMenuOpen', 'Mantle.MoonContextMenu', function() + if !IsValid(MoonContextMenu.menu) then + Create() + end +end) + +hook.Add('OnContextMenuClose', 'Mantle.MoonContextMenu', function() + Close() +end) diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/advert.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/advert.png new file mode 100644 index 0000000..0cdee03 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/advert.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/camera.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/camera.png new file mode 100644 index 0000000..4f51d82 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/camera.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/doors.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/doors.png new file mode 100644 index 0000000..c9934bf Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/doors.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/drop_money.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/drop_money.png new file mode 100644 index 0000000..6a07abe Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/drop_money.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/gangs.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/gangs.png new file mode 100644 index 0000000..71490b0 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/gangs.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/give_money.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/give_money.png new file mode 100644 index 0000000..c31e0b5 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/give_money.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/gun.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/gun.png new file mode 100644 index 0000000..930e364 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/gun.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/help.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/help.png new file mode 100644 index 0000000..81f220b Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/help.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/logs.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/logs.png new file mode 100644 index 0000000..75d5b67 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/logs.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/nick.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/nick.png new file mode 100644 index 0000000..72c49b5 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/nick.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/roll.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/roll.png new file mode 100644 index 0000000..553267e Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/roll.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/schedule.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/schedule.png new file mode 100644 index 0000000..fbd3e58 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/schedule.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/sounds.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/sounds.png new file mode 100644 index 0000000..2ca7f53 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/sounds.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/thirdperson_toggle.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/thirdperson_toggle.png new file mode 100644 index 0000000..e640639 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/thirdperson_toggle.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/unwanted.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/unwanted.png new file mode 100644 index 0000000..e40d01a Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/unwanted.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/wanted.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/wanted.png new file mode 100644 index 0000000..40a09d8 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/wanted.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/warrant.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/warrant.png new file mode 100644 index 0000000..b9d7b35 Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/warrant.png differ diff --git a/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/wrench.png b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/wrench.png new file mode 100644 index 0000000..641806b Binary files /dev/null and b/addons/cmenu_darkfated_ultracode/materials/mooncontextmenu/wrench.png differ diff --git a/addons/f4/lua/autorun/server/resource.lua b/addons/f4/lua/autorun/server/resource.lua new file mode 100644 index 0000000..a9d6db1 --- /dev/null +++ b/addons/f4/lua/autorun/server/resource.lua @@ -0,0 +1,34 @@ +/*---------------------------------------------------------------------- +Leak by Famouse (P.s. Я это не вырезаю для атмосферы ахуенной сервера хуй знает какого года) + +Play good games:↓ +http://store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +www.youtube.com/c/Famouse + +More leaks in the discord:↓ +https://discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +resource.AddFile("materials/icons/back.png") +resource.AddFile("materials/icons/close.png") +resource.AddFile("materials/icons/commands.png") +resource.AddFile("materials/icons/database.png") +resource.AddFile("materials/icons/donate.png") +resource.AddFile("materials/icons/home.png") +resource.AddFile("materials/icons/info.png") +resource.AddFile("materials/icons/jobs.png") +resource.AddFile("materials/icons/menu.png") +resource.AddFile("materials/icons/shop.png") +resource.AddFile("materials/icons/site.png") +resource.AddFile("materials/icons/user.png") + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ diff --git a/addons/f4/lua/autorun/smooth-f4menu.lua b/addons/f4/lua/autorun/smooth-f4menu.lua new file mode 100644 index 0000000..ee49e83 --- /dev/null +++ b/addons/f4/lua/autorun/smooth-f4menu.lua @@ -0,0 +1,51 @@ +/*---------------------------------------------------------------------- +Leak by Famouse (P.s. Я это не вырезаю для атмосферы ахуенной сервера хуй знает какого года) + +Play good games:↓ +store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +https://www.youtube.com/c/Famouse + +More leaks in the discord:↓ +https://discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +if CLIENT then + -- CLIENT MENU + include("smooth-f4menu/client/cl_smooth-f4menu.lua") + include("smooth-f4menu/client/cl_smooth-f4menu-commands.lua") + include("smooth-f4menu/client/cl_smooth-f4menu-jobs.lua") + include("smooth-f4menu/client/cl_smooth-f4menu-title.lua") + include("smooth-f4menu/client/cl_smooth-f4menu-shop.lua") + include("smooth-f4menu/client/cl_smooth-f4menu-site.lua") + + -- CFG + include("smooth-f4menu/cfg_smooth-f4menu.lua") +end + +if SERVER then + -- SERVER MENU + include("smooth-f4menu/server/sv_smooth-f4menu.lua") + + -- CLIENT MENU + AddCSLuaFile("smooth-f4menu/client/cl_smooth-f4menu.lua") + AddCSLuaFile("smooth-f4menu/client/cl_smooth-f4menu-commands.lua") + AddCSLuaFile("smooth-f4menu/client/cl_smooth-f4menu-jobs.lua") + AddCSLuaFile("smooth-f4menu/client/cl_smooth-f4menu-title.lua") + AddCSLuaFile("smooth-f4menu/client/cl_smooth-f4menu-shop.lua") + AddCSLuaFile("smooth-f4menu/client/cl_smooth-f4menu-site.lua") + + -- CFG + include("smooth-f4menu/cfg_smooth-f4menu.lua") + AddCSLuaFile("smooth-f4menu/cfg_smooth-f4menu.lua") +end + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ diff --git a/addons/f4/lua/smooth-f4menu/cfg_smooth-f4menu.lua b/addons/f4/lua/smooth-f4menu/cfg_smooth-f4menu.lua new file mode 100644 index 0000000..f0e3c67 --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/cfg_smooth-f4menu.lua @@ -0,0 +1,135 @@ +/*---------------------------------------------------------------------- +Leak by Famouse (P.s. Я это не вырезаю для атмосферы ахуенной сервера хуй знает какого года) + +Play good games:↓ +store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +https://www.youtube.com/c/Famouse + +More leaks in the discord:↓ +discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +-- TITLE NAME SERVER +SmoothF4MenuFrame_NameServer = "Мертвая Мама Кост Ича RP | Сенвай украл сапы" + +-- LANGUAGE MENU +SmoothF4MenuFrame_NameServer_Home = "Домой" +SmoothF4MenuFrame_NameServer_Jobs = "Работы" +SmoothF4MenuFrame_NameServer_Shop = "Магазин" +SmoothF4MenuFrame_NameServer_Command = "Команды" +SmoothF4MenuFrame_NameServer_Site = "Вики" +SmoothF4MenuFrame_DescriptionJobs = "Описание" +SmoothF4MenuFrame_DescriptionSalary = "Зарплата" +SmoothF4MenuFrame_Entities = "Предметы" +SmoothF4MenuFrame_Weapon = "Оружия" +SmoothF4MenuFrame_Shipments = "Ящики" +SmoothF4MenuFrame_Ammo = "Патроны" +SmoothF4MenuFrame_TMoney = "Деньги" +SmoothF4MenuFrame_Group = "Группа" +SmoothF4MenuFrame_OnlinePlayer = "Онлайн игроков" +SmoothF4MenuFrame_TStaffOnline = "Админы онлайн" +SmoothF4MenuFrame_RulesTitle = "Правила" +SmoothF4MenuFrame_Loading = "Загрузка..." +SmoothF4MenuFrame_ErrorVip = "Ты не имеешь прав!" + +-- MONEY +SmoothF4MenuFrame_Money = "$" + +-- ENABLE WEAPON ? +SmoothF4MenuFrame_EnableWeapon = true + +-- ENABLE SHIPMENTS ? +SmoothF4MenuFrame_EnableShipments = true + +-- ENABLE AMMO ? +SmoothF4MenuFrame_EnableAmmo = true + +-- SITE URL +SmoothF4MenuFrame_SiteURL = "https://dev.pavetr.ru/server/senwai-mmk/wiki.html" +SmoothF4MenuFrame_DonateURL = "https://forum.pavetr.ru/dbtech-donate/drives/bez-tseli.2/" + +-- STAFF ONLINE +SmoothF4MenuFrame_StaffOnline = { "superadmin", "owner", "vip", "admin" } + +-- RULES +SmoothF4MenuFrame_TRules = { + "Text 1 ...", + "Text 2 ...", + "Text 3 ...", + "Text 4 ...", +} + +-- VIP JOBS,WEAPON,SHIPMENTS,ENTITY +SmoothF4MenuFrame_VipGroup = { "superadmin", "owner", "vip", "admin" } +SmoothF4MenuFrame_VipWeapon = { "superadmin", "owner", "vip", "admin" } +SmoothF4MenuFrame_VipShipments = { "superadmin", "owner", "vip", "admin" } +SmoothF4MenuFrame_VipEntity = { "superadmin", "owner", "vip", "admin" } + + +-- COMMAND +MONEYCMD_BUTTONS = {} +RPCMD_BUTTONS = {} +CPCMD_BUTTONS = {} +MAYORCMD_BUTTONS = {} +Categories = {} +table.insert(Categories, { name = "Деньги", Table = MONEYCMD_BUTTONS }) +table.insert(Categories, { name = "Игрок", Table = RPCMD_BUTTONS }) +table.insert(Categories, { name = "Полиция", Table = CPCMD_BUTTONS }) +table.insert(Categories, { name = "Мэр", Table = MAYORCMD_BUTTONS }) +local function MenuAddMButton(n, f) + table.insert(MONEYCMD_BUTTONS, { NAME = n, FUNC = f }) +end +local function MenuAddRPButton(n, f) + table.insert(RPCMD_BUTTONS, { NAME = n, FUNC = f }) +end +local function MenuAddCPButton(n, f) + table.insert(CPCMD_BUTTONS, { NAME = n, FUNC = f }) +end +local function MenuAddMayorButton(n, f) + table.insert(MAYORCMD_BUTTONS, { NAME = n, FUNC = f }) +end +MenuAddMButton("Дать денег игроку", function() OpenTextBox("Дать денег", "Сколько?", "/give") end) +MenuAddMButton("Скинуть деньги", function() OpenTextBox("Скинуть деньги", "Сколько?", "/moneydrop") end) +MenuAddRPButton("Выбросить оружие", function() RunConsoleCommand("say", "/dropweapon") end) +MenuAddRPButton("Запросить лицензию", function() RunConsoleCommand("say", "/requestlicense") end) +MenuAddRPButton("Уволить игрока", + function() OpenPlyReasonBox("Уволить игрока", "Какого игрока вы хотите уволить?", "Причина увольнения?", "/demote") end) +MenuAddRPButton("Продать все двери", function() RunConsoleCommand("say", "/unownalldoors") end) + +MenuAddCPButton("Подать игрока в розыск", + function() + OpenPlyReasonBox("Подать игрока в розыск", "Какого игрока вы хотите подать в розыск?", "Причина розыска?", + "/wanted") + end) +MenuAddCPButton("Снять с игрока розыск", + function() OpenPlyBox("Снять с игрока розыск", "С какого игрока вы хотите снять розыск?", "/unwanted") end) +MenuAddCPButton("Ордер на обыск игрока", + function() OpenPlyReasonBox("Ордер на обыск игрока", "На кого вы хотите подать ордер?", "Причина ордера?", "/warrant") end) + +MenuAddMayorButton("Розыскивать игрока", + function() + OpenPlyReasonBox("Розыскивать игрока", "Какого игрока вы хотите подать в розыск?", "Причина розыска?", + "/wanted") + end) +MenuAddMayorButton("Снять с игрока розыск", + function() OpenPlyBox("Снять с игрока розыск", "С какого игрока вы хотите снять розыск", "/unwanted") end) +MenuAddMayorButton("Ордер на обыск игрока", + function() OpenPlyReasonBox("Ордер на обыск игрока", "На кого вы хотите подать ордер?", "Причина ордера?", "/warrant") end) +MenuAddMayorButton("Дать лицензию", function() RunConsoleCommand("say", "/givelicense") end) +MenuAddMayorButton("Объявить ком.час", function() RunConsoleCommand("say", "/lockdown") end) +MenuAddMayorButton("Завершить ком.час", function() RunConsoleCommand("say", "/unlockdown") end) +MenuAddMayorButton("Добавить правило", + function() OpenTextBox("Добавить правило", "Какое новое правило вы хотите добавить?", "/addlaw") end) +MenuAddMayorButton("Убрать правило", function() OpenTextBox("Убрать правило", "Номер правила?", "/removelaw") end) + + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ diff --git a/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-commands.lua b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-commands.lua new file mode 100644 index 0000000..d34cb48 --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-commands.lua @@ -0,0 +1,149 @@ +/*---------------------------------------------------------------------- +Leak by Famouse + +Play good games:↓ +http://store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +https://www.youtube.com/c/Famouse + +More leaks in the discord:↓ +discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +local PANEL = {} +function PANEL:Init() + + SmoothCommandDPanel = vgui.Create( "DPanel", SmoothF4MenuFrame ) + SmoothCommandDPanel:SetPos( 50, 40 ) + SmoothCommandDPanel:SetSize( 950, 605 ) + SmoothCommandDPanel.Paint = function() end + + SmoothCommandDPanelScroll = vgui.Create( "DPanel", SmoothCommandDPanel ) + SmoothCommandDPanelScroll:SetPos( 0, 0 ) + SmoothCommandDPanelScroll:SetSize( 1000, 605 ) + SmoothCommandDPanelScroll.Paint = function() + draw.RoundedBox( 0, 0, 0, 685, 660 , Color(255,255,255,0) ) + end + + local scroll = vgui.Create("DScrollPanel", SmoothCommandDPanelScroll) + scroll:SetSize(1000, 605) + scroll:SetPos(0, 0) + function scroll:Paint(w, h) + draw.RoundedBox(0, 0, 0, 0, 0, Color(42, 46, 48, 0)) + end + local scrollbar = scroll:GetVBar() + function scrollbar:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(46, 49, 54, 0)) + end + function scrollbar.btnUp:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + function scrollbar.btnDown:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + function scrollbar.btnGrip:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + + SmoothCommandPanelPoxY = 0 + for k,v in pairs(Categories) do + + local CategoriesTitle = vgui.Create("DLabel", scroll) + CategoriesTitle:SizeToContents() + CategoriesTitle:SetPos(5, SmoothCommandPanelPoxY) + CategoriesTitle:SetColor(Color(255,255,255,255)) + CategoriesTitle:SetText( " "..v.name ) + CategoriesTitle:SetWide( SmoothCommandDPanel:GetWide()-10 ) + CategoriesTitle:SetFont( "SmoothF4menuFontButton" ) + CategoriesTitle:SetTall(35) + CategoriesTitle.Paint = function() + draw.RoundedBox( 0, 0, 0, CategoriesTitle:GetWide(), CategoriesTitle:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,CategoriesTitle:GetWide(), CategoriesTitle:GetTall() ) + end + CategoriesTitle.Think = function() CategoriesTitle:SetWide( SmoothCommandDPanel:GetWide()-10 ) end + + SmoothCommandPanelPoxY = SmoothCommandPanelPoxY + 40 + + for d,c in pairs(v.Table) do + + local ButtonComamndClick = vgui.Create("DButton", scroll) + ButtonComamndClick:SetSize( SmoothCommandDPanel:GetWide()-35, 35 ) + ButtonComamndClick:SetPos( 30 , SmoothCommandPanelPoxY ) + ButtonComamndClick:SetColor( Color( 255, 255, 255 )) + ButtonComamndClick:SetFont("SmoothF4menuFontButton") + ButtonComamndClick:SetText(c.NAME) + ButtonComamndClick.Paint = function( ) + draw.RoundedBox( 0, 0, 0, ButtonComamndClick:GetWide(), ButtonComamndClick:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,ButtonComamndClick:GetWide(), ButtonComamndClick:GetTall() ) + if ButtonComamndClick.isHover then + draw.RoundedBox( 0, 0, 0, ButtonComamndClick:GetWide(), ButtonComamndClick:GetTall() , Color(41, 41, 41,70) ) + end + end + ButtonComamndClick.OnCursorEntered = function() + ButtonComamndClick.isHover = true + end + ButtonComamndClick.OnCursorExited = function() + ButtonComamndClick.isHover = false + end + ButtonComamndClick.DoClick = function() + c.FUNC() + end + ButtonComamndClick.Think = function() + ButtonComamndClick:SetSize( SmoothCommandDPanel:GetWide()-35, 35 ) + end + + SmoothCommandPanelPoxY = SmoothCommandPanelPoxY + 40 + + end + end + +end +vgui.Register( "SmoothCommandPanel", PANEL, "Panel" ) + +function OpenTextBox(text1,text2,cmd) + Derma_StringRequest( + text1, + text2, + "", + function( text ) RunConsoleCommand( "say", cmd.." "..text ) end, + function( text ) end + ) +end + +function OpenPlyReasonBox(text1,text2,text3,cmd) + local menu = DermaMenu() + for k,v in pairs(player.GetAll()) do + menu:AddOption(v:Name(),function() + Derma_StringRequest( + text1, + text3, + "", + function( text ) RunConsoleCommand( "say", cmd.." "..v:Name().." "..text ) end, + function( text ) end + ) + end) + end + menu:Open() +end + +function OpenPlyBox(text1,text2,cmd) + local menu = DermaMenu() + for k,v in pairs(player.GetAll()) do + menu:AddOption(v:Name(),function() + RunConsoleCommand( "say", cmd.." "..v:Name() ) + end) + end + menu:Open() +end + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-jobs.lua b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-jobs.lua new file mode 100644 index 0000000..0e883a8 --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-jobs.lua @@ -0,0 +1,237 @@ +/*---------------------------------------------------------------------- +Leak by Famouse + +Play good games:↓ +http://store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +www.youtube.com/c/Famouse + +More leaks in the discord:↓ +https://discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +local PANEL = {} +function PANEL:Init() + + SmoothJobsDPanel = vgui.Create( "DPanel", SmoothF4MenuFrame ) + SmoothJobsDPanel:SetPos( 50, 35 ) + SmoothJobsDPanel:SetSize( 950, 615 ) + SmoothJobsDPanel.Paint = function() end + + SmoothJobsDPanelScroll = vgui.Create( "DPanel", SmoothJobsDPanel ) + SmoothJobsDPanelScroll:SetPos( 0, 0 ) + SmoothJobsDPanelScroll:SetSize( 1000, 615 ) + SmoothJobsDPanelScroll.Paint = function() + draw.RoundedBox( 0, 0, 0, 685, 660 , Color(255,255,255,0) ) + end + + local scroll = vgui.Create("DScrollPanel", SmoothJobsDPanelScroll) + scroll:SetSize(1000, 615) + scroll:SetPos(0, 0) + function scroll:Paint(w, h) + draw.RoundedBox(0, 0, 0, 0, 0, Color(42, 46, 48, 0)) + end + local scrollbar = scroll:GetVBar() + function scrollbar:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(46, 49, 54, 0)) + end + function scrollbar.btnUp:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + function scrollbar.btnDown:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + function scrollbar.btnGrip:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + + SmoothJobsDPanelPosX = 0 + for k, v in pairs( RPExtraTeams ) do + + local colorchange = string.Explode(" ",tostring(v.color)) + + local playerplayjob = 0 + local playerjob = v.command + local maxjobs = v.max + for d,c in pairs(player.GetAll()) do + local jobtableplay = c:getJobTable() + if playerjob == jobtableplay.command then + playerplayjob = playerplayjob + 1 + end + end + + SmoothJobsMain = vgui.Create( "DPanel", scroll ) + SmoothJobsMain:SetPos( 0,SmoothJobsDPanelPosX ) + SmoothJobsMain:SetSize( 950, 60 ) + SmoothJobsMain.Paint = function() + draw.RoundedBox( 0, 0, 0, SmoothJobsMain:GetWide(), SmoothJobsMain:GetTall() , Color(colorchange[1],colorchange[2],colorchange[3],60) ) + draw.SimpleText(v.name,"SmoothF4menuFontButton",75,20,Color(255,255,255)) + draw.RoundedBox( 0,0, 0, 60, 60 , Color(41, 41, 41,70) ) + + draw.SimpleText(playerplayjob.."/"..maxjobs,"SmoothF4menuFontButton",SmoothJobsDPanel:GetWide()-80,20,Color(255,255,255),TEXT_ALIGN_CENTER) + + if v.vip then + draw.SimpleText("[VIP]","SmoothF4menuFontButton",SmoothJobsDPanel:GetWide()-150,20,Color(255,255,255)) + end + end + + if type( v.model ) == "table" then + model = table.Random( v.model ) + else + model = v.model + end + + local jobsModel = vgui.Create( "SpawnIcon", SmoothJobsMain ) + jobsModel:SetSize( 56, 56 ) + jobsModel:SetPos( 5, 2 ) + jobsModel:SetModel( model ) + + local JobsSelect = vgui.Create("DButton", SmoothJobsMain) + JobsSelect:SetSize( 890,60 ) + JobsSelect:SetPos( 60,0 ) + JobsSelect:SetColor( Color( 255, 255, 255 )) + JobsSelect:SetFont("SmoothF4menuFontButton") + JobsSelect:SetText("") + JobsSelect.Paint = function(panel) + if JobsSelect.isHover then + draw.RoundedBox( 0,0, 0, JobsSelect:GetWide(), 60 , Color(41, 41, 41,50) ) + end + end + JobsSelect.OnCursorEntered = function() + JobsSelect.isHover = true + end + JobsSelect.OnCursorExited = function() + JobsSelect.isHover = false + end + JobsSelect.DoClick = function() + if v.vip then + if table.HasValue(SmoothF4MenuFrame_VipGroup,LocalPlayer():GetUserGroup()) then + if v.vote then + RunCmd("/vote"..v.command) + SmoothF4MenuFrame:Close() + else + RunCmd("/"..v.command) + SmoothF4MenuFrame:Close() + end + else + notification.AddLegacy(SmoothF4MenuFrame_ErrorVip,NOTIFY_ERROR,2) + end + else + if v.vote then + RunCmd("/vote"..v.command) + SmoothF4MenuFrame:Close() + else + RunCmd("/"..v.command) + SmoothF4MenuFrame:Close() + end + end + end + + local JobsInfoScresiption = vgui.Create("DButton", SmoothJobsMain) + JobsInfoScresiption:SetSize( 55,60 ) + JobsInfoScresiption:SetPos( SmoothJobsDPanel:GetWide()-55,0 ) + JobsInfoScresiption:SetColor( Color( 255, 255, 255 )) + JobsInfoScresiption:SetFont("SmoothF4menuFontButton") + JobsInfoScresiption:SetText("") + JobsInfoScresiption.Paint = function(panel) + local menuicon = Material( "materials/icons/info.png" ) + surface.SetMaterial( menuicon ) + if JobsInfoScresiption.isHover then + draw.RoundedBox( 0,0, 0, 55, 60 , Color(41, 41, 41,50) ) + end + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(13,15,30,30 ) + end + JobsInfoScresiption.Think = function() + JobsInfoScresiption:SetPos( SmoothJobsDPanel:GetWide()-55,0 ) + end + JobsInfoScresiption.OnCursorEntered = function() + JobsInfoScresiption.isHover = true + end + JobsInfoScresiption.OnCursorExited = function() + JobsInfoScresiption.isHover = false + end + JobsInfoScresiption.DoClick = function() + SmoothJobsDescription(v.description,v.name,v.salary) + end + + SmoothJobsDPanelPosX = SmoothJobsDPanelPosX + 60 + end + +end +vgui.Register( "SmoothJobsPanel", PANEL, "Panel" ) + +function SmoothJobsDescription(textdescription,name,salary) + + SmoothJobsMainDEscription = vgui.Create( "DPanel", SmoothJobsDPanel) + SmoothJobsMainDEscription:SetPos( SmoothJobsDPanel:GetWide(),0 ) + SmoothJobsMainDEscription:SetSize( SmoothJobsDPanel:GetWide(), 615 ) + SmoothJobsMainDEscription.Paint = function() + draw.RoundedBox( 0,0, 0, SmoothJobsDPanel:GetWide(), 615 , Color(41, 41, 41,150) ) + end + SmoothJobsMainDEscription.Think = function() + if IsValid(SmoothJobsMainDEscription) then + SmoothJobsMainDEscription:SetSize( SmoothJobsDPanel:GetWide(), 615 ) + end + end + + local JobsInfoScresiptionCloseDPanel = vgui.Create("DButton", SmoothJobsMainDEscription) + JobsInfoScresiptionCloseDPanel:SetSize( 55,60 ) + JobsInfoScresiptionCloseDPanel:SetPos( 0,0 ) + JobsInfoScresiptionCloseDPanel:SetColor( Color( 255, 255, 255 )) + JobsInfoScresiptionCloseDPanel:SetFont("SmoothF4menuFontButton") + JobsInfoScresiptionCloseDPanel:SetText("") + JobsInfoScresiptionCloseDPanel.Paint = function(panel) + local menuicon = Material( "materials/icons/back.png" ) + surface.SetMaterial( menuicon ) + if JobsInfoScresiptionCloseDPanel.isHover then + surface.SetDrawColor( 200, 200, 200, 255 ) + else + surface.SetDrawColor( 255, 255, 255, 255 ) + end + surface.DrawTexturedRect(15,20,32,32 ) + end + JobsInfoScresiptionCloseDPanel.OnCursorEntered = function() + JobsInfoScresiptionCloseDPanel.isHover = true + end + JobsInfoScresiptionCloseDPanel.OnCursorExited = function() + JobsInfoScresiptionCloseDPanel.isHover = false + end + JobsInfoScresiptionCloseDPanel.DoClick = function() + SmoothJobsMainDEscription:MoveTo(SmoothJobsDPanel:GetWide(),0,0.3,0,-1) + SmoothJobsDPanelScroll:MoveTo(0,0,0.3,0,-1) + timer.Simple(0.3,function() + SmoothJobsMainDEscription:Remove() + end) + end + + local DescriptionJobs = vgui.Create("DTextEntry", SmoothJobsMainDEscription) + DescriptionJobs:SizeToContents() + DescriptionJobs:SetPos(15, 65) + DescriptionJobs:SetTextColor(Color(255,255,255,255)) + DescriptionJobs:SetMultiline( true ) + DescriptionJobs:SetEditable( false ) + DescriptionJobs:SetFont("SmoothF4menuFontButton") + DescriptionJobs:SetWrap( true ) + DescriptionJobs:SetText( SmoothF4MenuFrame_DescriptionJobs..": \n\n"..name.."\n"..SmoothF4MenuFrame_DescriptionSalary..": "..salary..SmoothF4MenuFrame_Money.."\n\n"..textdescription ) + DescriptionJobs:SetDrawBorder(false) + DescriptionJobs:SetDrawBackground(false) + DescriptionJobs:SetSize(SmoothJobsMainDEscription:GetWide()-15,550) + DescriptionJobs.Think = function() + DescriptionJobs:SetSize(SmoothJobsMainDEscription:GetWide()-15,550) + end + + SmoothJobsMainDEscription:MoveTo(0,0,0.3,0,-1) + SmoothJobsDPanelScroll:MoveTo(-1000,0,0.3,0,-1) + +end + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-shop.lua b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-shop.lua new file mode 100644 index 0000000..7f9fc53 --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-shop.lua @@ -0,0 +1,458 @@ +/*---------------------------------------------------------------------- +Leak by Famouse + +Play good games:↓ +store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +https://www.youtube.com/c/Famouse + +More leaks in the discord:↓ +https://discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +local PANEL = {} +function PANEL:Init() + + SmoothShopDPanel = vgui.Create( "DPanel", SmoothF4MenuFrame ) + SmoothShopDPanel:SetPos( 50, 40 ) + SmoothShopDPanel:SetSize( 950, 605 ) + SmoothShopDPanel.Paint = function() end + + SmoothShopDPanelScroll = vgui.Create( "DPanel", SmoothShopDPanel ) + SmoothShopDPanelScroll:SetPos( 0, 0 ) + SmoothShopDPanelScroll:SetSize( 1000, 605 ) + SmoothShopDPanelScroll.Paint = function() + draw.RoundedBox( 0, 0, 0, 685, 660 , Color(255,255,255,0) ) + end + + local scroll = vgui.Create("DScrollPanel", SmoothShopDPanelScroll) + scroll:SetSize(1000, 605) + scroll:SetPos(0, 0) + function scroll:Paint(w, h) + draw.RoundedBox(0, 0, 0, 0, 0, Color(42, 46, 48, 0)) + end + local scrollbar = scroll:GetVBar() + function scrollbar:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(46, 49, 54, 0)) + end + function scrollbar.btnUp:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + function scrollbar.btnDown:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + function scrollbar.btnGrip:Paint(w, h) + draw.RoundedBox(3, 5, 0, 0, 0, Color(36, 39, 44, 0)) + end + + SmoothShopDPanelPosX = 0 + + local EntityTitle = vgui.Create("DLabel", scroll) + EntityTitle:SizeToContents() + EntityTitle:SetPos(5, 0) + EntityTitle:SetColor(Color(255,255,255,255)) + EntityTitle:SetText( " "..SmoothF4MenuFrame_Entities ) + EntityTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) + EntityTitle:SetFont( "SmoothF4menuFontButton" ) + EntityTitle:SetTall(35) + EntityTitle.Paint = function() + draw.RoundedBox( 0, 0, 0, EntityTitle:GetWide(), EntityTitle:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,EntityTitle:GetWide(), EntityTitle:GetTall() ) + end + EntityTitle.Think = function() EntityTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 40 + DarkRPEntitiesCount = 0 + for k,v in pairs(DarkRPEntities) do + local canbuy = false + if v.allowed then + if istable(v.allowed) then + if table.HasValue(v.allowed,LocalPlayer():Team()) then + canbuy = true + end + elseif v.allowed == LocalPlayer():Team() then + canbuy = true + end + else + canbuy = true + end + if canbuy then + DarkRPEntitiesCount = DarkRPEntitiesCount + 1 + + EnitytMain = vgui.Create( "DPanel", scroll ) + EnitytMain:SetPos( 30,SmoothShopDPanelPosX) + EnitytMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + EnitytMain.Paint = function() + draw.RoundedBox( 0, 0, 0, EnitytMain:GetWide(), EnitytMain:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,EnitytMain:GetWide(), EnitytMain:GetTall() ) + + if v.vip then + draw.SimpleText("[VIP]","SmoothF4menuFontButton",EnitytMain:GetWide()-55,15,Color(255,255,255)) + end + end + EnitytMain.Think = function() + if IsValid(EnitytMain) then + EnitytMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + end + end + + local entModel = vgui.Create( "SpawnIcon", EnitytMain ) + entModel:SetSize( 50, 48 ) + entModel:SetPos( 5, 1 ) + entModel:SetModel( v.model ) + + local EnitytItem = vgui.Create("DLabel", EnitytMain) + EnitytItem:SizeToContents() + EnitytItem:SetPos(75, 5) + EnitytItem:SetColor(Color(255,255,255,255)) + EnitytItem:SetText( v.name ) + EnitytItem:SetWide( 500 ) + EnitytItem:SetFont( "SmoothF4menuFontButton" ) + EnitytItem:SetTall(20) + + local EnitytCost = vgui.Create("DLabel", EnitytMain) + EnitytCost:SizeToContents() + EnitytCost:SetPos(75, 26) + EnitytCost:SetColor(Color(255,255,255,255)) + EnitytCost:SetText( SmoothF4MenuFrame_Money..v.price ) + EnitytCost:SetWide( 500 ) + EnitytCost:SetFont( "SmoothF4menuFontButton" ) + EnitytCost:SetTall(20) + + local EnitytBuy = vgui.Create("DButton", EnitytMain) + EnitytBuy:SetSize( EnitytMain:GetWide(), 50 ) + EnitytBuy:SetPos( 0 , 0 ) + EnitytBuy:SetColor( Color( 255, 255, 255 )) + EnitytBuy:SetFont("SmoothF4menuFontButton") + EnitytBuy:SetText("") + EnitytBuy.Paint = function( ) + if EnitytBuy.isHover then + draw.RoundedBox( 0, 0, 0, EnitytMain:GetWide(), EnitytMain:GetTall() , Color(41, 41, 41,70) ) + end + end + EnitytBuy.OnCursorEntered = function() + EnitytBuy.isHover = true + end + EnitytBuy.OnCursorExited = function() + EnitytBuy.isHover = false + end + EnitytBuy.DoClick = function() + if v.vip then + if table.HasValue(SmoothF4MenuFrame_VipEntity,LocalPlayer():GetUserGroup()) then + RunEntCmd(v.cmd) + else + notification.AddLegacy(SmoothF4MenuFrame_ErrorVip,NOTIFY_ERROR,2) + end + else + RunEntCmd(v.cmd) + end + end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 55 + end + end + if DarkRPEntitiesCount == 0 then if IsValid(EntityTitle) then EntityTitle:Remove() SmoothShopDPanelPosX = SmoothShopDPanelPosX - 40 end end + + if SmoothF4MenuFrame_EnableWeapon then + local WeaponTitle = vgui.Create("DLabel", scroll) + WeaponTitle:SizeToContents() + WeaponTitle:SetPos(5, SmoothShopDPanelPosX) + WeaponTitle:SetColor(Color(255,255,255,255)) + WeaponTitle:SetText( " "..SmoothF4MenuFrame_Weapon ) + WeaponTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) + WeaponTitle:SetFont( "SmoothF4menuFontButton" ) + WeaponTitle:SetTall(35) + WeaponTitle.Paint = function() + draw.RoundedBox( 0, 0, 0, WeaponTitle:GetWide(), WeaponTitle:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,WeaponTitle:GetWide(), WeaponTitle:GetTall() ) + end + WeaponTitle.Think = function() WeaponTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 40 + CustomShipmentsCount = 0 + for k,v in pairs(CustomShipments) do + if (v.seperate and (not GAMEMODE.Config.restrictbuypistol or + (GAMEMODE.Config.restrictbuypistol and (not v.allowed[1] or table.HasValue(v.allowed, LocalPlayer():Team()))))) + and (not v.customCheck or v.customCheck and v.customCheck(LocalPlayer())) then + CustomShipmentsCount = CustomShipmentsCount + 1 + + WeaponMain = vgui.Create( "DPanel", scroll ) + WeaponMain:SetPos( 30,SmoothShopDPanelPosX) + WeaponMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + WeaponMain.Paint = function() + draw.RoundedBox( 0, 0, 0, EnitytMain:GetWide(), EnitytMain:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,EnitytMain:GetWide(), EnitytMain:GetTall() ) + + draw.SimpleText("х1","SmoothF4menuFontButton",WeaponMain:GetWide()-44,15,Color(255,255,255)) + + if v.vip then + draw.SimpleText("[VIP]","SmoothF4menuFontButton",EnitytMain:GetWide()-110,15,Color(255,255,255)) + end + end + WeaponMain.Think = function() + if IsValid(WeaponMain) then + WeaponMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + end + end + + local entModel = vgui.Create( "SpawnIcon", WeaponMain ) + entModel:SetSize( 50, 48 ) + entModel:SetPos( 5, 1 ) + entModel:SetModel( v.model ) + + local WeaponItem = vgui.Create("DLabel", WeaponMain) + WeaponItem:SizeToContents() + WeaponItem:SetPos(75, 5) + WeaponItem:SetColor(Color(255,255,255,255)) + WeaponItem:SetText( v.name ) + WeaponItem:SetWide( 250 ) + WeaponItem:SetFont( "SmoothF4menuFontButton" ) + WeaponItem:SetTall(20) + + local WeaponCost = vgui.Create("DLabel", WeaponMain) + WeaponCost:SizeToContents() + WeaponCost:SetPos(75, 26) + WeaponCost:SetColor(Color(255,255,255,255)) + WeaponCost:SetText( SmoothF4MenuFrame_Money..v.pricesep ) + WeaponCost:SetWide( 150 ) + WeaponCost:SetFont( "SmoothF4menuFontButton" ) + WeaponCost:SetTall(20) + + local WeaponBuy = vgui.Create("DButton", WeaponMain) + WeaponBuy:SetSize( WeaponMain:GetWide(), 50 ) + WeaponBuy:SetPos( 0 , 0 ) + WeaponBuy:SetColor( Color( 255, 255, 255 )) + WeaponBuy:SetFont("SmoothF4menuFontButton") + WeaponBuy:SetText("") + WeaponBuy.Paint = function( ) + if WeaponBuy.isHover then + draw.RoundedBox( 0, 0, 0, EnitytMain:GetWide(), EnitytMain:GetTall() , Color(41, 41, 41,70) ) + end + end + WeaponBuy.OnCursorEntered = function() + WeaponBuy.isHover = true + end + WeaponBuy.OnCursorExited = function() + WeaponBuy.isHover = false + end + WeaponBuy.DoClick = function() + if v.vip then + if table.HasValue(SmoothF4MenuFrame_VipWeapon,LocalPlayer():GetUserGroup()) then + RunEntCmd("buy "..v.name) + else + notification.AddLegacy(SmoothF4MenuFrame_ErrorVip,NOTIFY_ERROR,2) + end + else + RunEntCmd("buy "..v.name) + end + end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 55 + end + end + if CustomShipmentsCount == 0 then if IsValid(WeaponTitle) then WeaponTitle:Remove() SmoothShopDPanelPosX = SmoothShopDPanelPosX - 40 end end + end + + if SmoothF4MenuFrame_EnableShipments then + local ShipMentsTitle = vgui.Create("DLabel", scroll) + ShipMentsTitle:SizeToContents() + ShipMentsTitle:SetPos(5, SmoothShopDPanelPosX) + ShipMentsTitle:SetColor(Color(255,255,255,255)) + ShipMentsTitle:SetText( " "..SmoothF4MenuFrame_Shipments ) + ShipMentsTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) + ShipMentsTitle:SetFont( "SmoothF4menuFontButton" ) + ShipMentsTitle:SetTall(35) + ShipMentsTitle.Paint = function() + draw.RoundedBox( 0, 0, 0, ShipMentsTitle:GetWide(), ShipMentsTitle:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,ShipMentsTitle:GetWide(), ShipMentsTitle:GetTall() ) + end + ShipMentsTitle.Think = function() ShipMentsTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 40 + CustomShipmentsShipments = 0 + for k,v in pairs(CustomShipments) do + if !v.noship and table.HasValue(v.allowed, LocalPlayer():Team()) + and (not v.customCheck or (v.customCheck and v.customCheck(LocalPlayer()))) then + CustomShipmentsShipments = CustomShipmentsShipments + 1 + + ShipmentsMain = vgui.Create( "DPanel", scroll ) + ShipmentsMain:SetPos( 30,SmoothShopDPanelPosX) + ShipmentsMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + ShipmentsMain.Paint = function() + draw.RoundedBox( 0, 0, 0, EnitytMain:GetWide(), EnitytMain:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,EnitytMain:GetWide(), EnitytMain:GetTall() ) + + draw.SimpleText("х"..v.amount,"SmoothF4menuFontButton",ShipmentsMain:GetWide()-50,15,Color(255,255,255)) + + if v.vip then + draw.SimpleText("[VIP]","SmoothF4menuFontButton",EnitytMain:GetWide()-110,15,Color(255,255,255)) + end + end + ShipmentsMain.Think = function() + if IsValid(ShipmentsMain) then + ShipmentsMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + end + end + + local entModel = vgui.Create( "SpawnIcon", ShipmentsMain ) + entModel:SetSize( 50, 48 ) + entModel:SetPos( 5, 1 ) + entModel:SetModel( v.model ) + + local ShipmentsItem = vgui.Create("DLabel", ShipmentsMain) + ShipmentsItem:SizeToContents() + ShipmentsItem:SetPos(75, 5) + ShipmentsItem:SetColor(Color(255,255,255,255)) + ShipmentsItem:SetText( v.name ) + ShipmentsItem:SetWide( 250 ) + ShipmentsItem:SetFont( "SmoothF4menuFontButton" ) + ShipmentsItem:SetTall(20) + + local ShipmentsCost = vgui.Create("DLabel", ShipmentsMain) + ShipmentsCost:SizeToContents() + ShipmentsCost:SetPos(75, 26) + ShipmentsCost:SetColor(Color(255,255,255,255)) + ShipmentsCost:SetText( SmoothF4MenuFrame_Money..v.price ) + ShipmentsCost:SetWide( 150 ) + ShipmentsCost:SetFont( "SmoothF4menuFontButton" ) + ShipmentsCost:SetTall(20) + + local ShipmentsBuy = vgui.Create("DButton", ShipmentsMain) + ShipmentsBuy:SetSize( ShipmentsMain:GetWide(), 50 ) + ShipmentsBuy:SetPos( 0 , 0 ) + ShipmentsBuy:SetColor( Color( 255, 255, 255 )) + ShipmentsBuy:SetFont("SmoothF4menuFontButton") + ShipmentsBuy:SetText("") + ShipmentsBuy.Paint = function( ) + if ShipmentsBuy.isHover then + draw.RoundedBox( 0, 0, 0, EnitytMain:GetWide(), EnitytMain:GetTall() , Color(41, 41, 41,70) ) + end + end + ShipmentsBuy.OnCursorEntered = function() + ShipmentsBuy.isHover = true + end + ShipmentsBuy.OnCursorExited = function() + ShipmentsBuy.isHover = false + end + ShipmentsBuy.DoClick = function() + if v.vip then + if table.HasValue(SmoothF4MenuFrame_VipShipments,LocalPlayer():GetUserGroup()) then + RunEntCmd("buyshipment "..v.name) + else + notification.AddLegacy(SmoothF4MenuFrame_ErrorVip,NOTIFY_ERROR,2) + end + else + RunEntCmd("buyshipment "..v.name) + end + end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 55 + end + end + if CustomShipmentsShipments == 0 then if IsValid(ShipMentsTitle) then ShipMentsTitle:Remove() SmoothShopDPanelPosX = SmoothShopDPanelPosX - 40 end end + end + + if SmoothF4MenuFrame_EnableAmmo then + local AmmoTitle = vgui.Create("DLabel", scroll) + AmmoTitle:SizeToContents() + AmmoTitle:SetPos(5, SmoothShopDPanelPosX) + AmmoTitle:SetColor(Color(255,255,255,255)) + AmmoTitle:SetText( " "..SmoothF4MenuFrame_Ammo ) + AmmoTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) + AmmoTitle:SetFont( "SmoothF4menuFontButton" ) + AmmoTitle:SetTall(35) + AmmoTitle.Paint = function() + draw.RoundedBox( 0, 0, 0, AmmoTitle:GetWide(), AmmoTitle:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,AmmoTitle:GetWide(), AmmoTitle:GetTall() ) + end + AmmoTitle.Think = function() AmmoTitle:SetWide( SmoothShopDPanel:GetWide()-10 ) end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 40 + CustomAmmoAmmo = 0 + for k,v in pairs(GAMEMODE.AmmoTypes) do + CustomAmmoAmmo = CustomAmmoAmmo + 1 + + AmmoMain = vgui.Create( "DPanel", scroll ) + AmmoMain:SetPos( 30,SmoothShopDPanelPosX) + AmmoMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + AmmoMain.Paint = function() + draw.RoundedBox( 0, 0, 0, AmmoMain:GetWide(), AmmoMain:GetTall() , Color(41, 41, 41,100) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,AmmoMain:GetWide(), AmmoMain:GetTall() ) + + draw.SimpleText("х"..v.amountGiven,"SmoothF4menuFontButton",AmmoMain:GetWide()-47,15,Color(255,255,255)) + end + AmmoMain.Think = function() + if IsValid(AmmoMain) then + AmmoMain:SetSize( SmoothShopDPanel:GetWide()-35, 50 ) + end + end + + local entModel = vgui.Create( "SpawnIcon", AmmoMain ) + entModel:SetSize( 50, 48 ) + entModel:SetPos( 5, 1 ) + entModel:SetModel( v.model ) + + local AmmoItem = vgui.Create("DLabel", AmmoMain) + AmmoItem:SizeToContents() + AmmoItem:SetPos(75, 5) + AmmoItem:SetColor(Color(255,255,255,255)) + AmmoItem:SetText( v.name ) + AmmoItem:SetWide( 250 ) + AmmoItem:SetFont( "SmoothF4menuFontButton" ) + AmmoItem:SetTall(20) + + local AmmoCost = vgui.Create("DLabel", AmmoMain) + AmmoCost:SizeToContents() + AmmoCost:SetPos(75, 26) + AmmoCost:SetColor(Color(255,255,255,255)) + AmmoCost:SetText( SmoothF4MenuFrame_Money..v.price ) + AmmoCost:SetWide( 150 ) + AmmoCost:SetFont( "SmoothF4menuFontButton" ) + AmmoCost:SetTall(20) + + local AmmoBuy = vgui.Create("DButton", AmmoMain) + AmmoBuy:SetSize( AmmoMain:GetWide(), 50 ) + AmmoBuy:SetPos( 0 , 0 ) + AmmoBuy:SetColor( Color( 255, 255, 255 )) + AmmoBuy:SetFont("SmoothF4menuFontButton") + AmmoBuy:SetText("") + AmmoBuy.Paint = function( ) + if AmmoBuy.isHover then + draw.RoundedBox( 0, 0, 0, AmmoMain:GetWide(), AmmoMain:GetTall() , Color(41, 41, 41,70) ) + end + end + AmmoBuy.OnCursorEntered = function() + AmmoBuy.isHover = true + end + AmmoBuy.OnCursorExited = function() + AmmoBuy.isHover = false + end + AmmoBuy.DoClick = function() + RunEntCmd( "buyammo "..v.ammoType ) + end + + SmoothShopDPanelPosX = SmoothShopDPanelPosX + 55 + end + if CustomAmmoAmmo == 0 then if IsValid(AmmoTitle) then AmmoTitle:Remove() end end + end + +end +vgui.Register( "SmoothShopPanel", PANEL, "Panel" ) + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-site.lua b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-site.lua new file mode 100644 index 0000000..aa70d87 --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-site.lua @@ -0,0 +1,80 @@ +local PANEL = {} +function PANEL:Init() + + SmoothSiteDPanel = vgui.Create( "DPanel", SmoothF4MenuFrame ) + SmoothSiteDPanel:SetPos( 50, 35 ) + SmoothSiteDPanel:SetSize( 950, 615 ) + SmoothSiteDPanel.Paint = function() + draw.SimpleText(SmoothF4MenuFrame_Loading,"SmoothF4menuFontButton",SmoothSiteDPanel:GetWide()/2,SmoothSiteDPanel:GetTall()/2,Color(255,255,255),TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) + end + + local SmoothOpenHtml = vgui.Create( "HTML", SmoothSiteDPanel ) + SmoothOpenHtml:SetSize(SmoothSiteDPanel:GetWide()-10, 570) + SmoothOpenHtml:SetPos(5,0) + SmoothOpenHtml:OpenURL( SmoothF4MenuFrame_SiteURL ) + SmoothOpenHtml.Think = function() + SmoothOpenHtml:SetSize(SmoothSiteDPanel:GetWide()-10, 570) + end + + local OpenBtn = vgui.Create("DButton", SmoothSiteDPanel) + OpenBtn:SetText("Открыть в браузере") + OpenBtn:SetFont("SmoothF4menuFontButton") + OpenBtn:SetPos(5, 575) + OpenBtn:SetSize(SmoothSiteDPanel:GetWide()-10, 35) + OpenBtn.Paint = function(self, w, h) + if self:IsHovered() then + draw.RoundedBox(0, 0, 0, w, h, Color(40, 40, 40, 255)) + self:SetTextColor(Color(255, 255, 255, 255)) + else + draw.RoundedBox(0, 0, 0, w, h, Color(20, 20, 20, 255)) + self:SetTextColor(Color(200, 200, 200, 255)) + end + end + OpenBtn.DoClick = function() + gui.OpenURL(SmoothF4MenuFrame_SiteURL) + end + +end +vgui.Register( "SmoothSitePanel", PANEL, "Panel" ) + + + + +local PANEL = {} +function PANEL:Init() + + SmoothDonateDPanel = vgui.Create( "DPanel", SmoothF4MenuFrame ) + SmoothDonateDPanel:SetPos( 50, 35 ) + SmoothDonateDPanel:SetSize( 950, 615 ) + SmoothDonateDPanel.Paint = function() + draw.SimpleText(SmoothF4MenuFrame_Loading,"SmoothF4menuFontButton",SmoothDonateDPanel:GetWide()/2,SmoothDonateDPanel:GetTall()/2,Color(255,255,255),TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) + end + + local SmoothOpenHtml = vgui.Create( "HTML", SmoothDonateDPanel ) + SmoothOpenHtml:SetSize(SmoothDonateDPanel:GetWide()-10, 570) + SmoothOpenHtml:SetPos(5,0) + SmoothOpenHtml:OpenURL( SmoothF4MenuFrame_DonateURL ) + SmoothOpenHtml.Think = function() + SmoothOpenHtml:SetSize(SmoothDonateDPanel:GetWide()-10, 570) + end + + local OpenBtn = vgui.Create("DButton", SmoothDonateDPanel) + OpenBtn:SetText("Открыть в браузере") + OpenBtn:SetFont("SmoothF4menuFontButton") + OpenBtn:SetPos(5, 575) + OpenBtn:SetSize(SmoothDonateDPanel:GetWide()-10, 35) + OpenBtn.Paint = function(self, w, h) + if self:IsHovered() then + draw.RoundedBox(0, 0, 0, w, h, Color(40, 40, 40, 255)) + self:SetTextColor(Color(255, 255, 255, 255)) + else + draw.RoundedBox(0, 0, 0, w, h, Color(20, 20, 20, 255)) + self:SetTextColor(Color(200, 200, 200, 255)) + end + end + OpenBtn.DoClick = function() + gui.OpenURL(SmoothF4MenuFrame_DonateURL) + end + +end +vgui.Register( "SmoothDonatePanel", PANEL, "Panel" ) diff --git a/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-title.lua b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-title.lua new file mode 100644 index 0000000..4f562be --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu-title.lua @@ -0,0 +1,153 @@ +/*---------------------------------------------------------------------- +Leak by Famouse + +Play good games:↓ +store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +https://www.youtube.com/c/Famouse + +More leaks in the discord:↓ +discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +local PANEL = {} +function PANEL:Init() + + SmoothTitleDPanel = vgui.Create( "DPanel", SmoothF4MenuFrame ) + SmoothTitleDPanel:SetPos( 50, 35 ) + SmoothTitleDPanel:SetSize( 950, 615 ) + SmoothTitleDPanel.Paint = function() + + end + + SmoothPanelInfoServer = vgui.Create( "DPanel", SmoothTitleDPanel ) + SmoothPanelInfoServer:SetPos( 5, 5 ) + SmoothPanelInfoServer:SetSize( SmoothTitleDPanel:GetWide()-10, 70 ) + SmoothPanelInfoServer.Paint = function() + draw.RoundedBox( 0, 0, 0, SmoothPanelInfoServer:GetWide(), SmoothPanelInfoServer:GetTall() , Color(47, 47, 47,150) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,SmoothPanelInfoServer:GetWide(), SmoothPanelInfoServer:GetTall()) + + draw.SimpleText(SmoothF4MenuFrame_NameServer,"SmoothF4menuFont1",29,2,Color(255,255,255)) + draw.RoundedBox( 2, 5, 35, SmoothPanelInfoServer:GetWide()-14, 2 , Color(255,255,255) ) + + local menuicon = Material( "materials/icons/database.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(5,7,22,22 ) + + local onlineplayer = 0 + for k,v in pairs(player.GetAll()) do onlineplayer = onlineplayer + 1 end + + draw.SimpleText(SmoothF4MenuFrame_OnlinePlayer..": "..onlineplayer,"SmoothF4menuFontButton",30,43,Color(255,255,255)) + + local menuicon = Material( "materials/icons/user.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(9,46,15,15 ) + end + SmoothPanelInfoServer.Think = function() SmoothPanelInfoServer:SetSize( SmoothTitleDPanel:GetWide()-10, 70 ) end + + SmoothPanelInfoPlayer = vgui.Create( "DPanel", SmoothTitleDPanel ) + SmoothPanelInfoPlayer:SetPos( 5, 80 ) + SmoothPanelInfoPlayer:SetSize( SmoothTitleDPanel:GetWide()-10, 85 ) + SmoothPanelInfoPlayer.Paint = function() + draw.RoundedBox( 0, 0, 0, SmoothPanelInfoPlayer:GetWide(), SmoothPanelInfoPlayer:GetTall() , Color(47, 47, 47,150) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,SmoothPanelInfoPlayer:GetWide(), SmoothPanelInfoPlayer:GetTall()) + + draw.SimpleText(LocalPlayer():Name(),"SmoothF4menuFont1",90,3,Color(255,255,255)) + draw.RoundedBox( 2, 91, 35, SmoothPanelInfoPlayer:GetWide()-100, 2 , Color(255,255,255) ) + + draw.SimpleText(SmoothF4MenuFrame_TMoney..": "..LocalPlayer():getDarkRPVar("money")..SmoothF4MenuFrame_Money,"SmoothF4menuFontButton",90,40,Color(255,255,255)) + draw.SimpleText(SmoothF4MenuFrame_Group..": "..LocalPlayer():GetUserGroup(),"SmoothF4menuFontButton",90,59,Color(255,255,255)) + end + SmoothPanelInfoPlayer.Think = function() SmoothPanelInfoPlayer:SetSize( SmoothTitleDPanel:GetWide()-10, 85 ) end + + local Avatar = vgui.Create( "AvatarImage", SmoothPanelInfoPlayer ) + Avatar:SetSize( 75, 75 ) + Avatar:SetPos( 5, 5 ) + Avatar:SetPlayer( LocalPlayer(), 128 ) + + SmoothPanelStaffOnline = vgui.Create( "DPanel", SmoothTitleDPanel ) + SmoothPanelStaffOnline:SetPos( 5, 170 ) + SmoothPanelStaffOnline:SetSize( 300, 440 ) + SmoothPanelStaffOnline.Paint = function() + draw.RoundedBox( 0, 0, 0, SmoothPanelStaffOnline:GetWide(), SmoothPanelStaffOnline:GetTall() , Color(47, 47, 47,150) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,SmoothPanelStaffOnline:GetWide(), SmoothPanelStaffOnline:GetTall()) + + draw.SimpleText(SmoothF4MenuFrame_TStaffOnline,"SmoothF4menuFont1",5,3,Color(255,255,255)) + draw.RoundedBox( 2, 5, 35, 290, 2 , Color(255,255,255) ) + + end + SmoothPanelStaffOnline.Think = function() SmoothPanelStaffOnline:SetSize( 300, 440 ) end + + SmoothStaffPosY = 34 + SmoothStaffPosYNumber = 0 + for k,v in pairs(player.GetAll()) do + if SmoothStaffPosYNumber < 17 then + if table.HasValue(SmoothF4MenuFrame_StaffOnline,v:GetUserGroup()) then + local SmoothStaffLabel = vgui.Create("DLabel", SmoothPanelStaffOnline) + SmoothStaffLabel:SetPos(5, SmoothStaffPosY) + SmoothStaffLabel:SetColor(Color(255,255,255,255)) + SmoothStaffLabel:SetText( "" ) + SmoothStaffLabel:SetWide( 290 ) + SmoothStaffLabel:SetFont( "SmoothF4menuFontButton" ) + SmoothStaffLabel:SetTall(100) + SmoothStaffLabel.Paint = function() + draw.SimpleText(v:Name().." - "..v:GetUserGroup(),"SmoothF4menuFontButton",0,6,Color(255,255,255)) + end + SmoothStaffPosYNumber = SmoothStaffPosYNumber + 1 + SmoothStaffPosY = SmoothStaffPosY + 23.5 + end + end + end + + SmoothPanelRules = vgui.Create( "DPanel", SmoothTitleDPanel ) + SmoothPanelRules:SetPos( 310, 170 ) + SmoothPanelRules:SetSize( SmoothTitleDPanel:GetWide()-310, 440 ) + SmoothPanelRules.Paint = function() + draw.RoundedBox( 0, 0, 0, SmoothPanelRules:GetWide(), SmoothPanelRules:GetTall() , Color(47, 47, 47,150) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,SmoothPanelRules:GetWide(), SmoothPanelRules:GetTall()) + + draw.SimpleText(SmoothF4MenuFrame_RulesTitle,"SmoothF4menuFont1",5,3,Color(255,255,255)) + draw.RoundedBox( 2, 5, 35, SmoothTitleDPanel:GetWide()-330, 2 , Color(255,255,255) ) + + end + SmoothPanelRules.Think = function() SmoothPanelRules:SetSize( SmoothTitleDPanel:GetWide()-315, 440 ) end + + local SmoothStaffLabel = vgui.Create("DLabel", SmoothPanelRules) + SmoothStaffLabel:SetPos(10, 45) + SmoothStaffLabel:SetColor(Color(255,255,255,255)) + SmoothStaffLabel:SetText( "" ) + SmoothStaffLabel:SetWide( SmoothPanelRules:GetWide()-20 ) + SmoothStaffLabel:SetFont( "SmoothF4menuFontButton" ) + SmoothStaffLabel:SetTall(1) + SmoothStaffLabel.Paint = function() end + SmoothStaffLabel.Think = function() + rulestext = "" + for k,v in pairs(DarkRP.getLaws()) do + rulestext = rulestext..v.."\n" + end + rulestext = DarkRP.textWrap(rulestext, "DarkRPHUD1", SmoothPanelRules:GetWide()-20) + rulestextexp = string.Explode("\n",rulestext) + + SmoothStaffLabel:SetText( rulestext ) + SmoothStaffLabel:SetTall(#rulestextexp*20) + end + + +end +vgui.Register( "SmoothTitlePanel", PANEL, "Panel" ) + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu.lua b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu.lua new file mode 100644 index 0000000..dc61bae --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/client/cl_smooth-f4menu.lua @@ -0,0 +1,425 @@ +/*---------------------------------------------------------------------- +Leak by Famouse + +Play good games:↓ +store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +https://www.youtube.com/c/Famouse + +More leaks in the discord:↓ +discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +surface.CreateFont( "SmoothF4menuFont1", { + font = "Open Sans", + size = 30, + weight = 700, + antialias = true, +}) + +surface.CreateFont( "SmoothF4menuFontButton", { + font = "Open Sans", + size = 20, + weight = 700, + antialias = true, +}) + +Texts = {} +Texts.DarkRPCommand = "say" +function RunEntCmd(...) + local arg = {...} + if Texts.DarkRPCommand:lower():find('say') then + arg = table.concat(arg,' ') + else + arg = table.concat(arg,'" "') + end + RunConsoleCommand(Texts.DarkRPCommand, "/"..arg) +end + +function RunCmd(...) + local arg = {...} + if Texts.DarkRPCommand:lower():find('say') then + arg = table.concat(arg,' ') + else + arg = table.concat(arg,'" "') + end + RunConsoleCommand(Texts.DarkRPCommand,arg) +end + +function ClearAllDPanel() + if IsValid(SmoothJobsDPanel) then SmoothJobsDPanel:Remove() end + if IsValid(SmoothShopDPanel) then SmoothShopDPanel:Remove() end + if IsValid(SmoothSiteDPanel) then SmoothSiteDPanel:Remove() end + if IsValid(SmoothTitleDPanel) then SmoothTitleDPanel:Remove() end + if IsValid(SmoothCommandDPanel) then SmoothCommandDPanel:Remove() end + if IsValid(SmoothDonateDPanel) then SmoothDonateDPanel:Remove() end +end + +timersimpleasd = 0 +hook.Add("Think","CloseF4Menu",function() + if CurTime() > timersimpleasd then + if input.IsKeyDown(KEY_F4) then + if IsValid(SmoothF4MenuFrame) then + -- SmoothF4MenuFrame:MoveTo( -1000,ScrH()/2-(SmoothF4MenuFrame:GetTall()/2), 0, 0, -1) + -- timer.Simple(0.5,function() SmoothF4MenuFrame:Close() end) + SmoothF4MenuFrame:Close() + end + timersimpleasd = CurTime() + 1 + end + end +end) + +net.Receive("OpenSmoothF4menu",function() + + if IsValid(SmoothF4MenuFrame) then SmoothF4MenuFrame:Close() end + + timer.Simple(0,function() local SmoothTitlePanel = SmoothF4MenuFrame:Add("SmoothTitlePanel") end) + + SmoothF4MenuFrame = vgui.Create("DFrame") + SmoothF4MenuFrame:SetSize(1000, 650) + SmoothF4MenuFrame:SetPos(-1000,ScrH()/2-(SmoothF4MenuFrame:GetTall()/2)) + SmoothF4MenuFrame:SetDraggable( true ) + SmoothF4MenuFrame:MakePopup() + SmoothF4MenuFrame:ShowCloseButton( false ) + SmoothF4MenuFrame:SetTitle("") + SmoothF4MenuFrame.Paint = function( panel ) + SmoothDrawBlur(panel, 3) + + draw.RoundedBox( 0, 0, 0, SmoothF4MenuFrame:GetWide(), SmoothF4MenuFrame:GetTall() , Color(47, 47, 47,150) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,SmoothF4MenuFrame:GetWide(), SmoothF4MenuFrame:GetTall()) + + draw.RoundedBox( 0, 0, 0, SmoothF4MenuFrame:GetWide(), 35 , Color(41, 41, 41,75) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,SmoothF4MenuFrame:GetWide(), 35) + + local menuicon = Material( "materials/icons/menu.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(5,5,25,25 ) + end + SmoothF4MenuFrame:MoveTo( (ScrW()/2-(SmoothF4MenuFrame:GetWide()/2)),ScrH()/2-(SmoothF4MenuFrame:GetTall()/2), 0, 0, -1) + + local SmoothF4Menuclose = vgui.Create("DButton", SmoothF4MenuFrame) + SmoothF4Menuclose:SetSize( 45, 35 ) + SmoothF4Menuclose:SetPos( SmoothF4MenuFrame:GetWide() - 45 , 0 ) + SmoothF4Menuclose:SetColor( Color( 255, 255, 255 )) + SmoothF4Menuclose:SetFont("SmoothF4menuFont1") + SmoothF4Menuclose:SetText("") + SmoothF4Menuclose.Paint = function( ) + local menuicon = Material( "materials/icons/close.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(13,8,19,19 ) + if SmoothF4Menuclose.isHover then + draw.RoundedBox( 0, 0, 0, SmoothF4Menuclose:GetWide(), 35 , Color(41, 41, 41,75) ) + end + end + SmoothF4Menuclose.OnCursorEntered = function() + SmoothF4Menuclose.isHover = true + end + SmoothF4Menuclose.OnCursorExited = function() + SmoothF4Menuclose.isHover = false + end + SmoothF4Menuclose.DoClick = function() + SmoothF4MenuFrame:Close() + end + + local NameServerTitle = vgui.Create("DLabel", SmoothF4MenuFrame) + NameServerTitle:SizeToContents() + NameServerTitle:SetPos(35,0) + NameServerTitle:SetColor(Color(255,255,255,255)) + NameServerTitle:SetText( SmoothF4MenuFrame_NameServer ) + NameServerTitle:SetWide( 800 ) + NameServerTitle:SetFont( "SmoothF4menuFont1" ) + NameServerTitle:SetTall(34) + + SmoothF4MenuDPanel = vgui.Create( "DPanel",SmoothF4MenuFrame ) + SmoothF4MenuDPanel:SetPos( 0, 34 ) + SmoothF4MenuDPanel:SetSize( 50, 616 ) + SmoothF4MenuDPanel.isActive1 = false + SmoothF4MenuDPanel.isActive2 = false + SmoothF4MenuDPanel.Paint = function() + draw.RoundedBox( 0, 0, 0, SmoothF4MenuDPanel:GetWide(), SmoothF4MenuDPanel:GetTall() , Color(41, 41, 41,75) ) + surface.SetDrawColor( 47, 47, 47,150 ) + surface.DrawOutlinedRect(0,0,SmoothF4MenuDPanel:GetWide(), SmoothF4MenuDPanel:GetTall()) + + if (SmoothF4MenuDPanel.isHover or SmoothF4MenuButtonTitle.isHover or SmoothF4MenuButtonJobs.isHover or SmoothF4MenuButtonShop.isHover or SmoothF4MenuButtonSite.isHover or SmoothF4MenuButtonCommands.isHover or SmoothF4MenuButtonDonate.isHover ) and !SmoothF4MenuDPanel.isActive1 then + SmoothF4MenuDPanel:SizeTo( 250,616, 0.3, 0, -1) + SmoothF4MenuDPanel.isActive1 = true + SmoothF4MenuDPanel.isActive2 = false + + -- JOBS + if IsValid(SmoothJobsDPanel) then + SmoothJobsDPanel:SizeTo( 750,615, 0.3, 0, -1) + SmoothJobsDPanel:MoveTo( 250,35, 0.3, 0, -1) + end + -- ENT + if IsValid(SmoothShopDPanel) then + SmoothShopDPanel:SizeTo( 750,605, 0.3, 0, -1) + SmoothShopDPanel:MoveTo( 250,40, 0.3, 0, -1) + end + -- SITE + if IsValid(SmoothSiteDPanel) then + SmoothSiteDPanel:SizeTo( 750,615, 0.3, 0, -1) + SmoothSiteDPanel:MoveTo( 250,40, 0.3, 0, -1) + end + -- SITE + if IsValid(SmoothDonateDPanel) then + SmoothDonateDPanel:SizeTo( 750,615, 0.3, 0, -1) + SmoothDonateDPanel:MoveTo( 250,40, 0.3, 0, -1) + end + -- TITLE + if IsValid(SmoothTitleDPanel) then + SmoothTitleDPanel:SizeTo( 750,615, 0.3, 0, -1) + SmoothTitleDPanel:MoveTo( 250,35, 0.3, 0, -1) + end + -- COMMAND + if IsValid(SmoothCommandDPanel) then + SmoothCommandDPanel:SizeTo( 750,605, 0.3, 0, -1) + SmoothCommandDPanel:MoveTo( 250,40, 0.3, 0, -1) + end + elseif (!SmoothF4MenuDPanel.isHover and !SmoothF4MenuButtonTitle.isHover and !SmoothF4MenuButtonJobs.isHover and !SmoothF4MenuButtonShop.isHover and !SmoothF4MenuButtonSite.isHover and !SmoothF4MenuButtonCommands.isHover and !SmoothF4MenuButtonDonate.isHover) and !SmoothF4MenuDPanel.isActive2 then + SmoothF4MenuDPanel:SizeTo( 50,616, 0.3, 0, -1) + SmoothF4MenuDPanel.isActive2 = true + SmoothF4MenuDPanel.isActive1 = false + + -- JOBS + if IsValid(SmoothJobsDPanel) then + SmoothJobsDPanel:SizeTo( 950,615, 0.3, 0, -1) + SmoothJobsDPanel:MoveTo( 50,35, 0.3, 0, -1) + end + -- ENT + if IsValid(SmoothShopDPanel) then + SmoothShopDPanel:SizeTo( 950,605, 0.3, 0, -1) + SmoothShopDPanel:MoveTo( 50,40, 0.3, 0, -1) + end + -- SITE + if IsValid(SmoothSiteDPanel) then + SmoothSiteDPanel:SizeTo( 950,615, 0.3, 0, -1) + SmoothSiteDPanel:MoveTo( 50,40, 0.3, 0, -1) + end + -- SITE + if IsValid(SmoothDonateDPanel) then + SmoothDonateDPanel:SizeTo( 950,615, 0.3, 0, -1) + SmoothDonateDPanel:MoveTo( 50,40, 0.3, 0, -1) + end + -- TITLE + if IsValid(SmoothTitleDPanel) then + SmoothTitleDPanel:SizeTo( 950,615, 0.3, 0, -1) + SmoothTitleDPanel:MoveTo( 50,35, 0.3, 0, -1) + end + -- COMMAND + if IsValid(SmoothCommandDPanel) then + SmoothCommandDPanel:SizeTo( 950,605, 0.3, 0, -1) + SmoothCommandDPanel:MoveTo( 50,40, 0.3, 0, -1) + end + end + end + SmoothF4MenuDPanel.OnCursorEntered = function() + SmoothF4MenuDPanel.isHover = true + end + SmoothF4MenuDPanel.OnCursorExited = function() + SmoothF4MenuDPanel.isHover = false + end + + SmoothF4MenuButtonTitle = vgui.Create("DButton", SmoothF4MenuFrame) + SmoothF4MenuButtonTitle:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + SmoothF4MenuButtonTitle:SetPos( 0,35 ) + SmoothF4MenuButtonTitle:SetColor( Color( 255, 255, 255 )) + SmoothF4MenuButtonTitle:SetFont("SmoothF4menuFontButton") + SmoothF4MenuButtonTitle:SetText("") + SmoothF4MenuButtonTitle.Paint = function(panel) + local menuicon = Material( "materials/icons/home.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(10,10,30,30 ) + if SmoothF4MenuButtonTitle.isHover then + draw.RoundedBox( 0, 0, 0, SmoothF4MenuButtonTitle:GetWide(), SmoothF4MenuButtonTitle:GetTall() , Color(41, 41, 41,70) ) + end + draw.SimpleText(SmoothF4MenuFrame_NameServer_Home,"SmoothF4menuFontButton",55,15,Color(255,255,255)) + SmoothF4MenuButtonTitle:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + end + SmoothF4MenuButtonTitle.OnCursorEntered = function() + SmoothF4MenuButtonTitle.isHover = true + end + SmoothF4MenuButtonTitle.OnCursorExited = function() + SmoothF4MenuButtonTitle.isHover = false + end + SmoothF4MenuButtonTitle.DoClick = function() + ClearAllDPanel() + local SmoothTitlePanel = SmoothF4MenuFrame:Add("SmoothTitlePanel") + SmoothF4MenuDPanel:SetSize(50,616) + end + + SmoothF4MenuButtonJobs = vgui.Create("DButton", SmoothF4MenuFrame) + SmoothF4MenuButtonJobs:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + SmoothF4MenuButtonJobs:SetPos( 0,85 ) + SmoothF4MenuButtonJobs:SetColor( Color( 255, 255, 255 )) + SmoothF4MenuButtonJobs:SetFont("SmoothF4menuFontButton") + SmoothF4MenuButtonJobs:SetText("") + SmoothF4MenuButtonJobs.Paint = function(panel) + local menuicon = Material( "materials/icons/jobs.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(10,10,30,30 ) + if SmoothF4MenuButtonJobs.isHover then + draw.RoundedBox( 0, 0, 0, SmoothF4MenuButtonJobs:GetWide(), SmoothF4MenuButtonJobs:GetTall() , Color(41, 41, 41,70) ) + end + draw.SimpleText(SmoothF4MenuFrame_NameServer_Jobs,"SmoothF4menuFontButton",55,15,Color(255,255,255)) + SmoothF4MenuButtonJobs:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + end + SmoothF4MenuButtonJobs.OnCursorEntered = function() + SmoothF4MenuButtonJobs.isHover = true + end + SmoothF4MenuButtonJobs.OnCursorExited = function() + SmoothF4MenuButtonJobs.isHover = false + end + SmoothF4MenuButtonJobs.DoClick = function() + ClearAllDPanel() + local SmoothJobsPanel = SmoothF4MenuFrame:Add("SmoothJobsPanel") + SmoothF4MenuDPanel:SetSize(50,616) + end + + SmoothF4MenuButtonShop = vgui.Create("DButton", SmoothF4MenuFrame) + SmoothF4MenuButtonShop:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + SmoothF4MenuButtonShop:SetPos( 0,135 ) + SmoothF4MenuButtonShop:SetColor( Color( 255, 255, 255 )) + SmoothF4MenuButtonShop:SetFont("SmoothF4menuFontButton") + SmoothF4MenuButtonShop:SetText("") + SmoothF4MenuButtonShop.Paint = function(panel) + local menuicon = Material( "materials/icons/shop.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(10,10,30,30 ) + if SmoothF4MenuButtonShop.isHover then + draw.RoundedBox( 0, 0, 0, SmoothF4MenuButtonShop:GetWide(), SmoothF4MenuButtonShop:GetTall() , Color(41, 41, 41,70) ) + end + draw.SimpleText(SmoothF4MenuFrame_NameServer_Shop,"SmoothF4menuFontButton",55,15,Color(255,255,255)) + SmoothF4MenuButtonShop:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + end + SmoothF4MenuButtonShop.OnCursorEntered = function() + SmoothF4MenuButtonShop.isHover = true + end + SmoothF4MenuButtonShop.OnCursorExited = function() + SmoothF4MenuButtonShop.isHover = false + end + SmoothF4MenuButtonShop.DoClick = function() + ClearAllDPanel() + local SmoothShopPanel = SmoothF4MenuFrame:Add("SmoothShopPanel") + SmoothF4MenuDPanel:SetSize(50,616) + end + + SmoothF4MenuButtonCommands = vgui.Create("DButton", SmoothF4MenuFrame) + SmoothF4MenuButtonCommands:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + SmoothF4MenuButtonCommands:SetPos( 0,185 ) + SmoothF4MenuButtonCommands:SetColor( Color( 255, 255, 255 )) + SmoothF4MenuButtonCommands:SetFont("SmoothF4menuFontButton") + SmoothF4MenuButtonCommands:SetText("") + SmoothF4MenuButtonCommands.Paint = function(panel) + local menuicon = Material( "materials/icons/commands.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(10,10,30,30 ) + if SmoothF4MenuButtonCommands.isHover then + draw.RoundedBox( 0, 0, 0, SmoothF4MenuButtonCommands:GetWide(), SmoothF4MenuButtonCommands:GetTall() , Color(41, 41, 41,70) ) + end + draw.SimpleText(SmoothF4MenuFrame_NameServer_Command,"SmoothF4menuFontButton",55,15,Color(255,255,255)) + SmoothF4MenuButtonCommands:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + end + SmoothF4MenuButtonCommands.OnCursorEntered = function() + SmoothF4MenuButtonCommands.isHover = true + end + SmoothF4MenuButtonCommands.OnCursorExited = function() + SmoothF4MenuButtonCommands.isHover = false + end + SmoothF4MenuButtonCommands.DoClick = function() + ClearAllDPanel() + local SmoothCommandPanel = SmoothF4MenuFrame:Add("SmoothCommandPanel") + SmoothF4MenuDPanel:SetSize(50,616) + end + + SmoothF4MenuButtonSite = vgui.Create("DButton", SmoothF4MenuFrame) + SmoothF4MenuButtonSite:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + SmoothF4MenuButtonSite:SetPos( 0,235 ) + SmoothF4MenuButtonSite:SetColor( Color( 255, 255, 255 )) + SmoothF4MenuButtonSite:SetFont("SmoothF4menuFontButton") + SmoothF4MenuButtonSite:SetText("") + SmoothF4MenuButtonSite.Paint = function(panel) + local menuicon = Material( "materials/icons/site.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(10,10,30,30 ) + if SmoothF4MenuButtonSite.isHover then + draw.RoundedBox( 0, 0, 0, SmoothF4MenuButtonSite:GetWide(), SmoothF4MenuButtonSite:GetTall() , Color(41, 41, 41,70) ) + end + draw.SimpleText(SmoothF4MenuFrame_NameServer_Site,"SmoothF4menuFontButton",55,15,Color(255,255,255)) + SmoothF4MenuButtonSite:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + end + SmoothF4MenuButtonSite.OnCursorEntered = function() + SmoothF4MenuButtonSite.isHover = true + end + SmoothF4MenuButtonSite.OnCursorExited = function() + SmoothF4MenuButtonSite.isHover = false + end + SmoothF4MenuButtonSite.DoClick = function() + ClearAllDPanel() + local SmoothSitePanel = SmoothF4MenuFrame:Add("SmoothSitePanel") + SmoothF4MenuDPanel:SetSize(50,616) + end + + SmoothF4MenuButtonDonate = vgui.Create("DButton", SmoothF4MenuFrame) + SmoothF4MenuButtonDonate:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + SmoothF4MenuButtonDonate:SetPos( 0,285 ) + SmoothF4MenuButtonDonate:SetColor( Color( 255, 255, 255 )) + SmoothF4MenuButtonDonate:SetFont("SmoothF4menuFontButton") + SmoothF4MenuButtonDonate:SetText("") + SmoothF4MenuButtonDonate.Paint = function(panel) + local menuicon = Material( "materials/icons/donate.png" ) + surface.SetMaterial( menuicon ) + surface.SetDrawColor( 255, 255, 255, 255 ) + surface.DrawTexturedRect(10,10,30,30 ) + if SmoothF4MenuButtonDonate.isHover then + draw.RoundedBox( 0, 0, 0, SmoothF4MenuButtonDonate:GetWide(), SmoothF4MenuButtonDonate:GetTall() , Color(41, 41, 41,70) ) + end + draw.SimpleText("Донат","SmoothF4menuFontButton",55,15,Color(255,255,255)) + SmoothF4MenuButtonDonate:SetSize( SmoothF4MenuDPanel:GetWide(),50 ) + end + SmoothF4MenuButtonDonate.OnCursorEntered = function() + SmoothF4MenuButtonDonate.isHover = true + end + SmoothF4MenuButtonDonate.OnCursorExited = function() + SmoothF4MenuButtonDonate.isHover = false + end + SmoothF4MenuButtonDonate.DoClick = function() + ClearAllDPanel() + local SmoothDonatePanel = SmoothF4MenuFrame:Add("SmoothDonatePanel") + SmoothF4MenuDPanel:SetSize(50,616) + end + +end) + +-- BLUR PANEL +function SmoothDrawBlur(panel, amount) + local blurmaterial = Material("pp/blurscreen") + local x, y = panel:LocalToScreen(0, 0) + local scrW, scrH = ScrW(), ScrH() + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(blurmaterial) + for i = 1, 3 do + blurmaterial:SetFloat("$blur", (i / 3) * (amount or 6)) + blurmaterial:Recompute() + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(x * -1, y * -1, scrW, scrH) + end +end + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ diff --git a/addons/f4/lua/smooth-f4menu/server/sv_smooth-f4menu.lua b/addons/f4/lua/smooth-f4menu/server/sv_smooth-f4menu.lua new file mode 100644 index 0000000..61ec5bf --- /dev/null +++ b/addons/f4/lua/smooth-f4menu/server/sv_smooth-f4menu.lua @@ -0,0 +1,29 @@ +/*---------------------------------------------------------------------- +Leak by Famouse + +Play good games:↓ +http://store.steampowered.com/curator/32364216 + +Subscribe to the channel:↓ +www.youtube.com/c/Famouse + +More leaks in the discord:↓ +https://discord.gg/rFdQwzm +------------------------------------------------------------------------*/ + +util.AddNetworkString( "OpenSmoothF4menu" ) + +function OpenSmoothF4menu( ply ) + net.Start("OpenSmoothF4menu") + net.Send(ply) +end +hook.Add("ShowSpare2", "OpenSmoothF4menu", OpenSmoothF4menu) + +/*------------------------------------------------------------------------ +Donation for leaks + +Qiwi Wallet 4890494419811120 +YandexMoney 410013095053302 +WebMoney(WMR) R235985364414 +WebMoney(WMZ) Z309855690994 +------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/addons/f4/materials/icons/back.png b/addons/f4/materials/icons/back.png new file mode 100644 index 0000000..655ccdb Binary files /dev/null and b/addons/f4/materials/icons/back.png differ diff --git a/addons/f4/materials/icons/close.png b/addons/f4/materials/icons/close.png new file mode 100644 index 0000000..123fc79 Binary files /dev/null and b/addons/f4/materials/icons/close.png differ diff --git a/addons/f4/materials/icons/commands.png b/addons/f4/materials/icons/commands.png new file mode 100644 index 0000000..80860a2 Binary files /dev/null and b/addons/f4/materials/icons/commands.png differ diff --git a/addons/f4/materials/icons/database.png b/addons/f4/materials/icons/database.png new file mode 100644 index 0000000..81e8d84 Binary files /dev/null and b/addons/f4/materials/icons/database.png differ diff --git a/addons/f4/materials/icons/donate.png b/addons/f4/materials/icons/donate.png new file mode 100644 index 0000000..732ed9d Binary files /dev/null and b/addons/f4/materials/icons/donate.png differ diff --git a/addons/f4/materials/icons/home.png b/addons/f4/materials/icons/home.png new file mode 100644 index 0000000..706e0e2 Binary files /dev/null and b/addons/f4/materials/icons/home.png differ diff --git a/addons/f4/materials/icons/info.png b/addons/f4/materials/icons/info.png new file mode 100644 index 0000000..3108950 Binary files /dev/null and b/addons/f4/materials/icons/info.png differ diff --git a/addons/f4/materials/icons/jobs.png b/addons/f4/materials/icons/jobs.png new file mode 100644 index 0000000..fb5afa8 Binary files /dev/null and b/addons/f4/materials/icons/jobs.png differ diff --git a/addons/f4/materials/icons/menu.png b/addons/f4/materials/icons/menu.png new file mode 100644 index 0000000..14d2e98 Binary files /dev/null and b/addons/f4/materials/icons/menu.png differ diff --git a/addons/f4/materials/icons/shop.png b/addons/f4/materials/icons/shop.png new file mode 100644 index 0000000..e95ff8a Binary files /dev/null and b/addons/f4/materials/icons/shop.png differ diff --git a/addons/f4/materials/icons/site.png b/addons/f4/materials/icons/site.png new file mode 100644 index 0000000..c883367 Binary files /dev/null and b/addons/f4/materials/icons/site.png differ diff --git a/addons/f4/materials/icons/user.png b/addons/f4/materials/icons/user.png new file mode 100644 index 0000000..80f3aeb Binary files /dev/null and b/addons/f4/materials/icons/user.png differ diff --git a/addons/itemstore/lua/autorun/itemstore.lua b/addons/itemstore/lua/autorun/itemstore.lua new file mode 100644 index 0000000..0f8b28e --- /dev/null +++ b/addons/itemstore/lua/autorun/itemstore.lua @@ -0,0 +1,9 @@ +hook.Add( "PostGamemodeLoaded", "ItemStoreInitialize", function() + itemstore = {} + + if SERVER then + include( "itemstore/sv_init.lua" ) + else + include( "itemstore/cl_init.lua" ) + end +end ) \ No newline at end of file diff --git a/addons/itemstore/lua/entities/itemstore_bank.lua b/addons/itemstore/lua/entities/itemstore_bank.lua new file mode 100644 index 0000000..d1b921e --- /dev/null +++ b/addons/itemstore/lua/entities/itemstore_bank.lua @@ -0,0 +1,86 @@ +ENT.Type = "anim" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +ENT.PrintName = "Банк" +ENT.Category = "Scora" + +ENT.Spawnable = true +ENT.AdminOnly = true + +if SERVER then + AddCSLuaFile() + + function ENT:Initialize() + self:SetModel( "models/props_lab/reciever_cart.mdl" ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self:GetPhysicsObject():EnableMotion( false ) + end + + function ENT:SpawnFunction( pl, trace, class ) + local ent = ents.Create( class ) + ent:SetPos( trace.HitPos + trace.HitNormal * 16 ) + ent:Spawn() + + return ent + end + + function ENT:Use( pl ) + if not IsValid( pl ) then return end + + pl.Bank:Sync() + pl:OpenContainer( pl.Bank:GetID(), itemstore.Translate( "bank" ) ) + end + + concommand.Add( "itemstore_savebanks", function( pl ) + if not game.SinglePlayer() and IsValid( pl ) then return end + + local banks = {} + + for _, ent in ipairs( ents.FindByClass( "itemstore_bank" ) ) do + table.insert( banks, { + Position = ent:GetPos(), + Angles = ent:GetAngles() + } ) + end + + file.Write( "itemstore/banks/" .. game.GetMap() .. ".txt", util.TableToJSON( banks ) ) + + print( "Banks for map " .. game.GetMap() .. " saved." ) + end ) + + hook.Add( "InitPostEntity", "ItemStoreSpawnBanks", function() + local banks = util.JSONToTable( file.Read( "itemstore/banks/" .. game.GetMap() .. ".txt", "DATA" ) or "" ) or {} + + for _, data in ipairs( banks ) do + local bank = ents.Create( "itemstore_bank" ) + bank:SetPos( data.Position ) + bank:SetAngles( data.Angles ) + bank:Spawn() + end + end ) +else + function ENT:DrawTranslucent() + self:DrawModel() + + local text = itemstore.Translate( "bank" ) + local font = "DermaLarge" + + surface.SetFont( font ) + local textw, texth = surface.GetTextSize( text ) + local w = 5 + textw + 5 + local h = 2 + texth + 2 + local x, y = -w / 2, -h / 2 + + cam.Start3D2D( self:GetPos() + self:GetAngles():Up() * 50, Angle( 0, CurTime() * 45, 90 ), 0.35 ) + surface.SetDrawColor( Color( 0, 0, 0, 200 ) ) + surface.DrawRect( x, y, w, h ) + + draw.SimpleTextOutlined( text, font, 0, 0, Color( 255, 255, 255 ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, 1, Color( 0, 0, 0 ) ) + cam.End3D2D() + end +end diff --git a/addons/itemstore/lua/entities/itemstore_box.lua b/addons/itemstore/lua/entities/itemstore_box.lua new file mode 100644 index 0000000..26b4951 --- /dev/null +++ b/addons/itemstore/lua/entities/itemstore_box.lua @@ -0,0 +1,95 @@ +ENT.Type = "anim" + +ENT.PrintName = "Маленькая Коробочка" +ENT.Category = "Scora" + +ENT.Spawnable = true +ENT.AdminOnly = true + +function ENT:SetupDataTables() + self:NetworkVar( "Entity", 0, "owning_ent" ) -- i feel really stupid. +end + +if SERVER then + AddCSLuaFile() + + ENT.Model = "models/props/cs_office/Cardboard_box02.mdl" + + ENT.ContainerWidth = 4 + ENT.ContainerHeight = 3 + ENT.ContainerPages = 1 + + function ENT:Initialize() + self:SetModel( self.Model ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + + self:GetPhysicsObject():Wake() + + self.Container = itemstore.Container( self.ContainerWidth, self.ContainerHeight, self.ContainerPages ) + self.Container:SetOwner( self ) + + if self.Items then + for _, item in ipairs( self.Items ) do + self.Container:AddItem( item:Copy() ) + end + end + + local function callback( con, pl ) + if not IsValid( pl ) then return end + + if pl:GetPos():Distance( self:GetPos() ) < 250 then + return true + end + + return false + end + + self.Container:AddCallback( "read", callback ) + self.Container:AddCallback( "write", callback ) + + self:SetHealth( itemstore.config.BoxHealth ) + end + + function ENT:SpawnFunction( pl, trace, class ) + local ent = ents.Create( class ) + ent:SetPos( trace.HitPos + trace.HitNormal * 16 ) + ent:Spawn() + + return ent + end + + function ENT:Use( pl ) + self.Container:Sync() + pl:OpenContainer( self.Container:GetID(), "Box" ) + end + + function ENT:Break() + local effect = EffectData() + effect:SetOrigin( self:GetPos() ) + util.Effect( "Explosion", effect, true, true ) + + for _, item in pairs( self.Container.Items ) do + item:CreateEntity( self:GetPos() ) + end + + self:Remove() + end + + function ENT:OnTakeDamage( dmg ) + if not itemstore.config.BoxBreakable then return end + + self:SetHealth( self:Health() - dmg:GetDamage() ) + + if self:Health() <= 0 then + self:Break() + end + end + + function ENT:OnRemove() + self.Container:Remove() + end +end diff --git a/addons/itemstore/lua/entities/itemstore_box_huge.lua b/addons/itemstore/lua/entities/itemstore_box_huge.lua new file mode 100644 index 0000000..e62a094 --- /dev/null +++ b/addons/itemstore/lua/entities/itemstore_box_huge.lua @@ -0,0 +1,18 @@ +ENT.Type = "anim" +ENT.Base = "itemstore_box" + +ENT.PrintName = "Огромная Коробка" +ENT.Category = "Scora" + +ENT.Spawnable = true +ENT.AdminOnly = true + +if SERVER then + AddCSLuaFile() + + ENT.Model = "models/props_junk/wood_crate001a_damaged.mdl" + + ENT.ContainerWidth = 8 + ENT.ContainerHeight = 4 + ENT.ContainerPages = 2 +end diff --git a/addons/itemstore/lua/entities/itemstore_box_large.lua b/addons/itemstore/lua/entities/itemstore_box_large.lua new file mode 100644 index 0000000..3d190c1 --- /dev/null +++ b/addons/itemstore/lua/entities/itemstore_box_large.lua @@ -0,0 +1,18 @@ +ENT.Type = "anim" +ENT.Base = "itemstore_box" + +ENT.PrintName = "Большая Коробка" +ENT.Category = "Scora" + +ENT.Spawnable = true +ENT.AdminOnly = true + +if SERVER then + AddCSLuaFile() + + ENT.Model = "models/props/cs_office/cardboard_box01.mdl" + + ENT.ContainerWidth = 5 + ENT.ContainerHeight = 4 + ENT.ContainerPages = 1 +end diff --git a/addons/itemstore/lua/entities/itemstore_deathloot.lua b/addons/itemstore/lua/entities/itemstore_deathloot.lua new file mode 100644 index 0000000..6143e67 --- /dev/null +++ b/addons/itemstore/lua/entities/itemstore_deathloot.lua @@ -0,0 +1,24 @@ +ENT.Type = "anim" +ENT.Base = "itemstore_box" + +ENT.PrintName = "Смертельная Добыча" +ENT.Category = "Scora" + +ENT.Spawnable = false +ENT.AdminOnly = false + +if SERVER then + AddCSLuaFile() + + ENT.Model = "models/props_junk/garbage_bag001a.mdl" + + ENT.ContainerWidth = 5 + ENT.ContainerHeight = 5 + ENT.ContainerPages = 2 + + ENT.Timeout = 0 + + function ENT:Think() + if self.Timeout < CurTime() then self:Remove() end + end +end diff --git a/addons/itemstore/lua/entities/itemstore_item.lua b/addons/itemstore/lua/entities/itemstore_item.lua new file mode 100644 index 0000000..6bb6e27 --- /dev/null +++ b/addons/itemstore/lua/entities/itemstore_item.lua @@ -0,0 +1,106 @@ +ENT.Type = "anim" +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +function ENT:SetItem( item ) + self.Item = item + + if SERVER then + self:Sync() + end +end + +function ENT:GetItem() + return self.Item +end + +if SERVER then + AddCSLuaFile() + + function ENT:Initialize() + local item = self:GetItem() + if not item then self:Remove() end + + self:SetModel( item:GetModel() ) + + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + self:SetUseType( SIMPLE_USE ) + + self:SetColor( item:GetColor() or color_white ) + self:SetMaterial( item:GetMaterial() ) + + local phys = self:GetPhysicsObject() + if not IsValid( phys ) then + self:PhysicsInitSphere( 16, "default" ) + phys = self:GetPhysicsObject() + end + + phys:Wake() + end + + function ENT:Use( pl ) + if not IsValid( pl ) then return end + + local item = self:GetItem() + if not item then return end + + if pl.Inventory:AddItem( item ) then + pl:EmitSound( "items/itempickup.wav" ) + self:Remove() + else + pl:SendError( "Ваш инвентарь полон." ) + end + end + + function ENT:Sync( pl ) + local item = self:GetItem() + if not item then return end + + net.Start( "ItemStoreSyncItem" ) + net.WriteEntity( self ) + net.WriteString( item.Class ) + net.WriteTable( item.Data ) + net.Send( pl or player.GetAll() ) + end + + util.AddNetworkString( "ItemStoreSyncItem" ) + net.Receive( "ItemStoreSyncItem", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + + local ent = net.ReadEntity() + if not IsValid( ent ) or ent:GetClass() ~= "itemstore_item" then return end + + ent:Sync( pl ) + + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + end ) +else + function ENT:Initialize() + net.Start( "ItemStoreSyncItem" ) + net.WriteEntity( self ) + net.SendToServer() + end + + function ENT:DrawTranslucent() + local item = self:GetItem() + if not item then return end + + item:PreRender( self ) + self:DrawModel() + item:PostRender( self ) + end + + net.Receive( "ItemStoreSyncItem", function() + local ent = net.ReadEntity() + + if not IsValid( ent ) then return end + if not ent.SetItem then return end + + local class = net.ReadString() + local data = net.ReadTable() + + ent:SetItem( itemstore.Item( class, data ) ) + end ) +end diff --git a/addons/itemstore/lua/includes/modules/json.lua b/addons/itemstore/lua/includes/modules/json.lua new file mode 100644 index 0000000..5504f5c --- /dev/null +++ b/addons/itemstore/lua/includes/modules/json.lua @@ -0,0 +1,1053 @@ +-- -*- coding: utf-8 -*- +-- +-- Simple JSON encoding and decoding in pure Lua. +-- +-- Copyright 2010-2014 Jeffrey Friedl +-- http://regex.info/blog/ +-- +-- Latest version: http://regex.info/blog/lua/json +-- +-- This code is released under a Creative Commons CC-BY "Attribution" License: +-- http://creativecommons.org/licenses/by/3.0/deed.en_US +-- +-- It can be used for any purpose so long as the copyright notice above, +-- the web-page links above, and the 'AUTHOR_NOTE' string below are +-- maintained. Enjoy. +-- +local VERSION = 20141223.14 -- version history at end of file +local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20141223.14 ]-" + +-- +-- The 'AUTHOR_NOTE' variable exists so that information about the source +-- of the package is maintained even in compiled versions. It's also +-- included in OBJDEF below mostly to quiet warnings about unused variables. +-- +local OBJDEF = { + VERSION = VERSION, + AUTHOR_NOTE = AUTHOR_NOTE, +} + + +-- +-- Simple JSON encoding and decoding in pure Lua. +-- http://www.json.org/ +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- +-- +-- +-- DECODING (from a JSON string to a Lua table) +-- +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- If the JSON text is for an object or an array, e.g. +-- { "what": "books", "count": 3 } +-- or +-- [ "Larry", "Curly", "Moe" ] +-- +-- the result is a Lua table, e.g. +-- { what = "books", count = 3 } +-- or +-- { "Larry", "Curly", "Moe" } +-- +-- +-- The encode and decode routines accept an optional second argument, +-- "etc", which is not used during encoding or decoding, but upon error +-- is passed along to error handlers. It can be of any type (including nil). +-- +-- +-- +-- ERROR HANDLING +-- +-- With most errors during decoding, this code calls +-- +-- JSON:onDecodeError(message, text, location, etc) +-- +-- with a message about the error, and if known, the JSON text being +-- parsed and the byte count where the problem was discovered. You can +-- replace the default JSON:onDecodeError() with your own function. +-- +-- The default onDecodeError() merely augments the message with data +-- about the text and the location if known (and if a second 'etc' +-- argument had been provided to decode(), its value is tacked onto the +-- message as well), and then calls JSON.assert(), which itself defaults +-- to Lua's built-in assert(), and can also be overridden. +-- +-- For example, in an Adobe Lightroom plugin, you might use something like +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- LrErrors.throwUserError("Internal Error: invalid JSON data") +-- end +-- +-- or even just +-- +-- function JSON.assert(message) +-- LrErrors.throwUserError("Internal Error: " .. message) +-- end +-- +-- If JSON:decode() is passed a nil, this is called instead: +-- +-- JSON:onDecodeOfNilError(message, nil, nil, etc) +-- +-- and if JSON:decode() is passed HTML instead of JSON, this is called: +-- +-- JSON:onDecodeOfHTMLError(message, text, nil, etc) +-- +-- The use of the fourth 'etc' argument allows stronger coordination +-- between decoding and error reporting, especially when you provide your +-- own error-handling routines. Continuing with the the Adobe Lightroom +-- plugin example: +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- local note = "Internal Error: invalid JSON data" +-- if type(etc) = 'table' and etc.photo then +-- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') +-- end +-- LrErrors.throwUserError(note) +-- end +-- +-- : +-- : +-- +-- for i, photo in ipairs(photosToProcess) do +-- : +-- : +-- local data = JSON:decode(someJsonText, { photo = photo }) +-- : +-- : +-- end +-- +-- +-- +-- +-- +-- DECODING AND STRICT TYPES +-- +-- Because both JSON objects and JSON arrays are converted to Lua tables, +-- it's not normally possible to tell which original JSON type a +-- particular Lua table was derived from, or guarantee decode-encode +-- round-trip equivalency. +-- +-- However, if you enable strictTypes, e.g. +-- +-- JSON = assert(loadfile "JSON.lua")() --load the routines +-- JSON.strictTypes = true +-- +-- then the Lua table resulting from the decoding of a JSON object or +-- JSON array is marked via Lua metatable, so that when re-encoded with +-- JSON:encode() it ends up as the appropriate JSON type. +-- +-- (This is not the default because other routines may not work well with +-- tables that have a metatable set, for example, Lightroom API calls.) +-- +-- +-- ENCODING (from a lua table to a JSON string) +-- +-- JSON = assert(loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- local custom_pretty = JSON:encode(lua_table_or_value, etc, { pretty = true, indent = "| ", align_keys = false }) +-- +-- On error during encoding, this code calls: +-- +-- JSON:onEncodeError(message, etc) +-- +-- which you can override in your local JSON object. +-- +-- The 'etc' in the error call is the second argument to encode() +-- and encode_pretty(), or nil if it wasn't provided. +-- +-- +-- PRETTY-PRINTING +-- +-- An optional third argument, a table of options, allows a bit of +-- configuration about how the encoding takes place: +-- +-- pretty = JSON:encode(val, etc, { +-- pretty = true, -- if false, no other options matter +-- indent = " ", -- this provides for a three-space indent per nesting level +-- align_keys = false, -- see below +-- }) +-- +-- encode() and encode_pretty() are identical except that encode_pretty() +-- provides a default options table if none given in the call: +-- +-- { pretty = true, align_keys = false, indent = " " } +-- +-- For example, if +-- +-- JSON:encode(data) +-- +-- produces: +-- +-- {"city":"Kyoto","climate":{"avg_temp":16,"humidity":"high","snowfall":"minimal"},"country":"Japan","wards":11} +-- +-- then +-- +-- JSON:encode_pretty(data) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- The following three lines return identical results: +-- JSON:encode_pretty(data) +-- JSON:encode_pretty(data, nil, { pretty = true, align_keys = false, indent = " " }) +-- JSON:encode (data, nil, { pretty = true, align_keys = false, indent = " " }) +-- +-- An example of setting your own indent string: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = "| " }) +-- +-- produces: +-- +-- { +-- | "city": "Kyoto", +-- | "climate": { +-- | | "avg_temp": 16, +-- | | "humidity": "high", +-- | | "snowfall": "minimal" +-- | }, +-- | "country": "Japan", +-- | "wards": 11 +-- } +-- +-- An example of setting align_keys to true: +-- +-- JSON:encode_pretty(data, nil, { pretty = true, indent = " ", align_keys = true }) +-- +-- produces: +-- +-- { +-- "city": "Kyoto", +-- "climate": { +-- "avg_temp": 16, +-- "humidity": "high", +-- "snowfall": "minimal" +-- }, +-- "country": "Japan", +-- "wards": 11 +-- } +-- +-- which I must admit is kinda ugly, sorry. This was the default for +-- encode_pretty() prior to version 20141223.14. +-- +-- +-- AMBIGUOUS SITUATIONS DURING THE ENCODING +-- +-- During the encode, if a Lua table being encoded contains both string +-- and numeric keys, it fits neither JSON's idea of an object, nor its +-- idea of an array. To get around this, when any string key exists (or +-- when non-positive numeric keys exist), numeric keys are converted to +-- strings. +-- +-- For example, +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- produces the JSON object +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To prohibit this conversion and instead make it an error condition, set +-- JSON.noKeyConversion = true +-- + + + + +-- +-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT +-- +-- assert +-- onDecodeError +-- onDecodeOfNilError +-- onDecodeOfHTMLError +-- onEncodeError +-- +-- If you want to create a separate Lua JSON object with its own error handlers, +-- you can reload JSON.lua or use the :new() method. +-- +--------------------------------------------------------------------------- + +local default_pretty_indent = " " +local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } + +local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray +local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject + + +function OBJDEF:newArray(tbl) + return setmetatable(tbl or {}, isArray) +end + +function OBJDEF:newObject(tbl) + return setmetatable(tbl or {}, isObject) +end + +local function unicode_codepoint_as_utf8(codepoint) + -- + -- codepoint is a number + -- + if codepoint <= 127 then + return string.char(codepoint) + + elseif codepoint <= 2047 then + -- + -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 + -- + local highpart = math.floor(codepoint / 0x40) + local lowpart = codepoint - (0x40 * highpart) + return string.char(0xC0 + highpart, + 0x80 + lowpart) + + elseif codepoint <= 65535 then + -- + -- 1110yyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x1000) + local remainder = codepoint - 0x1000 * highpart + local midpart = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midpart + + highpart = 0xE0 + highpart + midpart = 0x80 + midpart + lowpart = 0x80 + lowpart + + -- + -- Check for an invalid character (thanks Andy R. at Adobe). + -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 + -- + if ( highpart == 0xE0 and midpart < 0xA0 ) or + ( highpart == 0xED and midpart > 0x9F ) or + ( highpart == 0xF0 and midpart < 0x90 ) or + ( highpart == 0xF4 and midpart > 0x8F ) + then + return "?" + else + return string.char(highpart, + midpart, + lowpart) + end + + else + -- + -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x40000) + local remainder = codepoint - 0x40000 * highpart + local midA = math.floor(remainder / 0x1000) + remainder = remainder - 0x1000 * midA + local midB = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midB + + return string.char(0xF0 + highpart, + 0x80 + midA, + 0x80 + midB, + 0x80 + lowpart) + end +end + +function OBJDEF:onDecodeError(message, text, location, etc) + if text then + if location then + message = string.format("%s at char %d of: %s", message, location, text) + else + message = string.format("%s: %s", message, text) + end + end + + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError +OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError + +function OBJDEF:onEncodeError(message, etc) + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +local function grok_number(self, text, start, etc) + -- + -- Grab the integer part + -- + local integer_part = text:match('^-?[1-9]%d*', start) + or text:match("^-?0", start) + + if not integer_part then + self:onDecodeError("expected number", text, start, etc) + end + + local i = start + integer_part:len() + + -- + -- Grab an optional decimal part + -- + local decimal_part = text:match('^%.%d+', i) or "" + + i = i + decimal_part:len() + + -- + -- Grab an optional exponential part + -- + local exponent_part = text:match('^[eE][-+]?%d+', i) or "" + + i = i + exponent_part:len() + + local full_number_text = integer_part .. decimal_part .. exponent_part + local as_number = tonumber(full_number_text) + + if not as_number then + self:onDecodeError("bad number", text, start, etc) + end + + return as_number, i +end + + +local function grok_string(self, text, start, etc) + + if text:sub(start,start) ~= '"' then + self:onDecodeError("expected string's opening quote", text, start, etc) + end + + local i = start + 1 -- +1 to bypass the initial quote + local text_len = text:len() + local VALUE = "" + while i <= text_len do + local c = text:sub(i,i) + if c == '"' then + return VALUE, i + 1 + end + if c ~= '\\' then + VALUE = VALUE .. c + i = i + 1 + elseif text:match('^\\b', i) then + VALUE = VALUE .. "\b" + i = i + 2 + elseif text:match('^\\f', i) then + VALUE = VALUE .. "\f" + i = i + 2 + elseif text:match('^\\n', i) then + VALUE = VALUE .. "\n" + i = i + 2 + elseif text:match('^\\r', i) then + VALUE = VALUE .. "\r" + i = i + 2 + elseif text:match('^\\t', i) then + VALUE = VALUE .. "\t" + i = i + 2 + else + local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if hex then + i = i + 6 -- bypass what we just read + + -- We have a Unicode codepoint. It could be standalone, or if in the proper range and + -- followed by another in a specific range, it'll be a two-code surrogate pair. + local codepoint = tonumber(hex, 16) + if codepoint >= 0xD800 and codepoint <= 0xDBFF then + -- it's a hi surrogate... see whether we have a following low + local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if lo_surrogate then + i = i + 6 -- bypass the low surrogate we just read + codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) + else + -- not a proper low, so we'll just leave the first codepoint as is and spit it out. + end + end + VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) + + else + + -- just pass through what's escaped + VALUE = VALUE .. text:match('^\\(.)', i) + i = i + 2 + end + end + end + + self:onDecodeError("unclosed string", text, start, etc) +end + +local function skip_whitespace(text, start) + + local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 + if match_end then + return match_end + 1 + else + return start + end +end + +local grok_one -- assigned later + +local function grok_object(self, text, start, etc) + if text:sub(start,start) ~= '{' then + self:onDecodeError("expected '{'", text, start, etc) + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' + + local VALUE = self.strictTypes and self:newObject { } or { } + + if text:sub(i,i) == '}' then + return VALUE, i + 1 + end + local text_len = text:len() + while i <= text_len do + local key, new_i = grok_string(self, text, i, etc) + + i = skip_whitespace(text, new_i) + + if text:sub(i, i) ~= ':' then + self:onDecodeError("expected colon", text, i, etc) + end + + i = skip_whitespace(text, i + 1) + + local new_val, new_i = grok_one(self, text, i) + + VALUE[key] = new_val + + -- + -- Expect now either '}' to end things, or a ',' to allow us to continue. + -- + i = skip_whitespace(text, new_i) + + local c = text:sub(i,i) + + if c == '}' then + return VALUE, i + 1 + end + + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '}'", text, i, etc) + end + + i = skip_whitespace(text, i + 1) + end + + self:onDecodeError("unclosed '{'", text, start, etc) +end + +local function grok_array(self, text, start, etc) + if text:sub(start,start) ~= '[' then + self:onDecodeError("expected '['", text, start, etc) + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' + local VALUE = self.strictTypes and self:newArray { } or { } + if text:sub(i,i) == ']' then + return VALUE, i + 1 + end + + local VALUE_INDEX = 1 + + local text_len = text:len() + while i <= text_len do + local val, new_i = grok_one(self, text, i) + + -- can't table.insert(VALUE, val) here because it's a no-op if val is nil + VALUE[VALUE_INDEX] = val + VALUE_INDEX = VALUE_INDEX + 1 + + i = skip_whitespace(text, new_i) + + -- + -- Expect now either ']' to end things, or a ',' to allow us to continue. + -- + local c = text:sub(i,i) + if c == ']' then + return VALUE, i + 1 + end + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '['", text, i, etc) + end + i = skip_whitespace(text, i + 1) + end + self:onDecodeError("unclosed '['", text, start, etc) +end + + +grok_one = function(self, text, start, etc) + -- Skip any whitespace + start = skip_whitespace(text, start) + + if start > text:len() then + self:onDecodeError("unexpected end of string", text, nil, etc) + end + + if text:find('^"', start) then + return grok_string(self, text, start, etc) + + elseif text:find('^[-0123456789 ]', start) then + return grok_number(self, text, start, etc) + + elseif text:find('^%{', start) then + return grok_object(self, text, start, etc) + + elseif text:find('^%[', start) then + return grok_array(self, text, start, etc) + + elseif text:find('^true', start) then + return true, start + 4 + + elseif text:find('^false', start) then + return false, start + 5 + + elseif text:find('^null', start) then + return nil, start + 4 + + else + self:onDecodeError("can't parse JSON", text, start, etc) + end +end + +function OBJDEF:decode(text, etc) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc) + end + + if text == nil then + self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc) + elseif type(text) ~= 'string' then + self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc) + end + + if text:match('^%s*$') then + return nil + end + + if text:match('^%s*<') then + -- Can't be JSON... we'll assume it's HTML + self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc) + end + + -- + -- Ensure that it's not UTF-32 or UTF-16. + -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), + -- but this package can't handle them. + -- + if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then + self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc) + end + + local success, value = pcall(grok_one, self, text, 1, etc) + + if success then + return value + else + -- if JSON:onDecodeError() didn't abort out of the pcall, we'll have received the error message here as "value", so pass it along as an assert. + if self.assert then + self.assert(false, value) + else + assert(false, value) + end + -- and if we're still here, return a nil and throw the error message on as a second arg + return nil, value + end +end + +local function backslash_replacement_function(c) + if c == "\n" then + return "\\n" + elseif c == "\r" then + return "\\r" + elseif c == "\t" then + return "\\t" + elseif c == "\b" then + return "\\b" + elseif c == "\f" then + return "\\f" + elseif c == '"' then + return '\\"' + elseif c == '\\' then + return '\\\\' + else + return string.format("\\u%04x", c:byte()) + end +end + +local chars_to_be_escaped_in_JSON_string + = '[' + .. '"' -- class sub-pattern to match a double quote + .. '%\\' -- class sub-pattern to match a backslash + .. '%z' -- class sub-pattern to match a null + .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters + .. ']' + +local function json_string_literal(value) + local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) + return '"' .. newval .. '"' +end + +local function object_or_array(self, T, etc) + -- + -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON + -- object. If there are only numbers, it's a JSON array. + -- + -- If we'll be converting to a JSON object, we'll want to sort the keys so that the + -- end result is deterministic. + -- + local string_keys = { } + local number_keys = { } + local number_keys_must_be_strings = false + local maximum_number_key + + for key in pairs(T) do + if type(key) == 'string' then + table.insert(string_keys, key) + elseif type(key) == 'number' then + table.insert(number_keys, key) + if key <= 0 or key >= math.huge then + number_keys_must_be_strings = true + elseif not maximum_number_key or key > maximum_number_key then + maximum_number_key = key + end + else + self:onEncodeError("can't encode table with a key of type " .. type(key), etc) + end + end + + if #string_keys == 0 and not number_keys_must_be_strings then + -- + -- An empty table, or a numeric-only array + -- + if #number_keys > 0 then + return nil, maximum_number_key -- an array + elseif tostring(T) == "JSON array" then + return nil + elseif tostring(T) == "JSON object" then + return { } + else + -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects + return nil + end + end + + table.sort(string_keys) + + local map + if #number_keys > 0 then + -- + -- If we're here then we have either mixed string/number keys, or numbers inappropriate for a JSON array + -- It's not ideal, but we'll turn the numbers into strings so that we can at least create a JSON object. + -- + + if self.noKeyConversion then + self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) + end + + -- + -- Have to make a shallow copy of the source table so we can remap the numeric keys to be strings + -- + map = { } + for key, val in pairs(T) do + map[key] = val + end + + table.sort(number_keys) + + -- + -- Throw numeric keys in there as strings + -- + for _, number_key in ipairs(number_keys) do + local string_key = tostring(number_key) + if map[string_key] == nil then + table.insert(string_keys , string_key) + map[string_key] = T[number_key] + else + self:onEncodeError("conflict converting table with mixed-type keys into a JSON object: key " .. number_key .. " exists both as a string and a number.", etc) + end + end + end + + return string_keys, nil, map +end + +-- +-- Encode +-- +-- 'options' is nil, or a table with possible keys: +-- pretty -- if true, return a pretty-printed version +-- indent -- a string (usually of spaces) used to indent each nested level +-- align_keys -- if true, align all the keys when formatting a table +-- +local encode_value -- must predeclare because it calls itself +function encode_value(self, value, parents, etc, options, indent) + + if value == nil then + return 'null' + + elseif type(value) == 'string' then + return json_string_literal(value) + + elseif type(value) == 'number' then + if value ~= value then + -- + -- NaN (Not a Number). + -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. + -- + return "null" + elseif value >= math.huge then + -- + -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should + -- really be a package option. Note: at least with some implementations, positive infinity + -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. + -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" + -- case first. + -- + return "1e+9999" + elseif value <= -math.huge then + -- + -- Negative infinity. + -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. + -- + return "-1e+9999" + else + return tostring(value) + end + + elseif type(value) == 'boolean' then + return tostring(value) + + elseif type(value) ~= 'table' then + self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) + + else + -- + -- A table to be converted to either a JSON object or array. + -- + local T = value + + if type(options) ~= 'table' then + options = {} + end + if type(indent) ~= 'string' then + indent = "" + end + + if parents[T] then + self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) + else + parents[T] = true + end + + local result_value + + local object_keys, maximum_number_key, map = object_or_array(self, T, etc) + if maximum_number_key then + -- + -- An array... + -- + local ITEMS = { } + for i = 1, maximum_number_key do + table.insert(ITEMS, encode_value(self, T[i], parents, etc, options, indent)) + end + + if options.pretty then + result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" + else + result_value = "[" .. table.concat(ITEMS, ",") .. "]" + end + + elseif object_keys then + -- + -- An object + -- + local TT = map or T + + if options.pretty then + + local KEYS = { } + local max_key_length = 0 + for _, key in ipairs(object_keys) do + local encoded = encode_value(self, tostring(key), parents, etc, options, indent) + if options.align_keys then + max_key_length = math.max(max_key_length, #encoded) + end + table.insert(KEYS, encoded) + end + local key_indent = indent .. tostring(options.indent or "") + local subtable_indent = key_indent .. string.rep(" ", max_key_length) .. (options.align_keys and " " or "") + local FORMAT = "%s%" .. string.format("%d", max_key_length) .. "s: %s" + + local COMBINED_PARTS = { } + for i, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, subtable_indent) + table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) + end + result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" + + else + + local PARTS = { } + for _, key in ipairs(object_keys) do + local encoded_val = encode_value(self, TT[key], parents, etc, options, indent) + local encoded_key = encode_value(self, tostring(key), parents, etc, options, indent) + table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) + end + result_value = "{" .. table.concat(PARTS, ",") .. "}" + + end + else + -- + -- An empty array/object... we'll treat it as an array, though it should really be an option + -- + result_value = "[]" + end + + parents[T] = false + return result_value + end +end + + +function OBJDEF:encode(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) + end + return encode_value(self, value, {}, etc, options or nil) +end + +function OBJDEF:encode_pretty(value, etc, options) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode_pretty must be called in method format", etc) + end + return encode_value(self, value, {}, etc, options or default_pretty_options) +end + +function OBJDEF.__tostring() + return "JSON encode/decode package" +end + +OBJDEF.__index = OBJDEF + +function OBJDEF:new(args) + local new = { } + + if args then + for key, val in pairs(args) do + new[key] = val + end + end + + return setmetatable(new, OBJDEF) +end + +JSON = OBJDEF:new() + +-- +-- Version history: +-- +-- 20141223.14 The encode_pretty() routine produced fine results for small datasets, but isn't really +-- appropriate for anything large, so with help from Alex Aulbach I've made the encode routines +-- more flexible, and changed the default encode_pretty() to be more generally useful. +-- +-- Added a third 'options' argument to the encode() and encode_pretty() routines, to control +-- how the encoding takes place. +-- +-- Updated docs to add assert() call to the loadfile() line, just as good practice so that +-- if there is a problem loading JSON.lua, the appropriate error message will percolate up. +-- +-- 20140920.13 Put back (in a way that doesn't cause warnings about unused variables) the author string, +-- so that the source of the package, and its version number, are visible in compiled copies. +-- +-- 20140911.12 Minor lua cleanup. +-- Fixed internal reference to 'JSON.noKeyConversion' to reference 'self' instead of 'JSON'. +-- (Thanks to SmugMug's David Parry for these.) +-- +-- 20140418.11 JSON nulls embedded within an array were being ignored, such that +-- ["1",null,null,null,null,null,"seven"], +-- would return +-- {1,"seven"} +-- It's now fixed to properly return +-- {1, nil, nil, nil, nil, nil, "seven"} +-- Thanks to "haddock" for catching the error. +-- +-- 20140116.10 The user's JSON.assert() wasn't always being used. Thanks to "blue" for the heads up. +-- +-- 20131118.9 Update for Lua 5.3... it seems that tostring(2/1) produces "2.0" instead of "2", +-- and this caused some problems. +-- +-- 20131031.8 Unified the code for encode() and encode_pretty(); they had been stupidly separate, +-- and had of course diverged (encode_pretty didn't get the fixes that encode got, so +-- sometimes produced incorrect results; thanks to Mattie for the heads up). +-- +-- Handle encoding tables with non-positive numeric keys (unlikely, but possible). +-- +-- If a table has both numeric and string keys, or its numeric keys are inappropriate +-- (such as being non-positive or infinite), the numeric keys are turned into +-- string keys appropriate for a JSON object. So, as before, +-- JSON:encode({ "one", "two", "three" }) +-- produces the array +-- ["one","two","three"] +-- but now something with mixed key types like +-- JSON:encode({ "one", "two", "three", SOMESTRING = "some string" })) +-- instead of throwing an error produces an object: +-- {"1":"one","2":"two","3":"three","SOMESTRING":"some string"} +-- +-- To maintain the prior throw-an-error semantics, set +-- JSON.noKeyConversion = true +-- +-- 20131004.7 Release under a Creative Commons CC-BY license, which I should have done from day one, sorry. +-- +-- 20130120.6 Comment update: added a link to the specific page on my blog where this code can +-- be found, so that folks who come across the code outside of my blog can find updates +-- more easily. +-- +-- 20111207.5 Added support for the 'etc' arguments, for better error reporting. +-- +-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. +-- +-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: +-- +-- * When encoding lua for JSON, Sparse numeric arrays are now handled by +-- spitting out full arrays, such that +-- JSON:encode({"one", "two", [10] = "ten"}) +-- returns +-- ["one","two",null,null,null,null,null,null,null,"ten"] +-- +-- In 20100810.2 and earlier, only up to the first non-null value would have been retained. +-- +-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". +-- Version 20100810.2 and earlier created invalid JSON in both cases. +-- +-- * Unicode surrogate pairs are now detected when decoding JSON. +-- +-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding +-- +-- 20100731.1 initial public release +-- diff --git a/addons/itemstore/lua/itemstore/admin.lua b/addons/itemstore/lua/itemstore/admin.lua new file mode 100644 index 0000000..17313d2 --- /dev/null +++ b/addons/itemstore/lua/itemstore/admin.lua @@ -0,0 +1,38 @@ +if SERVER then + util.AddNetworkString( "ItemStoreAdminInventory" ) + net.Receive( "ItemStoreAdminInventory", function( len, admin ) + if not admin:IsSuperAdmin() then return end + + local pl = net.ReadEntity() + + if not IsValid( pl ) then return end + if not pl.Inventory then return end + + pl.Inventory:SetPermissions( admin, true, true ) + pl.Inventory:Sync( admin ) + admin:OpenContainer( pl.Inventory:GetID(), itemstore.Translate( "players_inventory", pl:Name() ) ) + end ) + + util.AddNetworkString( "ItemStoreAdminBank" ) + net.Receive( "ItemStoreAdminBank", function( len, admin ) + if not admin:IsSuperAdmin() then return end + + local pl = net.ReadEntity() + + if not IsValid( pl ) then return end + if not pl.Bank then return end + + pl.Bank:SetPermissions( admin, true, true ) + pl.Bank:Sync( admin ) + admin:OpenContainer( pl.Bank:GetID(), itemstore.Translate( "players_bank", pl:Name() ) ) + end ) +else + concommand.Add( "itemstore_admin", function( pl ) + if not pl:IsSuperAdmin() then return end + + local panel = vgui.Create( "ItemStoreAdmin" ) + panel:SetSize( 200, 300 ) + panel:Center() + panel:MakePopup() + end ) +end diff --git a/addons/itemstore/lua/itemstore/cl_gui.lua b/addons/itemstore/lua/itemstore/cl_gui.lua new file mode 100644 index 0000000..e10535e --- /dev/null +++ b/addons/itemstore/lua/itemstore/cl_gui.lua @@ -0,0 +1,15 @@ +include( "skins/" .. itemstore.config.Skin .. ".lua" ) + +for _, filename in ipairs( file.Find( "itemstore/vgui/*.lua", "LUA" ) ) do + include( "vgui/" .. filename ) +end + +hook.Add( "ContextMenuCreated", "ItemStoreInventory", function( context ) + if not IsValid( context ) then return end + + context:Receiver( "ItemStore", function( receiver, droppable, dropped ) + if not dropped then return end + + LocalPlayer():DropItem( droppable[ 1 ]:GetContainerID(), droppable[ 1 ]:GetSlot() ) + end ) +end ) diff --git a/addons/itemstore/lua/itemstore/cl_init.lua b/addons/itemstore/lua/itemstore/cl_init.lua new file mode 100644 index 0000000..c0f2aec --- /dev/null +++ b/addons/itemstore/lua/itemstore/cl_init.lua @@ -0,0 +1,4 @@ +include( "shared.lua" ) + +include( "cl_gui.lua" ) +include( "cl_player.lua" ) \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/cl_player.lua b/addons/itemstore/lua/itemstore/cl_player.lua new file mode 100644 index 0000000..4fd9219 --- /dev/null +++ b/addons/itemstore/lua/itemstore/cl_player.lua @@ -0,0 +1,136 @@ +local meta = FindMetaTable( "Player" ) + +function meta:MoveItem( from_con_id, from_slot, to_con_id, to_slot ) + net.Start( "ItemStoreMove" ) + net.WriteUInt( from_con_id, 32 ) + net.WriteUInt( from_slot, 32 ) + net.WriteUInt( to_con_id, 32 ) + net.WriteUInt( to_slot, 32 ) + net.SendToServer() +end + +function meta:UseItem( con_id, slot, ... ) + net.Start( "ItemStoreUse" ) + net.WriteUInt( con_id, 32 ) + net.WriteUInt( slot, 32 ) + net.WriteTable( { ... } ) + net.SendToServer() +end + +function meta:UseItemWith( from_con_id, from_slot, to_con_id, to_slot ) + net.Start( "ItemStoreUseWith" ) + net.WriteUInt( from_con_id, 32 ) + net.WriteUInt( from_slot, 32 ) + net.WriteUInt( to_con_id, 32 ) + net.WriteUInt( to_slot, 32 ) + net.SendToServer() +end + +function meta:DropItem( con_id, slot ) + net.Start( "ItemStoreDrop" ) + net.WriteUInt( con_id, 32 ) + net.WriteUInt( slot, 32 ) + net.SendToServer() +end + +function meta:DestroyItem( con_id, slot ) + net.Start( "ItemStoreDestroy" ) + net.WriteUInt( con_id, 32 ) + net.WriteUInt( slot, 32 ) + net.SendToServer() +end + +function meta:MergeItem( from_con_id, from_slot, to_con_id, to_slot ) + net.Start( "ItemStoreMerge" ) + net.WriteUInt( from_con_id, 32 ) + net.WriteUInt( from_slot, 32 ) + net.WriteUInt( to_con_id, 32 ) + net.WriteUInt( to_slot, 32 ) + net.SendToServer() +end + +function meta:SplitItem( con_id, slot, amount ) + net.Start( "ItemStoreSplit" ) + net.WriteUInt( con_id, 32 ) + net.WriteUInt( slot, 32 ) + net.WriteUInt( amount, 16 ) + net.SendToServer() +end + +hook.Add( "InitPostEntity", "ItemStoreRequestInventory", function() + net.Start( "ItemStoreSyncInventory" ) net.SendToServer() +end ) + +local ContextInventory + +net.Receive( "ItemStoreSyncInventory", function() + LocalPlayer().InventoryID = net.ReadUInt( 32 ) + + if not itemstore.config.ContextInventory then return end + + local inv = vgui.Create( "ItemStoreContainerWindow", g_ContextMenu ) + inv:SetTitle( itemstore.Translate( "inventory" ) ) + inv:SetContainerID( LocalPlayer().InventoryID ) + inv:ShowCloseButton( false ) + inv:SetDraggable( false ) + inv:InvalidateLayout( true ) + + local side = itemstore.config.ContextInventoryPosition + if side == "bottom" then + inv:SetPos( ScrW() / 2 - inv:GetWide() / 2, ScrH() - inv:GetTall() ) + elseif side == "top" then + inv:SetPos( ScrW() / 2 - inv:GetWide() / 2, 0 ) + elseif side == "left" then + inv:SetPos( 0, ScrH() / 2 - inv:GetTall() / 2 ) + elseif side == "right" then + inv:SetPos( ScrW() - inv:GetWide(), ScrH() / 2 - inv:GetTall() / 2 ) + end + + ContextInventory = inv +end ) + +hook.Add( "Tick", "ItemStoreHideContextInventory", function() + if not IsValid( LocalPlayer() ) then return end + if not IsValid( ContextInventory ) then return end + + ContextInventory:SetVisible( LocalPlayer():CanUseInventory() ) +end ) + +net.Receive( "ItemStoreOpen", function() + local id = net.ReadUInt( 32 ) + local name = net.ReadString() + local hideinv = net.ReadBit() == 1 + + local con = itemstore.containers.Get( id ) + if not con then return end + + local panel = vgui.Create( "ItemStoreContainerWindow" ) + + panel:SetContainerID( id ) + panel:SetTitle( name ) + panel:Center() + + panel:MakePopup() + + if not hideinv then + local inv = vgui.Create( "ItemStoreContainerWindow" ) + inv:SetContainerID( LocalPlayer().InventoryID ) + inv:SetTitle( itemstore.Translate( "inventory" ) ) + inv:ShowCloseButton( false ) + inv:MakePopup() + inv:InvalidateLayout( true ) + + local think = inv.Think + function inv:Think() + think( self ) + + local x, y = panel:GetPos() + inv:SetPos( panel:GetPos() + ( panel:GetWide() / 2 - inv:GetWide() / 2 ), + y + panel:GetTall() + 10 ) + end + + function panel:OnClose() + inv:Close() + end + end +end ) \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/config.lua b/addons/itemstore/lua/itemstore/config.lua new file mode 100644 index 0000000..790aa31 --- /dev/null +++ b/addons/itemstore/lua/itemstore/config.lua @@ -0,0 +1,176 @@ +-- The maximum allowable size for stacked items. Set to math.huge for infinite stacks. +-- SOME ITEMS DO NOT OBEY THIS CONFIG OPTION!! Ammo and money are exempt for obvious reasons. +itemstore.config.MaxStack = 5 + +-- Where to save player data. Values are none, text, mysqloo (recommended) and tmysql4 (deprecated) +itemstore.config.DataProvider = "text" + +-- If true, saves the player's inventory every time it's changed. +-- DO NOT TURN THIS OFF IF YOU'RE RUNNING THE mysql.experimental DATA PROVIDER!! +itemstore.config.SaveOnWrite = true + +-- The gamemode to enable support for. Valid values are darkrp and darkrp24. +itemstore.config.GamemodeProvider = "darkrp" + +-- Prefix for chat commands +itemstore.config.ChatCommandPrefix = "/" + +-- The jobs that have access to an inventory. If this is empty, all teams have access. +-- Admins will still have access to their inventory though. +-- Names must be exact. +-- example: itemstore.config.LimitToTeams = { TEAM_CITIZEN, TEAM_COP } +itemstore.config.LimitToJobs = {} + +-- The interval at which the inventory saves all players automatically, in seconds. +itemstore.config.SaveInterval = 180 + +-- The language of the inventory. +-- There are two languages by default, en (English), fr (French), de (German) and ru (Russian) +itemstore.config.Language = "ru" + +-- Enable quick inventory viewing by holding the context menu key, default C. +itemstore.config.ContextInventory = true + +-- If context inventory is enabled, this defines where it appears on the player's screen. +-- Valid values are "top", "bottom", "left" and "right" +itemstore.config.ContextInventoryPosition = "bottom" + +-- Allow the use of the /invholster command +itemstore.config.EnableInvholster = false + +-- Force player to holster all of their ammo as well as their gun when they use /invholster, ala DarkRP. +itemstore.config.InvholsterTakesAmmo = false + +-- Split ammo on spawned_weapons instead of giving all ammo at once when used +itemstore.config.SplitWeaponAmmo = true + +-- Force player to retrieve their items from the bank before being able to use them. +itemstore.config.PickupsGotoBank = false + +-- The distance that the player is able to "reach" when picking up items. +itemstore.config.PickupDistance = 150 + +-- The distance that items will drop at relative to the player +itemstore.config.DropDistance = 100 + +-- The key to use in combination with +use (E) to pick up items. +-- A list of keys for this option is here: http://wiki.garrysmod.com/page/Enums/IN +-- Set this to -1 to disable the key combo. +itemstore.config.PickupKey = IN_DUCK + +-- Whether or not trading should be enabled. Set this to false to disable. +itemstore.config.TradingEnabled = true + +-- How long in seconds the player needs to wait after a trade to trade again +itemstore.config.TradeCooldown = 60 + +-- How close in hammer units two players need to be to trade. 0 means infinite. +itemstore.config.TradeDistance = 0 + +-- Whether or not the player should drop their inventory on death. +itemstore.config.DeathLoot = false + +-- How long in seconds the player's dropped inventory should exist for. +itemstore.config.DeathLootTimeout = 60 * 5 + +-- Makes boxes breakable if enough damage is inflicted +itemstore.config.BoxBreakable = false + +-- Amount of health for boxes to have +itemstore.config.BoxHealth = 100 + +-- Should users be able to pick up other users' entities +itemstore.config.IgnoreOwner = true + +-- Fixes a duplication bug by detouring ENTITY:Remove().. +-- WARNING: Turning this off will open an exploit that allows players to dupe items! +-- Only turn it off if it is somehow conflicting. +itemstore.config.AntiDupe = true + +-- Migrates text data from 2.0 to the current format. +-- This is experimental and may not function correctly. Please be careful if you decide to use this. +-- !!IMPORTANT!! +-- PLEASE make backups of your data -- this process is DESTRUCTIVE and will delete old data files +-- and overwrite any inventory data that players currently have. +itemstore.config.MigrateOldData = false + +-- Inventory sizes according to rank. +-- The format for this table is: +-- = { , , } +-- If a player's rank is not contained within this table, it defaults to default. +-- DO NOT REMOVE DEFAULT! If you remove it, there will be errors! +itemstore.config.InventorySizes = { + default = { 8, 2, 2 }, +} + +-- Same as above, for banks. Same format. DON'T REMOVE DEFAULT! +itemstore.config.BankSizes = { + default = { 8, 2, 2 }, +} + +-- The skin to use. Preinstalled skins are "flat" and "classic". +itemstore.config.Skin = "flat" + +-- The various colours of the VGUI in R, G, B, A 0-255 format. +-- Not available when using the flat skin +itemstore.config.Colours = { + Slot = Color( 29, 26, 31 ), + HoveredSlot = Color( 255, 255, 255, 150 ), + Title = Color( 255, 255, 255 ), + + TitleBackground = Color( 255, 255, 255 ), + Upper = Color( 100, 100, 100, 100 ), + Lower = Color( 30, 30, 30, 150 ), + InnerBorder = Color( 0, 0, 0, 0 ), + OuterBorder = Color( 0, 0, 0, 200 ) +} + +-- The style of the item highlight. Options are "old", "border", "corner", subtle" and "full" +itemstore.config.HighlightStyle = "corner" + +-- Highlight colours for the various types of items. +itemstore.config.HighlightColours = { + Weapons = Color( 231, 76, 60 ), + Ammo = Color( 241, 196, 15 ), + Shipments = Color( 230, 126, 34 ), + Factories = Color( 52, 152, 219 ), -- printers, gunlabs, microwaves, etc + Consumables = Color( 26, 188, 156 ), -- drugs, food + Money = Color( 46, 204, 113 ), + Other = Color( 236, 240, 241 ), -- never delete this! +} + +-- A table of disabled items. Set any value in this table to true to disallow picking up the item. +itemstore.config.DisabledItems = { + drug = false, + drug_lab = false, + food = false, + gunlab = false, + microwave = false, + money_printer = true, + spawned_food = false, + spawned_shipment = false, + spawned_weapon = false, + spawned_money = true, + + durgz_alcohol = false, + durgz_aspirin = false, + durgz_cigarette = false, + durgz_cocaine = false, + durgz_heroine = false, + durgz_lsd = false, + durgz_mushroom = false, + durgz_pcp = false, + durgz_weed = false, + + + prop_physics = true, +} + +-- Custom items. Defining these will allow server owners to make certain +-- entities pickupable... but may not work 100%. If this is the case, you will probably +-- need to code the item definition yourself. +-- Format for each entry is: +-- = { "", "", } +itemstore.config.CustomItems = { + sent_ball = { "Bouncy Ball", "A bouncy ball!", true }, +} diff --git a/addons/itemstore/lua/itemstore/containers.lua b/addons/itemstore/lua/itemstore/containers.lua new file mode 100644 index 0000000..03542c9 --- /dev/null +++ b/addons/itemstore/lua/itemstore/containers.lua @@ -0,0 +1,392 @@ +itemstore.containers = {} +itemstore.containers.Active = {} + +local Container = {} + +AccessorFunc( Container, "Owner", "Owner" ) +AccessorFunc( Container, "Suppressed", "Suppressed", FORCE_BOOL ) +AccessorFunc( Container, "Width", "Width", FORCE_NUMBER ) +AccessorFunc( Container, "Height", "Height", FORCE_NUMBER ) +AccessorFunc( Container, "Pages", "Pages", FORCE_NUMBER ) + +function Container:GetID() + return self.ID +end + +function Container:IsValid() + return self:GetID() and itemstore.containers.Active[ self:GetID() ] == self +end + +function Container:Remove() + itemstore.containers.Remove( self:GetID() ) +end + +function Container:GetPageSize() + return self:GetWidth() * self:GetHeight() +end + +function Container:GetPageFromSlot( slot ) + return math.ceil( slot / self:GetPageSize() ) +end + +function Container:GetSize() + return self:GetPageSize() * self:GetPages() +end + +function Container:CoordsToSlot( x, y, p ) + return ( ( p - 1 ) * self:GetPageSize() ) + ( ( y - 1 ) * self:GetWidth() + x ) +end + +function Container:GetItems() + return self.Items +end + +function Container:GetItem( slot ) + return self.Items[ slot ] +end + +function Container:SetItem( slot, item ) + if item and not item:IsValid() then return end + + slot = math.floor( slot ) + + if slot >= 1 and slot <= self:GetSize() then + if self:RunCallbacks( "set", slot, item ) == false then return end + + self.Items[ slot ] = item + + if item then + item.Container = self + item.Slot = slot + end + + if SERVER then self:QueueSync() end + end +end + +function Container:FirstEmptySlot() + for i = 1, self:GetSize() do + if not self:GetItem( i ) then + return i + end + end + + return false +end + +function Container:CanFit( item ) + for i = 1, self:GetSize() do + local merge_item = self:GetItem( i ) + + if merge_item and merge_item:CanMerge( item ) then + return true + end + end + + return self:FirstEmptySlot() ~= false +end + +function Container:AddItem( item, dontmerge ) + if not dontmerge then + for i = 1, self:GetSize() do + local merge_item = self:GetItem( i ) + + if merge_item and merge_item:CanMerge( item ) then + merge_item:Merge( item ) + self:QueueSync() + + return i, merge_item + end + end + end + + local slot = self:FirstEmptySlot() + + if slot then + self:SetItem( slot, item ) + return slot + end + + return false +end + +function Container:HasItem( item_class ) + for k, v in pairs( self:GetItems() ) do + if v:GetClass() == item_class then + return k + end + end + + return false +end + +function Container:CountItems( item_class ) + local amount = 0 + + for _, item in pairs( self:GetItems() ) do + if item:GetClass() == item_class then + amount = amount + item:GetAmount() + end + end + + return amount +end + +-- 76561198068291318 + +function Container:TakeItems( item_class, amount ) + local amount_taken = 0 + + self:Suppress( function() + for k, v in pairs( self:GetItems() ) do + if v:GetClass() == item_class then + local amount_to_take = v:GetAmount() + amount_to_take = math.Clamp( amount_to_take, 0, amount ) + + v:SetAmount( v:GetAmount() - amount_to_take ) + + if v:GetAmount() <= 0 then + self:SetItem( k, nil ) + end + + amount_taken = amount_taken + amount_to_take + amount = amount - amount_to_take + end + end + + return true + end ) + + return amount_taken +end + +function Container:GetDefaultPermissions() + return self.DefaultPermissions +end + +function Container:GetPermissions( pl ) + return self.Permissions[ pl ] or self:GetDefaultPermissions() +end + +function Container:SetDefaultPermissions( read, write ) + self.DefaultPermissions = { Read = read, Write = write } +end + +function Container:SetPermissions( pl, read, write ) + self.Permissions[ pl ] = { Read = read, Write = write } +end + +function Container:CanRead( pl, ... ) + local res = hook.Call( "ItemStoreCanRead", nil, self, pl, ... ) + if res ~= nil then + return res + end + + if self.Permissions[ pl ] then + return self.Permissions[ pl ].Read + end + + local res = self:RunCallbacks( "read", pl, ... ) + if res ~= nil then + return res + end + + return self.DefaultPermissions.Read +end + +function Container:CanWrite( pl, action, ... ) + local res = hook.Call( "ItemStoreCanWrite", nil, self, pl, action, ... ) + if res ~= nil then + return res + end + + if self.Permissions[ pl ] then + return self.Permissions[ pl ].Write + end + + local res = self:RunCallbacks( "write", pl, action, ... ) + if res ~= nil then + return res + end + + return self.DefaultPermissions.Write +end + +function Container:AddCallback( name, func ) + if not self.Callbacks[ name ] then + self.Callbacks[ name ] = {} + end + + return table.insert( self.Callbacks[ name ], func ) +end + +function Container:RemoveCallback( name, id ) + if self.Callbacks[ name ] then + self.Callbacks[ name ][ id ] = nil + end +end + +function Container:RunCallbacks( name, ... ) + if self.Callbacks[ name ] then + for _, func in pairs( self.Callbacks[ name ] ) do + local res = func( self, ... ) + + if res ~= nil then + return res + end + end + end +end + +function Container:GetSyncTargets() + players = {} + + for _, pl in ipairs( player.GetAll() ) do + if self:CanRead( pl ) then + table.insert( players, pl ) + end + end + + return players +end + +function Container:Suppress( func ) + self:SetSuppressed( true ) + local sync = func() + self:SetSuppressed( false ) + + if sync then self:QueueSync() end +end + +function Container:QueueSync() + self.ShouldSync = true +end + +function Container:Sync( pl ) + if SERVER then itemstore.containers.Sync( self:GetID(), pl ) end +end + +function itemstore.Container( w, h, pages, dontnetwork ) + local con = { + ShouldSync = false, + + Width = w or 4, + Height = h or 4, + Pages = pages or 1, + + Owner = nil, + Callbacks = {}, + Permissions = {}, + DefaultPermissions = { Read = false, Write = false }, + + Items = {} + } + + setmetatable( con, { __index = Container } ) + + if not dontnetwork then + con.ID = table.insert( itemstore.containers.Active, con ) + con:Sync() + end + + return con +end + +function itemstore.containers.Get( id ) + return itemstore.containers.Active[ id ] +end + +function itemstore.containers.Remove( id ) + itemstore.containers.Active[ id ] = nil +end + +if SERVER then + AddCSLuaFile() + + util.AddNetworkString( "ItemStoreSync" ) + function itemstore.containers.Sync( id, pl ) + local con = itemstore.containers.Active[ id ] + + if not con then return end + if con:GetSuppressed() then return end + + -- No longer using WriteTable! Net usage has been cut to less than half + -- This is still pretty damn unoptimized though + -- Ideally we send only the item that's changing... + -- But unfortunately it's a bit more complicated than that due to + -- an item's ability to modify other slots in a container + net.Start( "ItemStoreSync" ) + net.WriteUInt( con:GetID(), 32 ) + + net.WriteUInt( con:GetWidth(), 8 ) + net.WriteUInt( con:GetHeight(), 8 ) + net.WriteUInt( con:GetPages(), 8 ) + + net.WriteUInt( table.Count( con.Items ), 8 ) + + for k, v in pairs( con.Items ) do + net.WriteUInt( k, 8 ) + + local id = util.NetworkStringToID( v.Class ) + + if id == 0 then + if v.Class then + error( string.format( "[ItemStore] Tried to send data for unnetworked item %s", v.Class ) ) + else + error( "[ItemStore] Tried to send data for a classless item" ) + end + end + + net.WriteUInt( id, 16 ) + v:WriteNetworkData() + end + net.Send( pl or con:GetSyncTargets() ) + end + + hook.Add( "Tick", "ItemStoreSyncContainers", function() + for k, v in pairs( itemstore.containers.Active ) do + if v.ShouldSync then + v.ShouldSync = false + v:Sync() + end + end + end ) +else + itemstore.containers.Panels = {} + + function itemstore.containers.UpdatePanels( id ) + for k, v in pairs( itemstore.containers.Panels ) do + if IsValid( v ) then + if v:GetContainerID() == id then + v:Refresh() + end + else + itemstore.containers.Panels[ k ] = nil + end + end + end + + net.Receive( "ItemStoreSync", function() + local id = net.ReadUInt( 32 ) + local w, h = net.ReadUInt( 8 ), net.ReadUInt( 8 ) + local pages = net.ReadUInt( 8 ) + + local con = itemstore.Container( w, h, pages, true ) + + for i = 1, net.ReadUInt( 8 ) do + local slot = net.ReadUInt( 8 ) + + local class = util.NetworkIDToString( net.ReadUInt( 16 ) ) + local item = itemstore.Item( class ) + + item:ReadNetworkData() + + con:SetItem( slot, item ) + end + + con.ID = id + itemstore.containers.Active[ id ] = con + + itemstore.containers.UpdatePanels( id ) + end ) +end diff --git a/addons/itemstore/lua/itemstore/dataproviders/mysqloo.lua b/addons/itemstore/lua/itemstore/dataproviders/mysqloo.lua new file mode 100644 index 0000000..ad7f56d --- /dev/null +++ b/addons/itemstore/lua/itemstore/dataproviders/mysqloo.lua @@ -0,0 +1,431 @@ +local PROVIDER = PROVIDER + +-- Edit this with your authentication details +local auth = { + Host = "localhost", + Port = 3306, + Username = "root", + Password = "", + Database = "itemstore" +} + +local INVENTORY_DB = "Inventories" +local BANK_DB = "Banks" +local DEBUG = false + +function PROVIDER:Log( text ) + print( "[ItemStore MySQL] " .. text ) + file.Append( "itemstore/mysql.txt", os.date( "[%c]" ) .. " " .. text .. "\r\n" ) +end + +function PROVIDER:CreateTable( name, columns, primary ) + self:Log( "Creating tables.." ) + + local sql = "CREATE TABLE IF NOT EXISTS `" .. name .. "`(" + + for k, v in ipairs( columns ) do + sql = sql .. v + + if k ~= #columns then + sql = sql .. ", " + end + end + + if primary then + sql = sql .. ", PRIMARY KEY ( " .. primary .. " )" + end + + sql = sql .. " )" + + return self:Query( sql ) +end + +function PROVIDER:OnConnected( db ) + self.Database = db + + self:Log( "Connection succesful!" ) + + local columns = { + "`SteamID` VARCHAR( 32 ) NOT NULL", + "`Slot` INT NOT NULL", + "`Class` VARCHAR( 255 ) NULL", + "`Data` BLOB NULL", + } + + local primary = "`SteamID`, `Slot`" + + self:CreateTable( INVENTORY_DB, columns, primary ) + self:CreateTable( BANK_DB, columns, primary ) + + self:RunQueue() + -- I know I shouldn't store Data as a plain JSON string but I'm concerned about performance here. + --self:Query( "CREATE TABLE IF NOT EXISTS Inventories( SteamID VARCHAR( 32 ) NOT NULL, Slot INT NOT NULL, Class VARCHAR( 255 ) NULL, Data BLOB NULL, PRIMARY KEY ( SteamID, Slot ) )" ) + --self:Query( "CREATE TABLE IF NOT EXISTS Banks( SteamID VARCHAR( 32 ) NOT NULL, Slot INT NOT NULL, Class VARCHAR( 255 ) NULL, Data BLOB NULL, PRIMARY KEY ( SteamID, Slot ) )" ) +end + +function PROVIDER:OnError( err ) + self:Log( "Connection failed: " .. err ) + self:Log( "Retrying in 30 seconds: " .. err ) + + timer.Simple( 30, function() + self:Initialize() + end ) +end + +function PROVIDER:Initialize() + require( "mysqloo" ) + assert( mysqloo, "[ItemStore] MySQLoo is not installed" ) + + self:Log( "Connecting to database..." ) + + local db = mysqloo.connect( auth.Host, auth.Username, auth.Password, auth.Database, auth.Port ) + + db.onConnected = function( db ) + self:OnConnected( db ) + end + + db.onConnectionFailed = function( db, err ) + self:OnError( err ) + end + + db:connect() +end + +function PROVIDER:Escape( str ) + return self.Database:escape( str ) +end + +PROVIDER.QueuedQueries = {} + +function PROVIDER:RunQueue() + if not self.Database then return end + + for k, v in ipairs( self.QueuedQueries ) do + self:Query( unpack( v ) ) + end + + self.QueuedQueries = {} +end + +function PROVIDER:Query( sql, params, success, fail ) + if not success then + local success = function() end + end + + if not fail then + local fail = function( q, err ) + self:Log( "Query failed: " .. err ) + end + end + + if not self.Database then + if DEBUG then + self:Log( "Database not connected yet, queueing query: " .. sql ) + end + + -- wait for connection if we don't have a db yet + -- mysqloo docs say this should work anyways but :escape() will fail if not connected + table.insert( self.QueuedQueries, { sql, params, success, fail } ) + return + end + + if params then + for k, v in pairs( params ) do + if type( v ) ~= "number" then + v = self:Escape( v ) + v = "\"" .. v .. "\"" + end + + sql = string.gsub( sql, ":" .. k, v ) + end + end + + if DEBUG then + self:Log( "Starting query: " .. sql ) + end + + local q = self.Database:query( sql ) + + q.onSuccess = success + q.onError = fail + + q:start() +end + +function PROVIDER:LoadInventory( pl ) + local steamid = pl:SteamID64() or "0" + + local sql = "SELECT * FROM `" .. INVENTORY_DB .. "`" + sql = sql .. " WHERE `SteamID` = :1" + + local params = { steamid } + + self:Query( sql, params, function( q, data ) + if not IsValid( pl ) then return end + + for _, v in ipairs( data ) do + local slot = v.Slot + + if v.Class then + local class = v.Class + local data = util.JSONToTable( v.Data ) + + pl.Inventory:SetItem( slot, itemstore.Item( class, data ) ) + end + end + + pl.InventoryLoaded = true + end ) +end + +function PROVIDER:SaveInventory( pl ) + local steamid = pl:SteamID64() or "0" + + local sql = "REPLACE INTO `" .. INVENTORY_DB .. "`" + sql = sql .. "( `SteamID`, `Slot`, `Class`, `Data` )" + sql = sql .. " VALUES" + + local params = {} + params[ "steamid" ] = steamid + + for slot = 1, pl.Inventory:GetSize() do + local item = pl.Inventory:GetItem( slot ) + + sql = sql .. "( " + sql = sql .. " :steamid," + sql = sql .. " " .. slot .. "," -- no need to escape here since it's a number + + if item then + sql = sql .. " :item_" .. slot .. "_class," + sql = sql .. " :item_" .. slot .. "_data" + + params[ "item_" .. slot .. "_class" ] = item:GetClass() + params[ "item_" .. slot .. "_data" ] = util.TableToJSON( item.Data ) + else + sql = sql .. " NULL," + sql = sql .. " NULL" + end + + sql = sql .. " )" + + if slot ~= pl.Inventory:GetSize() then + sql = sql .. "," + end + end + + self:Query( sql, params ) +end + +function PROVIDER:LoadBank( pl ) + local steamid = pl:SteamID64() or "0" + + local sql = "SELECT * FROM `" .. BANK_DB .. "`" + sql = sql .. " WHERE `SteamID` = :1" + + local params = { steamid } + + self:Query( sql, params, function( q, data ) + if not IsValid( pl ) then return end + + for _, v in ipairs( data ) do + local slot = v.Slot + + if v.Class then + local class = v.Class + local data = util.JSONToTable( v.Data ) + + pl.Bank:SetItem( slot, itemstore.Item( class, data ) ) + end + end + + pl.BankLoaded = true + end ) +end + +function PROVIDER:SaveBank( pl ) + local steamid = pl:SteamID64() or "0" + + local params = {} + params[ "steamid" ] = steamid + + local sql = "REPLACE INTO `" .. BANK_DB .. "`" + sql = sql .. "( `SteamID`, `Slot`, `Class`, `Data` )" + sql = sql .. " VALUES" + + for slot = 1, pl.Bank:GetSize() do + local item = pl.Bank:GetItem( slot ) + + sql = sql .. "( " + sql = sql .. " :steamid," + sql = sql .. " " .. slot .. "," -- no need to escape here since it's a number + + if item then + sql = sql .. " :item_" .. slot .. "_class," + sql = sql .. " :item_" .. slot .. "_data" + + params[ "item_" .. slot .. "_class" ] = item:GetClass() + params[ "item_" .. slot .. "_data" ] = util.TableToJSON( item.Data ) + else + sql = sql .. " NULL," + sql = sql .. " NULL" + end + + sql = sql .. " )" + + if slot ~= pl.Bank:GetSize() then + sql = sql .. "," + end + end + + self:Query( sql, params ) +end + +function PROVIDER:Import( data ) + for k, v in pairs( data ) do + self:Query( "DELETE FROM `" .. INVENTORY_DB .. "` WHERE SteamID = :1", { k } ) + self:Query( "DELETE FROM `" .. BANK_DB .. "` WHERE SteamID = :1", { k } ) + + if v.Inventory and table.Count( v.Inventory ) > 0 then + local sql = "REPLACE INTO `" .. INVENTORY_DB .. "`" + sql = sql .. "( `SteamID`, `Slot`, `Class`, `Data` )" + sql = sql .. " VALUES" + + local params = {} + params[ "steamid" ] = k + + for slot, item in pairs( v.Inventory ) do + sql = sql .. "( " + sql = sql .. " :steamid," + sql = sql .. " " .. tonumber( slot ) .. "," + + if item then + sql = sql .. " :item_" .. slot .. "_class," + sql = sql .. " :item_" .. slot .. "_data" + + params[ "item_" .. slot .. "_class" ] = item.Class + params[ "item_" .. slot .. "_data" ] = util.TableToJSON( item.Data ) + else + sql = sql .. " NULL," + sql = sql .. " NULL" + end + + sql = sql .. " )," + end + + sql = string.TrimRight( sql, "," ) + + self:Query( sql, params ) + + --for slot, item in pairs( v.Inventory ) do + -- if item then + -- self:Query( "REPLACE INTO Inventories( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + -- else + -- self:Query( "REPLACE INTO Inventories( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + -- end + --end + end + + if v.Bank and table.Count( v.Bank ) > 0 then + local sql = "REPLACE INTO `" .. BANK_DB .. "`" + sql = sql .. "( `SteamID`, `Slot`, `Class`, `Data` )" + sql = sql .. " VALUES" + + local params = {} + params[ "steamid" ] = k + + for slot, item in pairs( v.Bank ) do + sql = sql .. "( " + sql = sql .. " :steamid," + sql = sql .. " " .. tonumber( slot ) .. "," + + if item then + sql = sql .. " :item_" .. slot .. "_class," + sql = sql .. " :item_" .. slot .. "_data" + + params[ "item_" .. slot .. "_class" ] = item.Class + params[ "item_" .. slot .. "_data" ] = util.TableToJSON( item.Data ) + else + sql = sql .. " NULL," + sql = sql .. " NULL" + end + + sql = sql .. " )," + end + + sql = string.TrimRight( sql, "," ) + + self:Query( sql, params ) + + --if v.Bank then + -- for slot, item in pairs( v.Bank ) do + -- if item then + -- self:Query( "REPLACE INTO Banks( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + -- else + -- self:Query( "REPLACE INTO Banks( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + -- end + -- end + --end + end + end + + return true +end + +function PROVIDER:Export( filename ) + local export = {} + + local inventory_loaded = false + local bank_loaded = false + + local function FinishExport( export ) + file.Write( filename, util.TableToJSON( export ) ) + end + + local sql = "SELECT * FROM `" .. INVENTORY_DB .. "`" + + self:Query( sql, nil, function( q, data ) + for _, row in ipairs( data ) do + if not ( export[ row.SteamID ] ) then + export[ row.SteamID ] = {} + end + + if not ( export[ row.SteamID ].Inventory ) then + export[ row.SteamID ].Inventory = {} + end + + if row.Class then + export[ row.SteamID ].Inventory[ row.Slot ] = { Class = row.Class, Data = util.JSONToTable( row.Data ) } + end + end + + inventory_loaded = true + + if inventory_loaded and bank_loaded then + FinishExport( export ) + end + end ) + + local sql = "SELECT * FROM `" .. BANK_DB .. "`" + + self:Query( sql, nil, function( q, data ) + for _, row in ipairs( data ) do + if not ( export[ row.SteamID ] ) then + export[ row.SteamID ] = {} + end + + if not ( export[ row.SteamID ].Bank ) then + export[ row.SteamID ].Bank = {} + end + + if row.Class then + export[ row.SteamID ].Bank[ row.Slot ] = { Class = row.Class, Data = util.JSONToTable( row.Data ) } + end + end + + bank_loaded = true + + if inventory_loaded and bank_loaded then + FinishExport( export ) + end + end ) +end diff --git a/addons/itemstore/lua/itemstore/dataproviders/none.lua b/addons/itemstore/lua/itemstore/dataproviders/none.lua new file mode 100644 index 0000000..2a0643a --- /dev/null +++ b/addons/itemstore/lua/itemstore/dataproviders/none.lua @@ -0,0 +1,17 @@ +function PROVIDER:LoadInventory( pl ) +end + +function PROVIDER:SaveInventory( pl ) +end + +function PROVIDER:LoadBank( pl ) +end + +function PROVIDER:SaveBank( pl ) +end + +function PROVIDER:Import( data ) +end + +function PROVIDER:Export( filename ) +end diff --git a/addons/itemstore/lua/itemstore/dataproviders/text.lua b/addons/itemstore/lua/itemstore/dataproviders/text.lua new file mode 100644 index 0000000..eb7ab8e --- /dev/null +++ b/addons/itemstore/lua/itemstore/dataproviders/text.lua @@ -0,0 +1,90 @@ +function PROVIDER:LoadInventory( pl ) + local json = file.Read( "itemstore/" .. ( pl:SteamID64() or "0" ) .. ".txt", "DATA" ) + + if json then + local inv = util.JSONToTable( json ) + + if inv then + for k, v in pairs( inv ) do + pl.Inventory:SetItem( k, itemstore.Item( v.Class, v.Data ) ) + end + end + end + + pl.InventoryLoaded = true +end + +function PROVIDER:SaveInventory( pl ) + local inv = {} + + for k, v in pairs( pl.Inventory:GetItems() ) do + inv[ k ] = { Class = v.Class, Data = v.Data } + end + + file.Write( "itemstore/" .. ( pl:SteamID64() or "0" ) .. ".txt", util.TableToJSON( inv ) ) +end + +function PROVIDER:LoadBank( pl ) + local json = file.Read( "itemstore/" .. ( pl:SteamID64() or "0" ) .. "_bank.txt", "DATA" ) + + if json then + local bank = util.JSONToTable( json ) + + if bank then + for k, v in pairs( bank ) do + pl.Bank:SetItem( k, itemstore.Item( v.Class, v.Data ) ) + end + end + end + + pl.BankLoaded = true +end + +function PROVIDER:SaveBank( pl ) + local inv = {} + + for k, v in pairs( pl.Bank:GetItems() ) do + inv[ k ] = { Class = v.Class, Data = v.Data } + end + + file.Write( "itemstore/" .. ( pl:SteamID64() or "0" ) .. "_bank.txt", util.TableToJSON( inv ) ) +end + +function PROVIDER:Import( data ) + for k, v in pairs( data ) do + file.Write( "itemstore/" .. k .. ".txt", util.TableToJSON( data.Inventory ) ) + file.Write( "itemstore/" .. k .. "_bank.txt", util.TableToJSON( data.Bank ) ) + end + + return true +end + +function PROVIDER:Export( filename ) + local export = {} + + for _, filename in ipairs( file.Find( "itemstore/*.txt", "DATA" ) ) do + local data = util.JSONToTable( file.Read( "itemstore/" .. filename, "DATA" ) ) + + if data then + local inv_steamid = string.match( filename, "(%d+).txt" ) + local bank_steamid = string.match( filename, "(%d+)_bank.txt" ) + local steamid = inv_steamid or bank_steamid + local isbank = steamid == bank_steamid + + -- you shouldn't put random shit in itemstore's data folder but just in case + if steamid then + if not export[ steamid ] then + export[ steamid ] = {} + end + + if isbank then + export[ steamid ].Bank = data + else + export[ steamid ].Inventory = data + end + end + end + end + + file.Write( filename, util.TableToJSON( export ) ) +end diff --git a/addons/itemstore/lua/itemstore/dataproviders/tmysql4.lua b/addons/itemstore/lua/itemstore/dataproviders/tmysql4.lua new file mode 100644 index 0000000..64d54db --- /dev/null +++ b/addons/itemstore/lua/itemstore/dataproviders/tmysql4.lua @@ -0,0 +1,242 @@ +local PROVIDER = PROVIDER + +-- Edit this with your authentication details +local auth = { + Host = "localhost", + Port = 3306, + Username = "root", + Password = "", + Database = "itemstore" +} + +function PROVIDER:Log( text ) + print( "[ItemStore MySQL] " .. text ) + file.Append( "itemstore/mysql.txt", os.date( "[%c]" ) .. " " .. text .. "\r\n" ) +end + +function PROVIDER:OnConnected() + self:Log( "Connection succesful!" ) + + -- I know I shouldn't store Data as a plain JSON string but I'm concerned about performance here. + self:Query( "CREATE TABLE IF NOT EXISTS Inventories( SteamID VARCHAR( 32 ) NOT NULL, Slot INT NOT NULL, Class VARCHAR( 255 ) NULL, Data BLOB NULL, PRIMARY KEY ( SteamID, Slot ) )" ) + self:Query( "CREATE TABLE IF NOT EXISTS Banks( SteamID VARCHAR( 32 ) NOT NULL, Slot INT NOT NULL, Class VARCHAR( 255 ) NULL, Data BLOB NULL, PRIMARY KEY ( SteamID, Slot ) )" ) +end + +function PROVIDER:OnError( err ) + self:Log( "Connection failed: " .. err ) + + timer.Simple( 30, function() + self:Initialize() + end ) +end + +function PROVIDER:Initialize() + require( "tmysql4" ) + assert( tmysql, "[ItemStore] TMySQL4 is not installed" ) + + self:Log( "Connecting to database..." ) + + local db, err = tmysql.initialize( auth.Host, auth.Username, auth.Password, auth.Database, auth.Port ) + + if err then + PROVIDER:OnError( err ) + else + PROVIDER.Database = db + PROVIDER:OnConnected() + end +end + +-- tmysql is so nice, no need for some stupid query buffer in case the database d/cs +function PROVIDER:Query( sql, params, success, fail ) + if not success then + function success( data ) end + end + + if not fail then + function fail( err ) self:Log( "Query failed: " .. err ) end + end + + if not self.Database then + fail( "Database not initialized" ) + return + end + + if params then + for k, v in pairs( params ) do + if type( v ) ~= "number" then + v = self.Database:Escape( v ) + v = "'" .. v .. "'" + end + + sql = string.gsub( sql, ":" .. k, v ) + end + end + + self.Database:Query( sql, function( results ) + if results[ 1 ].status then + success( results[ 1 ].data ) + else + fail( results[ 1 ].error ) + end + end ) +end + +function PROVIDER:LoadInventory( pl ) + local steamid = pl:SteamID64() or "0" + + self:Query( "SELECT * FROM Inventories WHERE SteamID = :1", { steamid }, function( data ) + if not IsValid( pl ) then return end + + for _, v in ipairs( data ) do + local slot = v.Slot + + if v.Class then + local class = v.Class + local data = util.JSONToTable( v.Data ) + + pl.Inventory:SetItem( slot, itemstore.Item( class, data ) ) + end + end + + pl.InventoryLoaded = true + end ) +end + +function PROVIDER:SaveInventory( pl ) + local steamid = pl:SteamID64() or "0" + + for slot = 1, pl.Inventory:GetSize() do + local item = pl.Inventory:GetItem( slot ) + + if item then + local class = item:GetClass() + local data = util.TableToJSON( item.Data ) + + self:Query( "REPLACE INTO Inventories( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { steamid, slot, class, data } ) + else + self:Query( "REPLACE INTO Inventories( SteamID, Slot, Class, Data ) VALUES( :1, :2, NULL, NULL )", { steamid, slot } ) + end + end +end + +function PROVIDER:LoadBank( pl ) + local steamid = pl:SteamID64() or "0" + + self:Query( "SELECT * FROM Banks WHERE SteamID = :1", { steamid }, function( data ) + if not IsValid( pl ) then return end + + for _, v in ipairs( data ) do + local slot = v.Slot + + if v.Class then + local class = v.Class + local data = util.JSONToTable( v.Data ) + + pl.Bank:SetItem( slot, itemstore.Item( class, data ) ) + end + end + + pl.BankLoaded = true + end ) +end + +function PROVIDER:SaveBank( pl ) + local steamid = pl:SteamID64() or "0" + + for slot = 1, pl.Bank:GetSize() do + local item = pl.Bank:GetItem( slot ) + + if item then + local class = item:GetClass() + local data = util.TableToJSON( item.Data ) + + self:Query( "REPLACE INTO Banks( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { steamid, slot, class, data } ) + else + self:Query( "REPLACE INTO Banks( SteamID, Slot, Class, Data ) VALUES( :1, :2, NULL, NULL )", { steamid, slot } ) + end + end +end + +function PROVIDER:Import( data ) + for k, v in pairs( data ) do + self:Query( "DELETE FROM Inventories WHERE SteamID = :1", { k } ) + self:Query( "DELETE FROM Banks WHERE SteamID = :1", { k } ) + + if v.Inventory then + for slot, item in pairs( v.Inventory ) do + if item then + self:Query( "REPLACE INTO Inventories( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + else + self:Query( "REPLACE INTO Inventories( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + end + end + end + + if v.Bank then + for slot, item in pairs( v.Bank ) do + if item then + self:Query( "REPLACE INTO Banks( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + else + self:Query( "REPLACE INTO Banks( SteamID, Slot, Class, Data ) VALUES( :1, :2, :3, :4 )", { k, slot, item.Class, util.TableToJSON( item.Data ) } ) + end + end + end + end + + return true +end + +function PROVIDER:Export( filename ) + local export = {} + + local inventory_loaded = false + local bank_loaded = false + + local function FinishExport( export ) + file.Write( filename, util.TableToJSON( export ) ) + end + + self:Query( "SELECT * FROM Inventories", nil, function( data ) + for _, row in ipairs( data ) do + if not ( export[ row.SteamID ] ) then + export[ row.SteamID ] = {} + end + + if not ( export[ row.SteamID ].Inventory ) then + export[ row.SteamID ].Inventory = {} + end + + if row.Class then + export[ row.SteamID ].Inventory[ row.Slot ] = { Class = row.Class, Data = util.JSONToTable( row.Data ) } + end + end + + inventory_loaded = true + + if inventory_loaded and bank_loaded then + FinishExport( export ) + end + end ) + + self:Query( "SELECT * FROM Banks", nil, function( data ) + for _, row in ipairs( data ) do + if not ( export[ row.SteamID ] ) then + export[ row.SteamID ] = {} + end + + if not ( export[ row.SteamID ].Bank ) then + export[ row.SteamID ].Bank = {} + end + + if row.Class then + export[ row.SteamID ].Bank[ row.Slot ] = { Class = row.Class, Data = util.JSONToTable( row.Data ) } + end + end + + bank_loaded = true + + if inventory_loaded and bank_loaded then + FinishExport( export ) + end + end ) +end diff --git a/addons/itemstore/lua/itemstore/gamemodeproviders/darkrp.lua b/addons/itemstore/lua/itemstore/gamemodeproviders/darkrp.lua new file mode 100644 index 0000000..673e9ac --- /dev/null +++ b/addons/itemstore/lua/itemstore/gamemodeproviders/darkrp.lua @@ -0,0 +1,70 @@ +function PROVIDER:GetMoney( pl ) + return pl:getDarkRPVar( "money" ) +end + +if SERVER then + function PROVIDER:SetMoney( pl, money ) + pl:setDarkRPVar( "money", money ) + if DarkRP.storeMoney then DarkRP.storeMoney( pl, money ) end + end + + if itemstore.config.EnableInvholster then + itemstore.AddCommand( "invholster", function( pl, args ) + if not pl:CanUseInventory() then return end + + local wep = pl:GetActiveWeapon() + + -- This code is not mine, I'm simply copypasting DarkRP stuff right here. + if not IsValid(wep) or not wep:GetModel() or wep:GetModel() == "" then + DarkRP.notify(pl, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return + end + + if itemstore.config.DisabledItems[ wep:GetClass() ] then + DarkRP.notify(pl, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return + end + + if GAMEMODE.Config.restrictdrop then + local found = false + for k,v in pairs(CustomShipments) do + if v.entity == wep:GetClass() then + found = true + break + end + end + + if not found then return end + end + + local canDrop = hook.Call("canDropWeapon", GAMEMODE, pl, wep) + if not canDrop then + DarkRP.notify(pl, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return + end + -- and back to our regularly scheduled coding + + local item = itemstore.Item( "spawned_weapon" ) + + item:SetData( "Class", wep:GetClass() ) + item:SetData( "Amount", 1 ) + item:SetData( "Model", wep:GetModel() ) + item:SetData( "Clip1", wep:Clip1() ) + item:SetData( "Clip2", wep:Clip2() ) + + if itemstore.config.InvholsterTakesAmmo then + local ammotype = wep:GetPrimaryAmmoType() + + if ammotype and ammotype ~= "none" then -- to be entirely honest I'm not sure if it returns nil or "none" + local ammo = pl:GetAmmoCount( ammotype ) + + item:SetData( "Ammo", ammo ) + pl:RemoveAmmo( ammo, ammotype ) + end + end + + pl:StripWeapon( wep:GetClass() ) + pl.Inventory:AddItem( item ) + end ) + end +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/gamemodeproviders/darkrp24.lua b/addons/itemstore/lua/itemstore/gamemodeproviders/darkrp24.lua new file mode 100644 index 0000000..1d82fe9 --- /dev/null +++ b/addons/itemstore/lua/itemstore/gamemodeproviders/darkrp24.lua @@ -0,0 +1,69 @@ +function PROVIDER:GetMoney( pl ) + return pl:getDarkRPVar( "money" ) +end + +if SERVER then + function PROVIDER:SetMoney( pl, money ) + return pl:SetDarkRPVar( "money", money ) + end + + if itemstore.config.EnableInvholster then + itemstore.AddCommand( "invholster", function( pl, args ) + if not pl:CanUseInventory() then return end + + local wep = pl:GetActiveWeapon() + + -- This code is not mine, I'm simply copypasting DarkRP stuff right here. + if not IsValid(wep) or not wep:GetModel() or wep:GetModel() == "" then + DarkRP.notify(pl, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return + end + + if itemstore.config.DisabledItems[ wep:GetClass() ] then + DarkRP.notify(pl, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return + end + + if GAMEMODE.Config.restrictdrop then + local found = false + for k,v in pairs(CustomShipments) do + if v.entity == wep:GetClass() then + found = true + break + end + end + + if not found then return end + end + + local canDrop = hook.Call("canDropWeapon", GAMEMODE, pl, wep) + if not canDrop then + DarkRP.notify(pl, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return + end + -- and back to our regularly scheduled coding + + local item = itemstore.Item( "spawned_weapon" ) + + item:SetData( "Class", wep:GetClass() ) + item:SetData( "Amount", 1 ) + item:SetData( "Model", wep:GetModel() ) + item:SetData( "Clip1", wep:Clip1() ) + item:SetData( "Clip2", wep:Clip2() ) + + if itemstore.config.InvholsterTakesAmmo then + local ammotype = wep:GetPrimaryAmmoType() + + if ammotype and ammotype ~= "none" then -- to be entirely honest I'm not sure if it returns nil or "none" + local ammo = pl:GetAmmoCount( ammotype ) + + item:SetData( "Ammo", ammo ) + pl:RemoveAmmo( ammo, ammotype ) + end + end + + pl:StripWeapon( wep:GetClass() ) + pl.Inventory:AddItem( item ) + end ) + end +end diff --git a/addons/itemstore/lua/itemstore/gamemodes.lua b/addons/itemstore/lua/itemstore/gamemodes.lua new file mode 100644 index 0000000..9ae61d8 --- /dev/null +++ b/addons/itemstore/lua/itemstore/gamemodes.lua @@ -0,0 +1,103 @@ +itemstore.gamemodes = {} + +PROVIDER = {} +include( "gamemodeproviders/" .. itemstore.config.GamemodeProvider .. ".lua" ) +if SERVER then AddCSLuaFile( "gamemodeproviders/" .. itemstore.config.GamemodeProvider .. ".lua" ) end +itemstore.gamemodes.Provider = PROVIDER +PROVIDER = nil + +assert( itemstore.gamemodes.Provider, "[ItemStore] Gamemode provider not found" ) + +function itemstore.gamemodes.Run( func_name, ... ) + local func = itemstore.gamemodes.Provider[ func_name ] + if not func then return end + + return func( itemstore.gamemodes.Provider, ... ) +end + +function itemstore.gamemodes.GetMoney( pl ) + return itemstore.gamemodes.Run( "GetMoney", pl ) +end + +if SERVER then + function itemstore.gamemodes.SetMoney( pl, money ) + return itemstore.gamemodes.Run( "SetMoney", pl, money ) + end + + function itemstore.gamemodes.GiveMoney( pl, money ) + return itemstore.gamemodes.SetMoney( pl, itemstore.gamemodes.GetMoney( pl ) + money ) + end + + function itemstore.gamemodes.AddCommand( command, info, func ) + return itemstore.gamemodes.Run( "AddCommand", command, info, func ) + end + + itemstore.AddCommand( "trade", function( pl, args ) + args = table.concat( args, " " ) + + if not pl:CanUseInventory() then + pl:ChatPrint( itemstore.Translate( "cant_access_inventory" ) ) + return + end + + if not itemstore.config.TradingEnabled then + return + end + + if pl.TradingCooldown and pl.TradingCooldown > CurTime() then + pl:ChatPrint( itemstore.Translate( "trading_cooldown" ) ) + return + end + + local target = pl:GetEyeTrace().Entity + + if args ~= "" then + for _, pl in ipairs( player.GetAll() ) do + if string.find( string.lower( pl:Name() ), string.lower( args ), 1, true ) then + target = pl + end + end + end + + if not IsValid( target ) or not target:IsPlayer() then + pl:ChatPrint( itemstore.Translate( "player_not_found" ) ) + return + end + + if not target:CanUseInventory() then + pl:ChatPrint( itemstore.Translate( "partner_cant_access_inventory" ) ) + return + end + + if pl.Trade then + pl:ChatPrint( itemstore.Translate( "already_in_trade" ) ) + return + end + + if target.Trade then + pl:ChatPrint( itemstore.Translate( "partner_is_in_trade" ) ) + return + end + + if itemstore.config.TradeDistance ~= 0 and pl:GetPos():Distance( target:GetPos() ) > itemstore.config.TradeDistance then + pl:ChatPrint( itemstore.Translate( "too_far_away" ) ) + return + end + + itemstore.Trade( pl, target ) + + pl.TradingCooldown = CurTime() + itemstore.config.TradeCooldown + end ) + + itemstore.AddCommand( "inv", function( pl ) + pl:OpenContainer( pl.Inventory:GetID(), itemstore.Translate( "inventory" ), true ) + end ) + + itemstore.AddCommand( "pickup", function( pl ) + pl:PickupItem() + end ) +end + +hook.Add( "PostGamemodeLoaded", "ItemStoreGamemodeLoad", function() + itemstore.gamemodes.Run( "Load" ) +end ) diff --git a/addons/itemstore/lua/itemstore/items.lua b/addons/itemstore/lua/itemstore/items.lua new file mode 100644 index 0000000..a9a41b9 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items.lua @@ -0,0 +1,273 @@ +itemstore.items = {} +itemstore.items.Registered = {} +itemstore.items.Pickups = {} + +local Item = {} +Item.Name = "Invalid Item" +--Item.Description = "Invalid Description" +Item.Model = "models/error.mdl" +--Item.Skin = 0 +--Item.Color = nil +--Item.Material = nil +Item.HighlightColor = itemstore.config.HighlightColours.Other + +Item.Stackable = false +Item.Amount = 1 +Item.MaxStack = itemstore.config.MaxStack +Item.DropStack = false + +Item.DontNetwork = {} + +function Item:GetClass() + return self.Class +end + +function Item:IsValid() + return true +end + +function Item:Run( func_name, ... ) + local func = self[ func_name ] + + if type( func ) ~= "function" then return end + + return func( self, ... ) +end + +function Item:Load() +end + +function Item:Initialize() +end + +function Item:GetContainer() + local con = self.Container + if not con then return end + + local slot = self.Slot + if not slot then return end + + if con:GetItem( slot ) == self then return self.Container end +end + +function Item:GetSlot() + local con = self.Container + if not con then return end + + local slot = self.Slot + if not slot then return end + + if con:GetItem( slot ) == self then return self.Slot end +end + +function Item:Copy() + return itemstore.Item( self:GetClass(), table.Copy( self.Data ) ) +end + +function Item:RegisterPickup( ent_class ) + itemstore.items.Pickups[ ent_class ] = self:GetClass() +end + +function Item:GetData( key, default ) + return self.Data[ key ] == nil and default or self.Data[ key ] +end + +function Item:SetData( key, value ) + self.Data[ key ] = value +end + +function Item:CreateMutator( key, default ) + self[ "Set" .. key ] = function( self, value ) + self:SetData( key, value ) + end + + self[ "Get" .. key ] = function( self, default ) + return self:GetData( key, default or self[ key ] ) + end +end + +Item:CreateMutator( "Name" ) +Item:CreateMutator( "Description" ) +Item:CreateMutator( "Model" ) +Item:CreateMutator( "Material" ) +Item:CreateMutator( "Skin" ) +Item:CreateMutator( "Color" ) +Item:CreateMutator( "MaxStack" ) +Item:CreateMutator( "Amount" ) + +function Item:GetStaticName() + return self.StaticName or self.Name +end + +function Item:Pickup( pl, con, slot, ent ) +end + +function Item:Drop( pl, con, slot, ent ) +end + +function Item:Destroy( pl, con, slot ) +end + +function Item:TakeOne() + self:SetAmount( self:GetAmount() - 1 ) + + if self:GetAmount() <= 0 then + return true + end + + return false +end + +function Item:CanPickup() + return true +end + +function Item:CanUseWith( item ) + return false +end + +function Item:CanMerge( item ) + return self.Stackable and item.Stackable and self:GetClass() == item:GetClass() + and ( self:GetAmount() + item:GetAmount() ) <= self:GetMaxStack() +end + +function Item:Merge( item ) + self:SetAmount( self:GetAmount() + item:GetAmount() ) +end + +function Item:CanSplit( amount ) + return self.Stackable and self:GetAmount() > amount +end + +function Item:Split( amount ) + self:SetAmount( self:GetAmount() - amount ) + + local item = self:Copy() + item:SetAmount( amount ) + + return item +end + +function Item:FormatAmount() + return "x" .. self:GetAmount() +end + +function Item:CreateEntity( pos ) + local ent = ents.Create( "itemstore_item" ) + ent:SetPos( pos ) + self:LoadData( ent ) + ent:Spawn() + + return ent +end + +function Item:SaveData( ent ) +end + +function Item:LoadData( ent ) + ent:SetItem( self ) +end + +function Item:WriteNetworkData() + local data = {} + + for k, v in pairs( self.Data ) do + if not self.DontNetwork[ k ] then data[ k ] = v end + end + + net.WriteUInt( table.Count( data ), 8 ) + + for k, v in pairs( data ) do + net.WriteString( k ) + net.WriteType( v ) + end +end + +function Item:ReadNetworkData() + for i = 1, net.ReadUInt( 8 ) do + self:SetData( net.ReadString(), net.ReadType() ) + end +end + +function Item:PreRender( ent ) +end + +function Item:PostRender( ent ) +end + +function itemstore.Item( class_name, data ) + local class = itemstore.items.Registered[ class_name ] + + if class then + local item = { + Class = class_name, + Data = data or {} + } + + setmetatable( item, { __index = class } ) + + item:Initialize() + + return item + end +end + +function itemstore.items.Get( class ) + return itemstore.items.Registered[ class ] +end + +function itemstore.items.Exists( class ) + return itemstore.items.Registered[ class ] ~= nil +end + +function itemstore.items.Register( tab ) + if SERVER then util.AddNetworkString( tab.Class ) end + itemstore.items.Registered[ tab.Class ] = tab +end + +function itemstore.items.Load() + for _, filename in ipairs( file.Find( "itemstore/items/*.lua", "LUA" ) ) do + local name = string.match( filename, "^(.+).lua$" ) + + if name then + ITEM = setmetatable( {}, { __index = Item } ) + ITEM.Class = name + + if SERVER then AddCSLuaFile( "itemstore/items/" .. filename ) end + include( "itemstore/items/" .. filename ) + + itemstore.items.Register( ITEM ) + + ITEM = nil + end + end + + for k, v in pairs( itemstore.config.CustomItems ) do + local ITEM = setmetatable( {}, { __index = Item } ) + + ITEM.Class = k + ITEM.Name = v[ 1 ] + ITEM.Description = v[ 2 ] + ITEM.Stackable = v[ 3 ] + ITEM.Base = "base_auto" + + itemstore.items.Register( ITEM ) + end + + for _, item in pairs( itemstore.items.Registered ) do + if item.Base then + local base = itemstore.items.Get( item.Base ) + + if base then + setmetatable( item, { __index = base } ) + else + ErrorNoHalt( "[ItemStore] " .. item.Class .. " tried to derive from non-existent base " .. item.Base ) + end + end + end + + for _, item in pairs( itemstore.items.Registered ) do + item:Load() + end +end +itemstore.items.Load() diff --git a/addons/itemstore/lua/itemstore/items/base_auto.lua b/addons/itemstore/lua/itemstore/items/base_auto.lua new file mode 100644 index 0000000..f826a19 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/base_auto.lua @@ -0,0 +1,28 @@ +ITEM.Name = "Auto Item Base" +ITEM.Model = "models/error.mdl" +ITEM.Base = "base_entity" + +ITEM.DontNetwork = { + EntityData = true +} + +function ITEM:SaveData( ent ) + self:SetModel( ent:GetModel() ) + self:SetData( "EntityData", duplicator.CopyEntTable( ent ) ) +end + +function ITEM:LoadData( ent ) + local data = self:GetData( "EntityData" ) + data.Pos = nil + data.Angle = nil + + duplicator.DoGeneric( ent, data ) + + if data.DT then + timer.Simple( 0, function() + for k, v in pairs( data.DT ) do + ent.dt[ k ] = v + end + end ) + end +end diff --git a/addons/itemstore/lua/itemstore/items/base_darkrp.lua b/addons/itemstore/lua/itemstore/items/base_darkrp.lua new file mode 100644 index 0000000..1d21e32 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/base_darkrp.lua @@ -0,0 +1,14 @@ +ITEM.Name = "DarkRP Item Base" +ITEM.Model = "models/error.mdl" +ITEM.Base = "base_entity" + +function ITEM:CanPickup( pl, ent ) + if not ent.dt or not ent.dt.owning_ent then return true end + + if not itemstore.config.IgnoreOwner and ent:Getowning_ent() ~= pl then + pl:ChatPrint( "You can't pick that up, it's not your's!" ) + return false + end + + return true +end diff --git a/addons/itemstore/lua/itemstore/items/base_entity.lua b/addons/itemstore/lua/itemstore/items/base_entity.lua new file mode 100644 index 0000000..10f50a2 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/base_entity.lua @@ -0,0 +1,21 @@ +ITEM.Name = "Entity Item Base" +ITEM.Model = "models/error.mdl" + +function ITEM:Load() + self:RegisterPickup( self.Class ) +end + +function ITEM:CreateEntity( pos ) + local ent = ents.Create( self.Class ) + ent:SetPos( pos ) + self:LoadData( ent ) + ent:Spawn() + + return ent +end + +function ITEM:SaveData() +end + +function ITEM:LoadData() +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/drug.lua b/addons/itemstore/lua/itemstore/items/drug.lua new file mode 100644 index 0000000..355fb6f --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/drug.lua @@ -0,0 +1,16 @@ +ITEM.Name = itemstore.Translate( "drug_name" ) +ITEM.Description = itemstore.Translate( "drug_desc" ) +ITEM.Model = "models/props_lab/jar01a.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Consumables +ITEM.Base = "base_darkrp" +ITEM.Stackable = true + +function ITEM:SaveData( ent ) + self:SetData( "Price", ent:Getprice() ) + self:SetData( "Owner", ent:Getowning_ent() ) +end + +function ITEM:LoadData( ent ) + ent:Setprice( self:GetData( "Price" ) ) + ent:Setowning_ent( self:GetData( "Owner" ) ) +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/drug_lab.lua b/addons/itemstore/lua/itemstore/items/drug_lab.lua new file mode 100644 index 0000000..ea67a5e --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/drug_lab.lua @@ -0,0 +1,21 @@ +ITEM.Name = itemstore.Translate( "druglab_name" ) +ITEM.Description = itemstore.Translate( "druglab_desc" ) +ITEM.Model = "models/props_lab/crematorcase.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Factories +ITEM.Base = "base_darkrp" + +function ITEM:SaveData( ent ) + self:SetData( "Price", ent:Getprice() ) + self:SetData( "Owner", ent:Getowning_ent() ) +end + +function ITEM:LoadData( ent ) + ent:Setprice( self:GetData( "Price" ) ) + + local owner = self:GetData( "Owner" ) + if not IsValid( owner ) then + owner = player.GetAll()[ 1 ] + end + + ent:Setowning_ent( owner ) +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/food.lua b/addons/itemstore/lua/itemstore/items/food.lua new file mode 100644 index 0000000..6b5e636 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/food.lua @@ -0,0 +1,21 @@ +ITEM.Name = itemstore.Translate( "food_name" ) +ITEM.Description = itemstore.Translate( "microwavefood_desc" ) +ITEM.Model = "models/props_junk/garbage_takeoutcarton001a.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Consumables +ITEM.Base = "base_darkrp" +ITEM.Stackable = true + +function ITEM:Use( pl ) + pl:setSelfDarkRPVar( "Energy", 100 ) + umsg.Start( "AteFoodIcon", pl ) umsg.End() + + return self:TakeOne() +end + +function ITEM:SaveData( ent ) + self:SetData( "Owner", ent:Getowning_ent() ) +end + +function ITEM:LoadData( ent ) + ent:Setowning_ent( self:GetData( "Owner" ) ) +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/gunlab.lua b/addons/itemstore/lua/itemstore/items/gunlab.lua new file mode 100644 index 0000000..a747120 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/gunlab.lua @@ -0,0 +1,21 @@ +ITEM.Name = itemstore.Translate( "gunlab_name" ) +ITEM.Description = itemstore.Translate( "gunlab_name" ) +ITEM.Model = "models/props_c17/TrapPropeller_Engine.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Factories +ITEM.Base = "base_darkrp" + +function ITEM:SaveData( ent ) + self:SetData( "Price", ent:Getprice() ) + self:SetData( "Owner", ent:Getowning_ent() ) +end + +function ITEM:LoadData( ent ) + ent:Setprice( self:GetData( "Price" ) ) + + local owner = self:GetData( "Owner" ) + if not IsValid( owner ) then + owner = player.GetAll()[ 1 ] + end + + ent:Setowning_ent( owner ) +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/microwave.lua b/addons/itemstore/lua/itemstore/items/microwave.lua new file mode 100644 index 0000000..22737e6 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/microwave.lua @@ -0,0 +1,21 @@ +ITEM.Name = itemstore.Translate( "microwave_name" ) +ITEM.Description = itemstore.Translate( "microwave_desc" ) +ITEM.Model = "models/props/cs_office/microwave.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Factories +ITEM.Base = "base_darkrp" + +function ITEM:SaveData( ent ) + self:SetData( "Price", ent:Getprice() ) + self:SetData( "Owner", ent:Getowning_ent() ) +end + +function ITEM:LoadData( ent ) + ent:Setprice( self:GetData( "Price" ) ) + + local owner = self:GetData( "Owner" ) + if not IsValid( owner ) then + owner = player.GetAll()[ 1 ] + end + + ent:Setowning_ent( owner ) +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/money_printer.lua b/addons/itemstore/lua/itemstore/items/money_printer.lua new file mode 100644 index 0000000..8a1daea --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/money_printer.lua @@ -0,0 +1,15 @@ +ITEM.Name = itemstore.Translate( "moneyprinter_name" ) +ITEM.Description = itemstore.Translate( "moneyprinter_desc" ) +ITEM.Model = "models/props_c17/consolebox01a.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Factories +ITEM.Base = "base_darkrp" + +function ITEM:SaveData( ent ) + self:SetData( "Price", ent:Getprice() ) + self:SetData( "Owner", ent:Getowning_ent() ) +end + +function ITEM:LoadData( ent ) + ent:Setprice( self:GetData( "Price" ) ) + ent:Setowning_ent( self:GetData( "Owner" ) ) +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/prop_physics.lua b/addons/itemstore/lua/itemstore/items/prop_physics.lua new file mode 100644 index 0000000..9917c19 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/prop_physics.lua @@ -0,0 +1,13 @@ +ITEM.Name = itemstore.Translate( "prop_name" ) +ITEM.Description = itemstore.Translate( "prop_desc" ) +ITEM.Base = "base_auto" + +function ITEM:CanPickup( pl, ent ) + if CPPI then + if ent:CPPIGetOwner() ~= pl then + return false + end + end + + return true +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/items/spawned_ammo.lua b/addons/itemstore/lua/itemstore/items/spawned_ammo.lua new file mode 100644 index 0000000..29f0c4d --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/spawned_ammo.lua @@ -0,0 +1,36 @@ +ITEM.Name = itemstore.Translate( "ammo_name" ) +ITEM.Description = itemstore.Translate( "ammo_desc" ) +ITEM.HighlightColor = itemstore.config.HighlightColours.Ammo +ITEM.Stackable = true +ITEM.DropStack = true +ITEM.Base = "base_darkrp" + +function ITEM:GetName() + if SERVER then + return self:GetData( "Name", self.Name ) + else + return self:GetData( "Name", language.GetPhrase( self:GetData( "AmmoType" ) .. "_ammo" ) ) + end +end + +function ITEM:Use( pl ) + pl:GiveAmmo( self:GetAmount(), self:GetData( "AmmoType" ) ) + return true +end + +function ITEM:CanMerge( item ) + return self.Stackable and self:GetClass() == item:GetClass() and + self:GetData( "AmmoType" ) == item:GetData( "AmmoType" ) +end + +function ITEM:SaveData( ent ) + self:SetModel( ent:GetModel() ) + self:SetAmount( ent.amountGiven ) + self:SetData( "AmmoType", ent.ammoType ) +end + +function ITEM:LoadData( ent ) + ent:SetModel( self:GetModel() ) + ent.amountGiven = self:GetAmount() + ent.ammoType = self:GetData( "AmmoType" ) +end diff --git a/addons/itemstore/lua/itemstore/items/spawned_food.lua b/addons/itemstore/lua/itemstore/items/spawned_food.lua new file mode 100644 index 0000000..b0f311c --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/spawned_food.lua @@ -0,0 +1,64 @@ +ITEM.Name = itemstore.Translate( "food_name" ) +ITEM.Description = itemstore.Translate( "food_desc" ) +ITEM.Model = "models/props_junk/watermelon01.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Consumables +ITEM.Base = "base_darkrp" + +ITEM.Foods = { + [ "models/props_junk/watermelon01.mdl" ] = itemstore.Translate( "food_melon" ), + [ "models/props/cs_italy/orange.mdl" ] = itemstore.Translate( "food_orange" ), + [ "models/props/cs_italy/bananna_bunch.mdl" ] = itemstore.Translate( "food_bananas" ), + [ "models/props/cs_italy/bananna.mdl" ] = itemstore.Translate( "food_banana" ), + [ "models/props_junk/glassbottle01a.mdl" ] = itemstore.Translate( "food_glassbottle" ), + [ "models/props_junk/popcan01a.mdl" ] = itemstore.Translate( "food_soda" ), + [ "models/props_junk/garbage_milkcarton002a.mdl" ] = itemstore.Translate( "food_milk" ), + [ "models/props_junk/garbage_glassbottle002a.mdl" ] = itemstore.Translate( "food_beer" ), + [ "models/props_junk/garbage_plasticbottle003a.mdl" ] = itemstore.Translate( "food_twolitresoda" ), + [ "models/props_junk/garbage_glassbottle001a.mdl" ] = itemstore.Translate( "food_onelitresoda" ), + [ "models/props_junk/garbage_glassbottle003a.mdl" ] = itemstore.Translate( "food_glassbottle" ) +} + +function ITEM:GetName() + local name = self.Name + + if self.Foods[ self:GetModel() ] then + name = self.Foods[ self:GetModel() ] + end + + return self:GetData( "Name", name ) +end + +function ITEM:GetDescription() + return self:GetData( "Description", string.format( self.Description, self:GetData( "Nutrition", 1 ) ) ) +end + +function ITEM:Use( pl ) + local energy = pl:getDarkRPVar( "Energy" ) + self:GetData( "Nutrition", 1 ) + pl:setSelfDarkRPVar( "Energy", math.Clamp( energy, 0, 100 ) ) + + umsg.Start( "AteFoodIcon", pl ) umsg.End() + + pl:EmitSound( "npc/barnacle/barnacle_crunch2.wav" ) + + return self:TakeOne() +end + +function ITEM:SaveData( ent ) + self:SetData( "Owner", ent:Getowning_ent() ) + self:SetData( "Nutrition", ent.FoodEnergy ) + self:SetModel( ent:GetModel() ) +end + +function ITEM:LoadData( ent ) + ent:Setowning_ent( self:GetData( "Owner" ) ) + ent:SetModel( self:GetModel() ) + ent.FoodEnergy = self:GetData( "Nutrition" ) + + -- One day fptje is gonna have some feces mailed to his house or something, christ + for k, v in ipairs( FoodItems ) do + if v.model == self:GetModel() then + ent.foodItem = v + break + end + end +end diff --git a/addons/itemstore/lua/itemstore/items/spawned_money.lua b/addons/itemstore/lua/itemstore/items/spawned_money.lua new file mode 100644 index 0000000..5908ed9 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/spawned_money.lua @@ -0,0 +1,25 @@ +ITEM.Name = itemstore.Translate( "money_name" ) +ITEM.Description = itemstore.Translate( "money_desc" ) +ITEM.Model = "models/props/cs_assault/money.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Money +ITEM.Base = "base_darkrp" +ITEM.Stackable = true +ITEM.DropStack = true +ITEM.MaxStack = math.huge + +function ITEM:FormatAmount() + return GAMEMODE.Config.currency .. self:GetAmount() +end + +function ITEM:Use( pl ) + itemstore.gamemodes.GiveMoney( pl, self:GetAmount() ) + return true +end + +function ITEM:SaveData( ent ) + self:SetAmount( ent:Getamount() ) +end + +function ITEM:LoadData( ent ) + ent:Setamount( self:GetAmount() ) +end diff --git a/addons/itemstore/lua/itemstore/items/spawned_shipment.lua b/addons/itemstore/lua/itemstore/items/spawned_shipment.lua new file mode 100644 index 0000000..00d1b17 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/spawned_shipment.lua @@ -0,0 +1,132 @@ +ITEM.Name = itemstore.Translate( "shipment_name" ) +ITEM.Description = itemstore.Translate( "shipment_desc" ) +ITEM.Model = "models/Items/item_item_crate.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Shipments +ITEM.Stackable = true +ITEM.DropStack = true +ITEM.Base = "base_darkrp" + +-- Because all of you feel the need to fuck with your shipments on a daily basis. + +function ITEM:Initialize() + if not SERVER then return end + if not self:GetData( "Class" ) then return end + + local shipment = CustomShipments[ self:GetData( "Contents" ) ] + if shipment and shipment.entity == self:GetData( "class" ) then return end + + for k, v in ipairs( CustomShipments ) do + if v.entity == self:GetData( "Class" ) then + self:SetData( "Contents", k ) + return + end + end +end + +function ITEM:GetDescription() + local shipment = CustomShipments[ self:GetData( "Contents" ) ] + + local str = itemstore.Translate( "shipment_invalid" ) + if shipment then + str = string.format( self.Description, shipment.name ) + end + + return self:GetData( "Description", str ) +end + +function ITEM:CanMerge( item ) + return self.Stackable and self:GetClass() == item:GetClass() and + self:GetData( "Contents" ) == item:GetData( "Contents" ) and + self:GetMaxStack() >= ( item:GetAmount() + self:GetAmount() ) +end + +function ITEM:SaveData( ent ) + self:SetData( "Owner", ent:Getowning_ent() ) + self:SetData( "Contents", ent:Getcontents() ) + self:SetData( "Amount", ent:Getcount() ) + + self:SetData( "Class", CustomShipments[ ent:Getcontents() ].entity ) + + self:SetData( "Ammo", ent.ammoadd ) + self:SetData( "Clip1", ent.clip1 ) + self:SetData( "Clip2", ent.clip2 ) +end + +function ITEM:CanPickup( pl, ent ) + return self:GetMaxStack() >= ent:Getcount() and not ent.locked +end + +function ITEM:Pickup( pl, con, slot, ent ) + timer.Destroy( ent:EntIndex() .. "crate" ) + --ent.locked = false + ent.sparking = false +end + +-- 76561198068291318 + +function ITEM:LoadData( ent ) + ent:Setcontents( self:GetData( "Contents" ) ) + ent:Setcount( self:GetData( "Amount" ) ) + ent:Setowning_ent( self:GetData( "Owner" ) ) + + ent.ammoadd = self:GetData( "Ammo" ) + ent.clip1 = self:GetData( "Clip1" ) + ent.clip2 = self:GetData( "Clip2" ) +end + +function ITEM:Use( pl ) + if not CustomShipments[ self:GetData( "Contents" ) ] then return end + if pl:isArrested() then return end + + local class = CustomShipments[ self:GetData( "Contents" ) ].entity + + local wep_table = weapons.Get( class ) + local ammo, ammo_type + + if wep_table then + ammo_type = wep_table.Primary.Ammo + + if ammo_type then + ammo = pl:GetAmmoCount( ammo_type ) + end + end + + local wep = pl:Give( class ) + + if ammo and ammo_type then + pl:SetAmmo( ammo, ammo_type ) + end + + local weapon_exists = false + + if not IsValid( wep ) then + wep = pl:GetWeapon( class ) + weapon_exists = IsValid( wep ) + end + + if IsValid( wep ) and wep:IsWeapon() then + if self:GetData( "Clip1" ) then + if weapon_exists then + pl:GiveAmmo( self:GetData( "Clip1" ), wep:GetPrimaryAmmoType() ) + else + wep:SetClip1( self:GetData( "Clip1" ) ) + end + end + + if self:GetData( "Clip2" ) then + if weapon_exists then + pl:GiveAmmo( self:GetData( "Clip2" ), wep:GetSecondaryAmmoType() ) + else + wep:SetClip2( self:GetData( "Clip2" ) ) + end + end + + if self:GetData( "Ammo" ) then + pl:GiveAmmo( self:GetData( "Ammo" ), wep:GetPrimaryAmmoType() ) + elseif wep.Primary and wep.Primary.DefaultClip then + pl:GiveAmmo( wep.Primary.DefaultClip, wep:GetPrimaryAmmoType() ) + end + end + + return self:TakeOne() +end diff --git a/addons/itemstore/lua/itemstore/items/spawned_weapon.lua b/addons/itemstore/lua/itemstore/items/spawned_weapon.lua new file mode 100644 index 0000000..1ae3a41 --- /dev/null +++ b/addons/itemstore/lua/itemstore/items/spawned_weapon.lua @@ -0,0 +1,234 @@ +ITEM.Name = itemstore.Translate( "weapon_name" ) +ITEM.Description = itemstore.Translate( "weapon_desc" ) +ITEM.Model = "models/weapons/w_pistol.mdl" +ITEM.HighlightColor = itemstore.config.HighlightColours.Weapons +ITEM.Base = "base_darkrp" +ITEM.Stackable = true + +ITEM.Weapons = { + weapon_physgun = itemstore.Translate( "weapon_physgun" ), + weapon_physcannon = itemstore.Translate( "weapon_physcannon" ), + weapon_crowbar = itemstore.Translate( "weapon_crowbar" ), + weapon_stunstick = itemstore.Translate( "weapon_stunstick" ), + weapon_pistol = itemstore.Translate( "weapon_pistol" ), + weapon_357 = itemstore.Translate( "weapon_357" ), + weapon_smg1 = itemstore.Translate( "weapon_smg1" ), + weapon_ar2 = itemstore.Translate( "weapon_ar2" ), + weapon_annabelle = itemstore.Translate( "weapon_annabelle" ), + weapon_shotgun = itemstore.Translate( "weapon_shotgun" ), + weapon_crossbow = itemstore.Translate( "weapon_crossbow" ), + weapon_frag = itemstore.Translate( "weapon_frag" ), + weapon_rpg = itemstore.Translate( "weapon_rpg" ), + weapon_slam = itemstore.Translate( "weapon_slam" ), + weapon_bugbait = itemstore.Translate( "weapon_bugbait" ) +} + +function ITEM:IsValid() + return self.Weapons[ self:GetData( "Class" ) ] or weapons.Get( self:GetData( "Class" ) ) +end + +function ITEM:GetWeaponClass( wep ) + return wep.GetWeaponClass and wep:GetWeaponClass() or wep.weaponclass +end + +function ITEM:GetName() + local name = self.Name + + if self.Weapons[ self:GetData( "Class" ) ] then + name = self.Weapons[ self:GetData( "Class" ) ] + end + + local wep_class = weapons.Get( self:GetData( "Class" ) ) + if wep_class and wep_class.PrintName then + name = wep_class.PrintName + end + + return self:GetData( "Name", name ) +end + +function ITEM:GetDescription() + local desc = self.Description + + local clip = self:GetData( "Clip1", 0 ) + local reserve = self:GetData( "Ammo", 0 ) + + return self:GetData( "Description", string.format( desc, clip, reserve ) ) +end + +function ITEM:CanPickup( pl, ent ) + if self.MaxStack < ent:Getamount() then return false end + if ent.PlayerUse then return false end + if not weapons.Get( self:GetData( "Class" ) ) and + not self.Weapons[ self:GetData( "Class" ) ] then return false end + if itemstore.config.DisabledItems[ self:GetData( "Class" ) ] then return false end + + return true +end + +function ITEM:CanMerge( item ) + return self.Stackable and item:GetClass() == self:GetClass() and + item:GetData( "Class" ) == self:GetData( "Class" ) and + self.MaxStack >= self:GetAmount() + item:GetAmount() +end + +function ITEM:Merge( item ) + self:SetAmount( self:GetAmount() + item:GetAmount() ) + + self:SetData( "Clip2", item:GetData( "Clip2", 0 ) + self:GetData( "Clip2", 0 ) ) + self:SetData( "Ammo", item:GetData( "Ammo", 0 ) + self:GetData( "Ammo", 0 ) + + item:GetData( "Clip1", 0 ) ) +end + +-- 76561198068291318 + +function ITEM:Split( amount ) + self:SetAmount( self:GetAmount() - amount ) + + local item = self:Copy() + item:SetAmount( amount ) + + self:SetData( "Clip1", 0 ) + self:SetData( "Clip2", 0 ) + self:SetData( "Ammo", 0 ) + + return item +end + +function ITEM:SaveData( ent ) + self:SetData( "Class", self:GetWeaponClass( ent ) ) + self:SetData( "Amount", ent:Getamount() ) + self:SetData( "Model", ent:GetModel() ) + + if ent.clip1 then self:SetData( "Clip1", ent.clip1 ) end + if ent.clip2 then self:SetData( "Clip2", ent.clip2 ) end + + local reserve = 0 + if ent.clip1 and ent:Getamount() > 1 then + reserve = reserve + ent.clip1 * ( ent:Getamount() - 1 ) + end + + if ent.ammoadd then + reserve = reserve + ent.ammoadd * ent:Getamount() + end + + self:SetData( "Ammo", reserve ) +end + +function ITEM:LoadData( ent ) + ent:SetModel( self:GetData( "Model" ) ) + ent:Setamount( 1 ) + + if ent.GetWeaponClass then + ent:SetWeaponClass( self:GetData( "Class" ) ) + else + ent.weaponclass = self:GetData( "Class" ) + end + + ent.clip1 = math.floor( self:GetData( "Clip1", 0 ) / ent:Getamount() ) + ent.clip2 = math.floor( self:GetData( "Clip2", 0 ) / ent:Getamount() ) + ent.ammoadd = math.floor( self:GetData( "Ammo", 0 ) / ent:Getamount() ) + + self:SetData( "Clip1", 0 ) + self:SetData( "Clip2", 0 ) + self:SetData( "Ammo", 0 ) + + function ent:Initialize() + self:PhysicsInit( SOLID_VPHYSICS ) + self:SetMoveType( MOVETYPE_VPHYSICS ) + self:SetSolid( SOLID_VPHYSICS ) + self:SetUseType( SIMPLE_USE ) + self:GetPhysicsObject():Wake() + + self:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS ) + end +end + +local hl2_ammo = { + weapon_pistol = 3, + weapon_357 = 5, + weapon_smg1 = 4, + weapon_ar2 = 1, + weapon_annabelle = 7, + weapon_shotgun = 7, + weapon_crossbow = 6, + weapon_frag = 10, + weapon_rpg = 8, +} + +function ITEM:Use( pl ) + if pl:isArrested() then return false end + + local class = self:GetData( "Class" ) + + if not self.Weapons[ class ] and + not weapons.Get( class ) then return false end + + -- taken from darkrp. may or may not work. + if GAMEMODE.Config.license and not pl:getDarkRPVar("HasGunlicense") and not pl.RPLicenseSpawn then + if not GAMEMODE.NoLicense[string.lower(class)] then + return false + end + end + + local has_weapon = pl:HasWeapon( class ) + + local wep_table = weapons.Get( class ) + local ammo, ammo_type + + if wep_table then + ammo_type = wep_table.Primary.Ammo + else + ammo_type = hl2_ammo[ class ] + end + + if ammo_type then + ammo = pl:GetAmmoCount( ammo_type ) + end + + pl:Give( class ) + + if ammo and ammo_type then + pl:SetAmmo( ammo, ammo_type ) + end + + local wep = pl:GetWeapon( class ) + + -- make sure we actually gave the weapon before we start deducting stuff + if not IsValid( wep ) then return end + + if self:GetData( "Clip1" ) then + if has_weapon then + pl:GiveAmmo( self:GetData( "Clip1" ), wep:GetPrimaryAmmoType() ) + else + wep:SetClip1( self:GetData( "Clip1" ) ) + end + end + + if self:GetData( "Clip2" ) then + if has_weapon then + pl:GiveAmmo( self:GetData( "Clip2" ), wep:GetPrimaryAmmoType() ) + else + wep:SetClip2( self:GetData( "Clip2" ) ) + end + end + + self:SetData( "Clip1", 0 ) + self:SetData( "Clip2", 0 ) + + if itemstore.config.SplitWeaponAmmo then + if self:GetData( "Ammo" ) then + local reserve = self:GetData( "Ammo" ) + local amount = self:GetAmount() + + local ammo = math.min( math.ceil( reserve / amount ), reserve ) + + pl:GiveAmmo( ammo, wep:GetPrimaryAmmoType() ) + self:SetData( "Ammo", reserve - ammo ) + end + else + pl:GiveAmmo( self:GetData( "Ammo", 0 ), wep:GetPrimaryAmmoType() ) + self:SetData( "Ammo", 0 ) + end + + return self:TakeOne() +end diff --git a/addons/itemstore/lua/itemstore/language.lua b/addons/itemstore/lua/itemstore/language.lua new file mode 100644 index 0000000..80712b7 --- /dev/null +++ b/addons/itemstore/lua/itemstore/language.lua @@ -0,0 +1,13 @@ +itemstore.Language = {} + +LANGUAGE = {} +include( "languages/" .. itemstore.config.Language .. ".lua" ) +if SERVER then AddCSLuaFile( "languages/" .. itemstore.config.Language .. ".lua" ) end +itemstore.Language = LANGUAGE +LANGUAGE = nil + +assert( itemstore.Language, "[ItemStore] Language not found" ) + +function itemstore.Translate( trans, ... ) + return string.format( itemstore.Language[ trans ] or trans, ... ) +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/languages/cn.lua b/addons/itemstore/lua/itemstore/languages/cn.lua new file mode 100644 index 0000000..6bfc159 --- /dev/null +++ b/addons/itemstore/lua/itemstore/languages/cn.lua @@ -0,0 +1,111 @@ +-- ATTN: Be extremely careful when translating this file. +-- All of the %s, %d, etc variables must be retained when translating. + +-- Generic stuff +LANGUAGE.ok = "OK" +LANGUAGE.cancel = "取消" +LANGUAGE.not_permitted = "你没有权限执行这个指令." +LANGUAGE.file_not_found = "文件未找到" +LANGUAGE.player_not_found = "玩家未找到" +LANGUAGE.invalid_data = "数据错误" +LANGUAGE.invalid_args = "数据错误" +LANGUAGE.cant_fit = "你不能把这个塞进口袋!" + +-- Various inventory lines +LANGUAGE.page = "页数 %d" +LANGUAGE.inventory = "库存" +LANGUAGE.bank = "Bank" +LANGUAGE.players_inventory = "%s的库存" +LANGUAGE.players_bank = "%s的银行" +LANGUAGE.admin_title = "管理员" + +-- Slot options +LANGUAGE.move = "移动" +LANGUAGE.use = "使用" +LANGUAGE.usewith = "使用..." +LANGUAGE.drop = "扔出" +LANGUAGE.destroy = "删除" +LANGUAGE.destroy_title = "删除物品" +LANGUAGE.destroy_confirmation = "你真的想要删除这个吗,它将不会被恢复!" +LANGUAGE.merge = "合并" +LANGUAGE.split = "拆分" +LANGUAGE.split_half = "一半 (%d)" +LANGUAGE.picked_up = "你捡起了个 %s." + +-- Slot help +LANGUAGE.dragtomove = "鼠标按住以拖动" +LANGUAGE.mclicktodrop = "鼠标中键以扔出" +LANGUAGE.rclickforoptions = "鼠标右键设置" +LANGUAGE.dclicktouse = "双击以使用" + +-- Trading +LANGUAGE.ready = "准备" +LANGUAGE.not_ready = "未准备" +LANGUAGE.accept = "接受" +LANGUAGE.deny = "拒绝" +LANGUAGE.trading_with = "与 %s 交易" +LANGUAGE.trade_request = "交易请求" +LANGUAGE.wants_to_trade = "%s 想和你交易" +LANGUAGE.too_far_away = "你离的太远了." +LANGUAGE.trading_cooldown = "冷却中请等待." +LANGUAGE.trade_failed = "交易失败,你没有足够的钱!" + +-- Items + +LANGUAGE.drug_name = "毒品" +LANGUAGE.drug_desc = "爽爽爽." + +LANGUAGE.druglab_name = "毒品台" +LANGUAGE.druglab_desc = "不要被警察看见!" + +LANGUAGE.food_name = "食物" +LANGUAGE.food_desc = "Nom.\n\nNutritional value: %%d" +LANGUAGE.microwavefood_desc = "Nom." + +LANGUAGE.food_melon = "西瓜" +LANGUAGE.food_orange = "橘子" +LANGUAGE.food_bananas = "一把香蕉" +LANGUAGE.food_banana = "香蕉" +LANGUAGE.food_glassbottle = "玻璃瓶" +LANGUAGE.food_soda = "汽水" +LANGUAGE.food_milk = "牛奶" +LANGUAGE.food_beer = "啤酒" +LANGUAGE.food_twolitresoda = "2 汽水" +LANGUAGE.food_onelitresoda = "1 汽水" + +LANGUAGE.gunlab_name = "武器制造器" +LANGUAGE.gunlab_desc = "造枪的." + +LANGUAGE.microwave_name = "微波炉" +LANGUAGE.microwave_desc = "为什么他外面是热的里面是冷的!?" + +LANGUAGE.moneyprinter_name = "印钞机" +LANGUAGE.moneyprinter_desc = "钱!" + +LANGUAGE.ammo_name = "弹药" +LANGUAGE.ammo_desc = "biu biu biu" + +LANGUAGE.money_name = "钱" +LANGUAGE.money_desc = "世界之主." + +LANGUAGE.shipment_name = "货物" +LANGUAGE.shipment_desc = "货物\n\n数量: %%s" +LANGUAGE.shipment_invalid = "错误 请删掉他" + +LANGUAGE.weapon_name = "武器" +LANGUAGE.weapon_desc = "一把可以装备的武器.\n弹药: %%d\n备弹: %%d" +LANGUAGE.weapon_physgun = "物理枪" +LANGUAGE.weapon_physcannon = "重力枪" +LANGUAGE.weapon_crowbar = "撬棍" +LANGUAGE.weapon_stunstick = "电棍" +LANGUAGE.weapon_pistol = "手枪" +LANGUAGE.weapon_357 = "357左轮" +LANGUAGE.weapon_smg1 = "冲锋枪1" +LANGUAGE.weapon_ar2 = "脉冲枪2" +LANGUAGE.weapon_annabelle = "猎枪" +LANGUAGE.weapon_shotgun = "霰弹枪" +LANGUAGE.weapon_crossbow = "弩" +LANGUAGE.weapon_frag = "手榴弹" +LANGUAGE.weapon_rpg = "RPG" +LANGUAGE.weapon_slam = "S.L.A.M." +LANGUAGE.weapon_bugbait = "翔" \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/languages/de.lua b/addons/itemstore/lua/itemstore/languages/de.lua new file mode 100644 index 0000000..4d13cfa --- /dev/null +++ b/addons/itemstore/lua/itemstore/languages/de.lua @@ -0,0 +1,112 @@ +-- ATTN: Be extremely careful when translating this file. +-- All of the %s, %d, etc variables must be retained when translating. +-- translated by GreenAurora! ;) (http://steamcommunity.com/id/nVrAm/) + +-- Generic stuff +LANGUAGE.ok = "Ok" +LANGUAGE.cancel = "Zurück" +LANGUAGE.not_permitted = "Du hast keinen Zugriff auf diese Funktion." +LANGUAGE.file_not_found = "Datei konnte nicht gefunden werden!" +LANGUAGE.player_not_found = "Der Spieler wurde nicht gefunden!" +LANGUAGE.invalid_data = "Ungültige Datei angegeben!" +LANGUAGE.invalid_args = "Ungültige Argumente angegeben!" +LANGUAGE.cant_fit = "Dieses Item ist zu groß für dein Inventar!" + +-- Various inventory lines +LANGUAGE.page = "Seite %d" +LANGUAGE.inventory = "Inventar" +LANGUAGE.bank = "Lager" +LANGUAGE.players_inventory = "%s's Inventar" +LANGUAGE.players_bank = "%s's Lager" +LANGUAGE.admin_title = "ItemStore Admin" + +-- Slot options +LANGUAGE.move = "Verschieben" +LANGUAGE.use = "Verwenden" +LANGUAGE.usewith = "Verwenden mit..." +LANGUAGE.drop = "Fallen lassen" +LANGUAGE.destroy = "Zerstören" +LANGUAGE.destroy_title = "Item zerstören" +LANGUAGE.destroy_confirmation = "Bist du dir sicher, dass du dieses Item zerstören möchtest?" +LANGUAGE.merge = "Zusammenfügen" +LANGUAGE.split = "Aufteilen" +LANGUAGE.split_half = "Halbieren (%d)" +LANGUAGE.picked_up = "Du packst ein(e/n) %s in dein Inventar." + +-- Slot help +LANGUAGE.dragtomove = "Klicken und ziehen zum verschieben!" +LANGUAGE.mclicktodrop = "Mittlere Maustaste zum fallen lassen." +LANGUAGE.rclickforoptions = "Rechts-Klick für Optionen." +LANGUAGE.dclicktouse = "Doppelklick zum Benutzen." + +-- Trading +LANGUAGE.ready = "Bereit" +LANGUAGE.not_ready = "Nicht Bereit" +LANGUAGE.accept = "Akzeptieren" +LANGUAGE.deny = "Ablehnen" +LANGUAGE.trading_with = "Handeln mit %s" +LANGUAGE.trade_request = "Handelsanfrage" +LANGUAGE.wants_to_trade = "%s möchte mit dir handeln!" +LANGUAGE.too_far_away = "Du bist zu weit weg, um mit dieser Person zu handeln." +LANGUAGE.trading_cooldown = "Du musst einen Moment warten, bevor du wieder handeln kannst." +LANGUAGE.trade_failed = "Handel fehlgeschlagen! Einer der beiden Handelspartner hat weniger Geld, als er angeboten hat!" + +-- Items + +LANGUAGE.drug_name = "Drogen" +LANGUAGE.drug_desc = "aaaaaaalteeeeer.." + +LANGUAGE.druglab_name = "Drogenlabor" +LANGUAGE.druglab_desc = "Also das sollte die Polizei nicht unbedingt sehen.." + +LANGUAGE.food_name = "Nahrung" +LANGUAGE.food_desc = "Mhhmm..\n\nNährwert: %%d" +LANGUAGE.microwavefood_desc = "Mhhmm.." + +LANGUAGE.food_melon = "Melone" +LANGUAGE.food_orange = "Orange" +LANGUAGE.food_bananas = "Bananen" +LANGUAGE.food_banana = "Banane" +LANGUAGE.food_glassbottle = "Glasflasche" +LANGUAGE.food_soda = "Dosenwasser" +LANGUAGE.food_milk = "Milch" +LANGUAGE.food_beer = "Bier" +LANGUAGE.food_twolitresoda = "2 Liter Wasser" +LANGUAGE.food_onelitresoda = "1 Liter Wasser" + +LANGUAGE.gunlab_name = "Waffenlabor" +LANGUAGE.gunlab_desc = "Es produziert Waffen.. WOW!" + +LANGUAGE.microwave_name = "Mikrowelle" +LANGUAGE.microwave_desc = "Wieso ist das Essen außen total heiß und innen noch kalt?!" + +LANGUAGE.moneyprinter_name = "Moneyprinter" +LANGUAGE.moneyprinter_desc = "Druckt dir die Scheinchen" + +LANGUAGE.ammo_name = "Munition" +LANGUAGE.ammo_desc = "Die Dinger, die vorn aus den Waffen rauskommen." + +LANGUAGE.money_name = "Geld" +LANGUAGE.money_desc = "Geld?! GELD!" + +LANGUAGE.shipment_name = "Lieferung" +LANGUAGE.shipment_desc = "Eine Lieferung.\n\nInhalt: %%s" +LANGUAGE.shipment_invalid = "ERROR: Lieferung ungültig. Bitte lösche es." + +LANGUAGE.weapon_name = "Waffe" +LANGUAGE.weapon_desc = "Eine nutzbare Waffe.\nMunition im Magazin: %%d\nMunition verfügbar: %%d" +LANGUAGE.weapon_physgun = "Physgun" +LANGUAGE.weapon_physcannon = "Gravity Gun" +LANGUAGE.weapon_crowbar = "Crowbar" +LANGUAGE.weapon_stunstick = "Stunstick" +LANGUAGE.weapon_pistol = "Pistol" +LANGUAGE.weapon_357 = "357" +LANGUAGE.weapon_smg1 = "SMG1" +LANGUAGE.weapon_ar2 = "AR2" +LANGUAGE.weapon_annabelle = "Annabelle" +LANGUAGE.weapon_shotgun = "Shotgun" +LANGUAGE.weapon_crossbow = "HL2 Armbrust" +LANGUAGE.weapon_frag = "Frag Grenade" +LANGUAGE.weapon_rpg = "RPG" +LANGUAGE.weapon_slam = "S.L.A.M." +LANGUAGE.weapon_bugbait = "Bug Bait" \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/languages/en.lua b/addons/itemstore/lua/itemstore/languages/en.lua new file mode 100644 index 0000000..d5b4d49 --- /dev/null +++ b/addons/itemstore/lua/itemstore/languages/en.lua @@ -0,0 +1,118 @@ +-- ATTN: Be extremely careful when translating this file. +-- All of the %s, %d, etc variables must be retained when translating. + +-- Generic stuff +LANGUAGE.ok = "OK" +LANGUAGE.cancel = "Cancel" +LANGUAGE.not_permitted = "You are not permitted to perform this action." +LANGUAGE.file_not_found = "File not found" +LANGUAGE.player_not_found = "Player not found" +LANGUAGE.invalid_data = "Invalid data" +LANGUAGE.invalid_args = "Invalid arguments" +LANGUAGE.cant_fit = "You can't fit that in your inventory!" + +-- Various inventory lines +LANGUAGE.page = "Page %d" +LANGUAGE.inventory = "Inventory" +LANGUAGE.bank = "Bank" +LANGUAGE.players_inventory = "%s's Inventory" +LANGUAGE.players_bank = "%s's Bank" +LANGUAGE.admin_title = "ItemStore Admin" + +-- Slot options +LANGUAGE.move = "Move" +LANGUAGE.use = "Use" +LANGUAGE.usewith = "Use with..." +LANGUAGE.drop = "Drop" +LANGUAGE.destroy = "Destroy" +LANGUAGE.destroy_title = "Destroy Item" +LANGUAGE.destroy_confirmation = "Are you sure you want to delete this item? You won't be able to get it back!" +LANGUAGE.merge = "Merge" +LANGUAGE.split = "Split" +LANGUAGE.split_half = "Half (%d)" +LANGUAGE.picked_up = "You picked up a(n) %s." + +-- Slot help +LANGUAGE.dragtomove = "Click and drag to move" +LANGUAGE.mclicktodrop = "Middle-click to drop" +LANGUAGE.rclickforoptions = "Right-click for options" +LANGUAGE.dclicktouse = "Double-click to use" + +-- Trading +LANGUAGE.ready = "Ready" +LANGUAGE.not_ready = "Not ready" +LANGUAGE.accept = "Accept" +LANGUAGE.deny = "Deny" +LANGUAGE.trading_with = "Trading with %s" +LANGUAGE.trade_request = "Trade request" +LANGUAGE.wants_to_trade = "%s wants to trade" +LANGUAGE.too_far_away = "You are too far away to initiate a trade." +LANGUAGE.trading_cooldown = "You must wait a bit before you can trade again." +LANGUAGE.trade_failed = "Trade failed. Either you or your partner tried to offer more money than you had!" +LANGUAGE.cant_access_inventory = "You can't trade if you don't have access to an inventory." +LANGUAGE.partner_cant_access_inventory = "You can't trade with a partner who doesn't have access to an inventory." +LANGUAGE.partner_is_in_trade = "You can't trade with a partner who is already in a trade." +LANGUAGE.already_in_trade = "You're already trading with someone else." + +-- Items + +LANGUAGE.drug_name = "Drugs" +LANGUAGE.drug_desc = "Duuuuuuuude." + +LANGUAGE.druglab_name = "Drug Lab" +LANGUAGE.druglab_desc = "Don't let it be seen by the CPs!" + +LANGUAGE.food_name = "Food" +LANGUAGE.food_desc = "Nom.\n\nNutritional value: %%d" +LANGUAGE.microwavefood_desc = "Nom." + +LANGUAGE.food_melon = "Melon" +LANGUAGE.food_orange = "Orange" +LANGUAGE.food_bananas = "Bunch of Bananas" +LANGUAGE.food_banana = "Banana" +LANGUAGE.food_glassbottle = "Glass Bottle" +LANGUAGE.food_soda = "Can of Soda" +LANGUAGE.food_milk = "Milk" +LANGUAGE.food_beer = "Beer" +LANGUAGE.food_twolitresoda = "2 Litre Soda" +LANGUAGE.food_onelitresoda = "1 Litre Soda" + +LANGUAGE.gunlab_name = "Gun Lab" +LANGUAGE.gunlab_desc = "A lab that makes guns. DUH." + +LANGUAGE.microwave_name = "Microwave" +LANGUAGE.microwave_desc = "Why is it always burning hot on the outside but freezing in the middle!?" + +LANGUAGE.moneyprinter_name = "Money Printer" +LANGUAGE.moneyprinter_desc = "Dealing out that cash money" + +LANGUAGE.ammo_name = "Ammo" +LANGUAGE.ammo_desc = "Things that you shoot out of a gun." + +LANGUAGE.money_name = "Money" +LANGUAGE.money_desc = "Lods of emone." + +LANGUAGE.prop_name = "Prop" +LANGUAGE.prop_desc = "Hit people with this to get banned." + +LANGUAGE.shipment_name = "Shipment" +LANGUAGE.shipment_desc = "A shipment of guns.\n\nContents: %%s" +LANGUAGE.shipment_invalid = "ERROR: Shipment invalid. Delete this item!" + +LANGUAGE.weapon_name = "Weapon" +LANGUAGE.weapon_desc = "An equippable weapon.\nAmmo in magazine: %%d\nAmmo in reserve: %%d" +LANGUAGE.weapon_physgun = "Physgun" +LANGUAGE.weapon_physcannon = "Gravity Gun" +LANGUAGE.weapon_crowbar = "Crowbar" +LANGUAGE.weapon_stunstick = "Stunstick" +LANGUAGE.weapon_pistol = "Pistol" +LANGUAGE.weapon_357 = "357" +LANGUAGE.weapon_smg1 = "SMG1" +LANGUAGE.weapon_ar2 = "AR2" +LANGUAGE.weapon_annabelle = "Annabelle" +LANGUAGE.weapon_shotgun = "Shotgun" +LANGUAGE.weapon_crossbow = "Crossbow" +LANGUAGE.weapon_frag = "Frag Grenade" +LANGUAGE.weapon_rpg = "RPG" +LANGUAGE.weapon_slam = "S.L.A.M." +LANGUAGE.weapon_bugbait = "Bug Bait" \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/languages/fr.lua b/addons/itemstore/lua/itemstore/languages/fr.lua new file mode 100644 index 0000000..86e3537 --- /dev/null +++ b/addons/itemstore/lua/itemstore/languages/fr.lua @@ -0,0 +1,114 @@ +-- ATTN: Be extremely careful when translating this file. +-- All of the %s, %d, etc variables must be retained when translating. + +-- Thank you so much max360! All credits for this french translation go to him. +-- http://steamcommunity.com/profiles/76561197990386123 + +-- Generic stuff +LANGUAGE.ok = "OK" +LANGUAGE.cancel = "Annuler" +LANGUAGE.not_permitted = "Il ne vous est pas permis de réaliser cette action." +LANGUAGE.file_not_found = "Fichier non trouvé" +LANGUAGE.player_not_found = "Joueur non trouvé" +LANGUAGE.invalid_data = "Données invalides" +LANGUAGE.invalid_args = "Arguments invalides" +LANGUAGE.cant_fit = "Vous ne pouvez pas faire rentrer ça dans votre inventaire!" + +-- Various inventory lines +LANGUAGE.page = "Page %d" +LANGUAGE.inventory = "Inventaire" +LANGUAGE.bank = "Banque" +LANGUAGE.players_inventory = "Inventaire de %s" +LANGUAGE.players_bank = "Banque de %s" +LANGUAGE.admin_title = "Admin ItemStore" + +-- Slot options +LANGUAGE.move = "Déplacer" +LANGUAGE.use = "Utiliser" +LANGUAGE.usewith = "Utiliser avec..." +LANGUAGE.drop = "Lâcher" +LANGUAGE.destroy = "Détruire" +LANGUAGE.destroy_title = "Détruire Item" +LANGUAGE.destroy_confirmation = "Etes-vous sûr de vouloir supprimer cet item? Il ne vous sera pas possible de le récupérer!" +LANGUAGE.merge = "Fusionner" +LANGUAGE.split = "Diviser" +LANGUAGE.split_half = "Moitié (%d)" +LANGUAGE.picked_up = "Vous avez pris un(e) %s." + +-- Slot help +LANGUAGE.dragtomove = "Cliquez et faites glisser pour déplacer" +LANGUAGE.mclicktodrop = "Clic du milieu pour lâcher" +LANGUAGE.rclickforoptions = "Clic-droit pour les options" +LANGUAGE.dclicktouse = "Double-clic pour utiliser" + +-- Trading +LANGUAGE.ready = "Prêt" +LANGUAGE.not_ready = "Pas prêt" +LANGUAGE.accept = "Accepter" +LANGUAGE.deny = "Refuser" +LANGUAGE.trading_with = "Echange avec %s" +LANGUAGE.trade_request = "Demande d'échange" +LANGUAGE.wants_to_trade = "%s veut échanger" +LANGUAGE.too_far_away = "Vous êtes trop loin pour initier un échange." +LANGUAGE.trading_cooldown = "Vous devez attendre un peu avant de pouvoir échanger de nouveau." +LANGUAGE.trade_failed = "L'échange a échoué. Soit vous soit votre partenaire a essayé de donner plus d'argent qu'il n'en avait!" + +-- Items + +LANGUAGE.drug_name = "Drogues" +LANGUAGE.drug_desc = "Meeeeeeeeec." + +LANGUAGE.druglab_name = "Labo à drogue" +LANGUAGE.druglab_desc = "Ne laissez pas la PC le voir!" + +LANGUAGE.food_name = "Nourriture" +LANGUAGE.food_desc = "Miam.\n\nValeur nutritionnelle: %%d" +LANGUAGE.microwavefood_desc = "Miam." + +LANGUAGE.food_melon = "Melon" +LANGUAGE.food_orange = "Orange" +LANGUAGE.food_bananas = "Grappe de bananes" +LANGUAGE.food_banana = "Banane" +LANGUAGE.food_glassbottle = "Bouteille en verre" +LANGUAGE.food_soda = "Cannette de soda" +LANGUAGE.food_milk = "Lait" +LANGUAGE.food_beer = "Bière" +LANGUAGE.food_twolitresoda = "Soda 2 Litre" +LANGUAGE.food_onelitresoda = "Soda 1 Litre" + +LANGUAGE.gunlab_name = "Labo à armes" +LANGUAGE.gunlab_desc = "Un labo qui fabrique des armes. TOH." + +LANGUAGE.microwave_name = "Micro-ondes" +LANGUAGE.microwave_desc = "Pourquoi ça brûle toujours sur l'extérieur et reste froid au milieu!?" + +LANGUAGE.moneyprinter_name = "Imprimante à argent" +LANGUAGE.moneyprinter_desc = "Dealez cet argent comptant" + +LANGUAGE.ammo_name = "Munitions" +LANGUAGE.ammo_desc = "Les choses que vous tirez hors du pistolet." + +LANGUAGE.money_name = "Argent" +LANGUAGE.money_desc = "Lods of emone." + +LANGUAGE.shipment_name = "Colis" +LANGUAGE.shipment_desc = "Un colis d'armes.\n\nContenu: %%s" +LANGUAGE.shipment_invalid = "ERREUR: Colis invalide. Supprimez cet item!" + +LANGUAGE.weapon_name = "Arme" +LANGUAGE.weapon_desc = "Une arme équipable.\nMunitions dans le chargeur: %%d\nMunitions en réserve: %%d" +LANGUAGE.weapon_physgun = "Physgun" +LANGUAGE.weapon_physcannon = "Gravity Gun" +LANGUAGE.weapon_crowbar = "Pied de biche" +LANGUAGE.weapon_stunstick = "Matraque" +LANGUAGE.weapon_pistol = "Pistolet" +LANGUAGE.weapon_357 = "357" +LANGUAGE.weapon_smg1 = "SMG1" +LANGUAGE.weapon_ar2 = "AR2" +LANGUAGE.weapon_annabelle = "Annabelle" +LANGUAGE.weapon_shotgun = "Fusil à pompe" +LANGUAGE.weapon_crossbow = "Arbalette" +LANGUAGE.weapon_frag = "Grenade à fragmentation" +LANGUAGE.weapon_rpg = "RPG" +LANGUAGE.weapon_slam = "S.L.A.M." +LANGUAGE.weapon_bugbait = "Appât à insectes" \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/languages/ru.lua b/addons/itemstore/lua/itemstore/languages/ru.lua new file mode 100644 index 0000000..5dab946 --- /dev/null +++ b/addons/itemstore/lua/itemstore/languages/ru.lua @@ -0,0 +1,45 @@ +-- Thank you for this Russian translation, Horo! +-- http://steamcommunity.com/id/HoroRu/ + +LANGUAGE.ok = "OK" +LANGUAGE.cancel = "Отмена" + +LANGUAGE.not_permitted = "У вас нет доступа к этому действию." +LANGUAGE.file_not_found = "Файл не найден" +LANGUAGE.player_not_found = "Игрок не найден" +LANGUAGE.invalid_data = "Неправильные данные" +LANGUAGE.invalid_args = "Неправильные аргументы" +LANGUAGE.cant_fit = "Это не поместится в ваш инвентарь!" + +LANGUAGE.page = "Страница %d" +LANGUAGE.inventory = "Инвентарь" +LANGUAGE.bank = "Банк" +LANGUAGE.players_inventory = "Инвентарь %s" +LANGUAGE.players_bank = "Банк %s" +LANGUAGE.admin_title = "ItemStore Админ" + +LANGUAGE.move = "Переместить" +LANGUAGE.use = "Использовать" +LANGUAGE.usewith = "Использовать с..." +LANGUAGE.drop = "Бросить" +LANGUAGE.destroy = "Уничтожить" +LANGUAGE.merge = "Совместить" +LANGUAGE.split = "Разделить" +LANGUAGE.split_half = "Поделить пополам (%d)" + +LANGUAGE.picked_up = "Вы подняли (n) %s." + +LANGUAGE.destroy_title = "Унчитожить предмет" +LANGUAGE.destroy_confirmation = "Вы уверены что хотите удалить предмет? Это действие необратимо!" + +LANGUAGE.ready = "Готово" +LANGUAGE.not_ready = "не готово" +LANGUAGE.accept = "Принять" +LANGUAGE.deny = "Отказаться" +LANGUAGE.trading_with = "Обмен с %s" +LANGUAGE.trade_request = "Запрос обмена" +LANGUAGE.wants_to_trade = "%s хочет обменятся" +LANGUAGE.too_far_away = "Вы слишком далеко чтобы обениваться." +LANGUAGE.trading_cooldown = "Вы должны немного подождать чтобы обмениваться снова." + +LANGUAGE.trade_failed = "Обмен не заверешен. Вы или ваш партнер затребовали больше денег чем у вас/у него есть!" diff --git a/addons/itemstore/lua/itemstore/languages/tr.lua b/addons/itemstore/lua/itemstore/languages/tr.lua new file mode 100644 index 0000000..613aea6 --- /dev/null +++ b/addons/itemstore/lua/itemstore/languages/tr.lua @@ -0,0 +1,118 @@ +-- ATTN: Be extremely careful when translating this file. +-- All of the %s, %d, etc variables must be retained when translating. + +-- Generic stuff +LANGUAGE.ok = "Tamam" +LANGUAGE.cancel = "Iptal" +LANGUAGE.not_permitted = "Bu eylemi gerceklestirmeye iznin yok." +LANGUAGE.file_not_found = "Dosya bulunamadi" +LANGUAGE.player_not_found = "Oyuncu bulunamadi" +LANGUAGE.invalid_data = "Gecersiz veri" +LANGUAGE.invalid_args = "Gecersiz argumanlar" +LANGUAGE.cant_fit = "Envanterine bunu sigdiramazsin!" + +-- Various inventory lines +LANGUAGE.page = "Sayfa %d" +LANGUAGE.inventory = "Envanter" +LANGUAGE.bank = "Banka" +LANGUAGE.players_inventory = "%s's Envanteri" +LANGUAGE.players_bank = "%s's Bankasi" +LANGUAGE.admin_title = "ItemStore Admin" + +-- Slot options +LANGUAGE.move = "Tasi" +LANGUAGE.use = "Kullan" +LANGUAGE.usewith = "Ile kullan..." +LANGUAGE.drop = "Birak" +LANGUAGE.destroy = "Yok Et" +LANGUAGE.destroy_title = "Esyayi Yok Et" +LANGUAGE.destroy_confirmation = "Esyayi silmek istedigine emin misin? Geri alamiyacaksin!" +LANGUAGE.merge = "Birlestir" +LANGUAGE.split = "Bol" +LANGUAGE.split_half = "Yarim (%d)" +LANGUAGE.picked_up = "Su esyayi aldin %s." + +-- Slot help +LANGUAGE.dragtomove = "Haraket ettirmek icin farenin sol tusuna tikla ve surukle" +LANGUAGE.mclicktodrop = "Birakmak icin farenin tekerlegine tikla" +LANGUAGE.rclickforoptions = "Ayarlar icin sag tik" +LANGUAGE.dclicktouse = "2 kez tiklayarak kullan" + +-- Trading +LANGUAGE.ready = "Hazir" +LANGUAGE.not_ready = "Hazir Degil" +LANGUAGE.accept = "Kabul" +LANGUAGE.deny = "Red" +LANGUAGE.trading_with = "%s ile takas yapiliyor" +LANGUAGE.trade_request = "Takas istegi" +LANGUAGE.wants_to_trade = "%s takas yapmak istiyor" +LANGUAGE.too_far_away = "Takas yapmak icin cok uzaksin." +LANGUAGE.trading_cooldown = "Yeniden takas yapmak icin biraz beklemen lazim." +LANGUAGE.trade_failed = "Takas basarisiz. Siz veya takas yaptiginiz kisi sahip oldugunuzdan daha fazla para teklif etmeye calisti!" +LANGUAGE.cant_access_inventory = "Envantere erisiminiz yoksa takas yapamazsiniz." +LANGUAGE.partner_cant_access_inventory = "Envanterine erisimi olmayan biri ile takas yapamazsiniz." +LANGUAGE.partner_is_in_trade = "Halihazirda takas yapan biriyle takas yapamazsiniz." +LANGUAGE.already_in_trade = "Halihazirda bir takas yaparken baskasiyla takas yapamazsiniz." + +-- Items + +LANGUAGE.drug_name = "Uyusturucular" +LANGUAGE.drug_desc = "Dostuuuuum." + +LANGUAGE.druglab_name = "Uyusturucu Laboratuvari" +LANGUAGE.druglab_desc = "Polislerin bizi bulmasina sakin izin verme!" + +LANGUAGE.food_name = "Yemek" +LANGUAGE.food_desc = "Leziz.\n\nNutritional value: %%d" +LANGUAGE.microwavefood_desc = "Leziz." + +LANGUAGE.food_melon = "Karpuz" +LANGUAGE.food_orange = "Portakal" +LANGUAGE.food_bananas = "Salkim Muz" +LANGUAGE.food_banana = "Muz" +LANGUAGE.food_glassbottle = "Cam Sise" +LANGUAGE.food_soda = "Kutu Soda" +LANGUAGE.food_milk = "Sut" +LANGUAGE.food_beer = "Bira" +LANGUAGE.food_twolitresoda = "2 Litre Soda" +LANGUAGE.food_onelitresoda = "1 Litre Soda" + +LANGUAGE.gunlab_name = "Silah Laboratuvari" +LANGUAGE.gunlab_desc = "Silah yapmani saglayan bi laboratuvar.Baska ne olabilir amk." + +LANGUAGE.microwave_name = "Mikrodalga" +LANGUAGE.microwave_desc = "Neden hep dis tarafi soguk ama ic taraflari soguk!?" + +LANGUAGE.moneyprinter_name = "Para Makinasi" +LANGUAGE.moneyprinter_desc = "Sanal para ile ugrasmak istemeyenler icin ideal.Kodumunun boomeri" + +LANGUAGE.ammo_name = "Mermi" +LANGUAGE.ammo_desc = "Silahtan firlayan sey var ya he iste o." + +LANGUAGE.money_name = "Para" +LANGUAGE.money_desc = "Gotunu sildigin maddeyi almani saglayan ancak ayni maddelerden yapilan sey." + +LANGUAGE.prop_name = "Prop" +LANGUAGE.prop_desc = "Baskasina bunla vur asiri eglenceli oluyo. Banlanma." + +LANGUAGE.shipment_name = "Kargo" +LANGUAGE.shipment_desc = "Silah Kargosu.\n\nContents: %%s" +LANGUAGE.shipment_invalid = "HATA: Kargo gecersiz. Bu esyayi sil!" + +LANGUAGE.weapon_name = "Silah" +LANGUAGE.weapon_desc = "Kullanılabilen bir silah.\nAmmo Şarjorde: %%d\nAmmo Reservde: %%d" +LANGUAGE.weapon_physgun = "Physgun" +LANGUAGE.weapon_physcannon = "Gravity Gun" +LANGUAGE.weapon_crowbar = "Crowbar" +LANGUAGE.weapon_stunstick = "Stunstick" +LANGUAGE.weapon_pistol = "Pistol" +LANGUAGE.weapon_357 = "357" +LANGUAGE.weapon_smg1 = "SMG1" +LANGUAGE.weapon_ar2 = "AR2" +LANGUAGE.weapon_annabelle = "Annabelle" +LANGUAGE.weapon_shotgun = "Shotgun" +LANGUAGE.weapon_crossbow = "Crossbow" +LANGUAGE.weapon_frag = "Frag Grenade" +LANGUAGE.weapon_rpg = "RPG" +LANGUAGE.weapon_slam = "S.L.A.M." +LANGUAGE.weapon_bugbait = "Bug Bait" \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/shared.lua b/addons/itemstore/lua/itemstore/shared.lua new file mode 100644 index 0000000..112ce26 --- /dev/null +++ b/addons/itemstore/lua/itemstore/shared.lua @@ -0,0 +1,113 @@ +itemstore.Version = "3.0" +itemstore.About = string.format( [[ItemStore v%s + +Authored solely by UselessGhost +http://steamcommunity.com/id/uselessghost +]], itemstore.Version ) + +MsgC( color_white, "ItemStore", Color( 100, 200, 255 ), " ", itemstore.Version, " ", Color( 200, 200, 200 ), "coded by ", Color( 255, 150, 150 ), "UselessGhost", "\n" ) + +concommand.Add( "itemstore_about", function() + MsgC( color_white, itemstore.About ) +end ) + +itemstore.config = {} + +function itemstore.config.Verify( setting, correct_type ) + if type( itemstore.config[ setting ] ) ~= correct_type then + ErrorNoHalt( string.format( "[ItemStore] Configuration error: %s is %s, should be %s.", setting, var_type, correct_type ) ) + --include( "config_default.lua" ) + + return false + end + + return true +end + +include( "config.lua" ) + +itemstore.config.Verify( "MaxStack", "number" ) +itemstore.config.Verify( "DataProvider", "string" ) +itemstore.config.Verify( "GamemodeProvider", "string" ) +itemstore.config.Verify( "Language", "string" ) +itemstore.config.Verify( "ContextInventory", "boolean" ) +itemstore.config.Verify( "ContextInventoryPosition", "string" ) +itemstore.config.Verify( "InvholsterTakesAmmo", "boolean" ) +itemstore.config.Verify( "PickupsGotoBank", "boolean" ) +itemstore.config.Verify( "AntiDupe", "boolean" ) +itemstore.config.Verify( "InventorySizes", "table" ) +itemstore.config.Verify( "BankSizes", "table" ) +itemstore.config.Verify( "PickupDistance", "number" ) +itemstore.config.Verify( "PickupKey", "number" ) +itemstore.config.Verify( "Colours", "table" ) +itemstore.config.Verify( "DisabledItems", "table" ) +itemstore.config.Verify( "CustomItems", "table" ) +itemstore.config.Verify( "IgnoreOwner", "boolean" ) +itemstore.config.Verify( "Skin", "string" ) +itemstore.config.Verify( "SplitWeaponAmmo", "boolean" ) +itemstore.config.Verify( "MigrateOldData", "boolean" ) +itemstore.config.Verify( "EnableInvholster", "boolean" ) +itemstore.config.Verify( "LimitToJobs", "table" ) +itemstore.config.Verify( "HighlightStyle", "string" ) +itemstore.config.Verify( "HighlightColours", "table" ) + +include( "language.lua" ) +include( "gamemodes.lua" ) +include( "items.lua" ) +include( "containers.lua" ) +include( "trading.lua" ) +include( "admin.lua" ) + +local _, dirs = file.Find( "itemstore/modules/*", "LUA" ) +for _, mod in ipairs( dirs ) do + MsgC( color_white, string.format( "Loading ItemStore module: %s\n", mod ) ) + + local path = "itemstore/modules/" .. mod + + for _, filename in ipairs( file.Find( path .. "/*.lua", "LUA" ) ) do + if not string.match( filename, "^sv_.+%.lua$" ) then + AddCSLuaFile( path .. "/" .. filename ) + end + end + + local sv_init = path .. "/sv_init.lua" + local cl_init = path .. "/cl_init.lua" + local shared = path .. "/shared.lua" + + if file.Exists( shared, "LUA" ) then + include( shared ) + end + + if SERVER and file.Exists( sv_init, "LUA" ) then + include( sv_init ) + end + + if CLIENT and file.Exists( cl_init, "LUA" ) then + include( cl_init ) + end +end + +local teams = nil +local meta = FindMetaTable( "Player" ) + +function meta:CanUseInventory() + if self:IsAdmin() then return true end -- always allow admins to access their inventories + if not self:Alive() then return false end -- using your inventory while dead can be a bit exploitable + + if #itemstore.config.LimitToJobs > 0 then + -- process this into an associative table for faster lookups + if not teams then + teams = {} + + for k, v in pairs( itemstore.config.LimitToJobs ) do + teams[ v ] = true + end + end + + if not teams[ self:Team() ] then + return false + end + end + + return true +end \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/skins/classic.lua b/addons/itemstore/lua/itemstore/skins/classic.lua new file mode 100644 index 0000000..1f13f92 --- /dev/null +++ b/addons/itemstore/lua/itemstore/skins/classic.lua @@ -0,0 +1,76 @@ +local SKIN = {} + +SKIN.GradientUp = Material( "gui/gradient_up" ) +SKIN.GradientDown = Material( "gui/gradient_down" ) +SKIN.Blur = Material( "pp/blurscreen" ) + +function SKIN:PaintFrame( panel, w, h ) + self.Blur:SetFloat( "$blur", 2 ) + self.Blur:Recompute() + render.UpdateScreenEffectTexture() + + local x, y = panel:LocalToScreen( 0, 0 ) + + surface.SetDrawColor( 255, 255, 255 ) + surface.SetMaterial( self.Blur ) + surface.DrawTexturedRect( x * -1, y * -1, ScrW(), ScrH() ) + + surface.SetDrawColor( itemstore.config.Colours.Lower ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetMaterial( self.GradientDown ) + surface.SetDrawColor( itemstore.config.Colours.Upper ) + surface.DrawTexturedRect( 0, 0, w, h ) + + surface.SetDrawColor( itemstore.config.Colours.OuterBorder ) + surface.DrawOutlinedRect( 0, 0, w, h ) + + surface.SetDrawColor( itemstore.config.Colours.InnerBorder ) + surface.DrawOutlinedRect( 1, 1, w - 2, h - 2 ) + + surface.SetDrawColor( itemstore.config.Colours.TitleBackground ) + surface.DrawRect( 2, 2, w - 4 , 22 ) +end + +function SKIN:PaintButton( panel, w, h ) + surface.SetDrawColor( Color( 200, 200, 200 ) ) + surface.DrawRect( 0, 0, w, h ) + + if not panel.Disabled then + surface.SetMaterial( panel.Depressed and self.GradientUp or self.GradientDown ) + surface.SetDrawColor( panel.Hovered and Color( 240, 240, 240 ) or Color( 230, 230, 230 ) ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + + surface.SetDrawColor( Color( 0, 0, 0, 150 ) ) + surface.DrawOutlinedRect( 0, 0, w, h ) +end + +function SKIN:PaintTab( panel, w, h ) + if panel:IsActive() then + surface.SetMaterial( self.GradientDown ) + surface.SetDrawColor( itemstore.config.Colours.Lower ) + surface.DrawTexturedRect( 2, 0, w - 5, h ) + else + surface.SetMaterial( self.GradientUp ) + surface.SetDrawColor( itemstore.config.Colours.Upper ) + surface.DrawTexturedRect( 2, 0, w - 5, h ) + end +end + +function SKIN:PaintPropertySheet( panel, w, h ) + surface.SetDrawColor( itemstore.config.Colours.Lower ) + surface.DrawRect( 0, 20, w, h ) + + surface.SetMaterial( self.GradientDown ) + surface.SetDrawColor( itemstore.config.Colours.Upper ) + surface.DrawTexturedRect( 0, 20, w, h - 20 ) + + surface.SetDrawColor( itemstore.config.Colours.OuterBorder ) + surface.DrawOutlinedRect( 0, 20, w, h - 20 ) + + surface.SetDrawColor( itemstore.config.Colours.InnerBorder ) + surface.DrawOutlinedRect( 1, 21, w - 2, h - 22 ) +end + +derma.DefineSkin( "itemstore", "Skin for ItemStore", SKIN ) diff --git a/addons/itemstore/lua/itemstore/skins/flat.lua b/addons/itemstore/lua/itemstore/skins/flat.lua new file mode 100644 index 0000000..e56a2c5 --- /dev/null +++ b/addons/itemstore/lua/itemstore/skins/flat.lua @@ -0,0 +1,117 @@ +local SKIN = {} + +SKIN.GradientUp = Material( "gui/gradient_up" ) +SKIN.GradientDown = Material( "gui/gradient_down" ) +SKIN.Blur = Material( "pp/blurscreen" ) + +function SKIN:PaintFrame( panel, w, h ) + self.Blur:SetFloat( "$blur", 8 ) + self.Blur:Recompute() + render.UpdateScreenEffectTexture() + + local x, y = panel:LocalToScreen( 0, 0 ) + + surface.SetDrawColor( 255, 255, 255 ) + surface.SetMaterial( self.Blur ) + surface.DrawTexturedRect( x * -1, y * -1, ScrW(), ScrH() ) + + surface.SetDrawColor( Color( 30, 30, 30, 200 ) ) + surface.DrawRect( 0, 22, w, h - 22 ) + + surface.SetDrawColor( Color( 44, 62, 80 ) ) + surface.DrawRect( 0, 0, w, 22 ) +end + +function SKIN:PaintButton( panel, w, h ) + surface.SetDrawColor( Color( 200, 200, 200 ) ) + surface.DrawRect( 0, 0, w, h ) + + if not panel.Disabled then + surface.SetMaterial( panel.Depressed and self.GradientUp or self.GradientDown ) + surface.SetDrawColor( panel.Hovered and Color( 240, 240, 240 ) or Color( 230, 230, 230 ) ) + surface.DrawTexturedRect( 0, 0, w, h ) + end + + surface.SetDrawColor( Color( 0, 0, 0, 150 ) ) + surface.DrawOutlinedRect( 0, 0, w, h ) +end + +function SKIN:PaintTab( panel, w, h ) + if panel:IsActive() then + draw.RoundedBoxEx( 2, 2, 0, w - 5, h - 8, Color( 0, 0, 0, 200 ), + true, true, false, false ) + else + draw.RoundedBoxEx( 2, 2, 0, w - 5, h, Color( 0, 0, 0, 150 ), + true, true, false, false ) + end +end + +function SKIN:PaintPropertySheet( panel, w, h ) + surface.SetDrawColor( Color( 0, 0, 0, 200 ) ) + surface.DrawRect( 0, 20, w, h ) +end + +function SKIN:PaintCategoryList( panel, w, h ) +end + +function SKIN:PaintCollapsibleCategory( panel, w, h ) + surface.SetDrawColor( Color( 0, 0, 0, 150 ) ) + surface.DrawRect( 0, 0, w, 20 ) + + surface.SetDrawColor( Color( 0, 0, 0, 150 ) ) + surface.DrawRect( 0, 0, w, h ) +end + +function SKIN:PaintWindowCloseButton( panel, w, h ) + local col = Color( 0, 0, 0, 50 ) + + if not panel:GetDisabled() and panel.Hovered then + if panel:IsDown() then + col = Color( 192, 57, 43 ) + else + col = Color( 231, 76, 60 ) + end + end + + draw.RoundedBoxEx( 4, 0, 2, w, 18, col, true, true, true, true ) + draw.SimpleText( "r", "Marlett", w / 2, 11, color_white, + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) +end + +function SKIN:PaintWindowMaximizeButton( panel, w, h ) + if panel:GetDisabled() then return end + + local col = Color( 0, 0, 0, 50 ) + + if panel.Hovered then + if panel:IsDown() then + col = Color( 41, 128, 185 ) + else + col = Color( 52, 152, 219 ) + end + end + + draw.RoundedBoxEx( 4, 0, 2, w, 18, col, false, false, false, false ) + draw.SimpleText( "1", "Marlett", w / 2, 11, color_white, + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) +end + +function SKIN:PaintWindowMinimizeButton( panel, w, h ) + if true then return end + + local col = Color( 0, 0, 0, 50 ) + + if panel.Hovered then + if panel:IsDown() then + col = Color( 41, 128, 185 ) + else + col = Color( 52, 152, 219 ) + end + end + + draw.RoundedBoxEx( 4, 0, 2, w, 18, col, true, false, true, false ) + draw.SimpleText( "0", "Marlett", w / 2, 11, color_white, + TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) +end + +derma.DefineSkin( "itemstore", "Flat skin for ItemStore", SKIN ) diff --git a/addons/itemstore/lua/itemstore/sv_commands.lua b/addons/itemstore/lua/itemstore/sv_commands.lua new file mode 100644 index 0000000..0892136 --- /dev/null +++ b/addons/itemstore/lua/itemstore/sv_commands.lua @@ -0,0 +1,34 @@ +itemstore.Commands = {} + +function itemstore.AddCommand( cmd, callback ) + itemstore.Commands[ cmd ] = callback +end + +function itemstore.RunCommand( pl, cmd, args ) + local func = itemstore.Commands[ cmd ] + if not func or not isfunction( func ) then return end + + return func( pl, args ) +end + +function itemstore.CommandExists( cmd ) + return itemstore.Commands[ cmd ] ~= nil +end + +hook.Add( "PlayerSay", "ItemStoreCommand", function( pl, text, team ) + local args = string.Explode( " ", text, false ) + local cmd = table.remove( args, 1 ) + + if not string.StartWith( cmd, itemstore.config.ChatCommandPrefix ) then + return + end + + cmd = string.sub( cmd, 2 ) + + if not itemstore.CommandExists( cmd ) then + return + end + + itemstore.RunCommand( pl, cmd, args ) + return "" +end ) \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/sv_data.lua b/addons/itemstore/lua/itemstore/sv_data.lua new file mode 100644 index 0000000..62d1d3e --- /dev/null +++ b/addons/itemstore/lua/itemstore/sv_data.lua @@ -0,0 +1,142 @@ +itemstore.data = {} + +PROVIDER = {} +include( "dataproviders/" .. itemstore.config.DataProvider .. ".lua" ) +itemstore.data.Provider = PROVIDER +PROVIDER = nil + +assert( itemstore.data.Provider, "[ItemStore] Data provider not found" ) + +if not file.IsDir( "itemstore", "DATA" ) then file.CreateDir( "itemstore" ) end +if not file.IsDir( "itemstore/banks", "DATA" ) then file.CreateDir( "itemstore/banks" ) end + +function itemstore.data.Run( func_name, ... ) + local func = itemstore.data.Provider[ func_name ] + + if func then + return func( itemstore.data.Provider, ... ) + end +end + +function itemstore.data.Initialize() + itemstore.data.Run( "Initialize" ) +end + +itemstore.data.Initialize() + +function itemstore.data.LoadInventory( pl ) + pl.InventoryLoaded = false + itemstore.data.Run( "LoadInventory", pl ) +end + +function itemstore.data.SaveInventory( pl ) + if pl.InventoryLoaded then + itemstore.data.Run( "SaveInventory", pl ) + end +end + +function itemstore.data.LoadBank( pl ) + pl.BankLoaded = false + itemstore.data.Run( "LoadBank", pl ) +end + +function itemstore.data.SaveBank( pl ) + if pl.BankLoaded then + itemstore.data.Run( "SaveBank", pl ) + end +end + +function itemstore.data.Load( pl ) + itemstore.data.LoadInventory( pl ) + itemstore.data.LoadBank( pl ) +end + +function itemstore.data.LoadAll() + for _, pl in ipairs( player.GetAll() ) do + itemstore.data.Load( pl ) + end +end + +function itemstore.data.Save( pl ) + itemstore.data.SaveInventory( pl ) + itemstore.data.SaveBank( pl ) +end + +function itemstore.data.SaveAll() + for _, pl in ipairs( player.GetAll() ) do + itemstore.data.Save( pl ) + end +end + +function itemstore.data.Export( filename ) + return itemstore.data.Run( "Export", filename ) +end + +function itemstore.data.Import( data ) + return itemstore.data.Run( "Import", data ) +end + +require( "json" ) + +concommand.Add( "itemstore_import", function( pl, cmd, args ) + if IsValid( pl ) and not pl:IsSuperAdmin() then + itemstore.Print( pl, itemstore.Translate( "not_permitted" ) ) + return + end + + local json = file.Read( args[ 1 ], "DATA" ) + if not json then + itemstore.Print( pl, itemstore.Translate( "file_not_found" ) ) + return + end + + local data = JSON:decode( json ) + if not data then + itemstore.Print( pl, itemstore.Translate( "invalid_data" ) ) + end + + itemstore.data.Import( data ) +end ) + +concommand.Add( "itemstore_export", function( pl, cmd, args ) + if IsValid( pl ) and not pl:IsSuperAdmin() then + itemstore.Print( pl, itemstore.Translate( "not_permitted" ) ) + return + end + + itemstore.data.Export( args[ 1 ] or os.date( "itemstore_export_%Y-%m-%d-%H-%M-%S.txt" ) ) +end ) + +-- we don't need any of these periodic saving things if we're using saveonwrite +timer.Create( "ItemStoreSave", itemstore.config.SaveInterval, 0, function() + if itemstore.config.SaveOnWrite then return end + + itemstore.data.SaveAll() +end ) + +-- save when we shutdown. +-- not called on crash tho +hook.Add( "ShutDown", "ItemStoreSaveOnShutdown", function() + itemstore.data.SaveAll() +end ) + +-- make sure we actually save on disconnect +-- using the game event here cause PlayerDisconnected is called inconsistently, prolly cause of a shitty addon +gameevent.Listen( "player_disconnect" ) +hook.Add( "player_disconnect", "ItemStoreSaveOnDisconnectFix", function( data ) + local pl = player.GetBySteamID( data.networkid ) + if not IsValid( pl ) then return end + + itemstore.data.Save( pl ) +end ) + +hook.Add( "Tick", "ItemStoreSaveOnWrite", function() + if not itemstore.config.SaveOnWrite then return end + + for _, pl in ipairs( player.GetAll() ) do + if pl.NextInventorySave and pl.NextInventorySave < CurTime() then + itemstore.data.Save( pl ) + pl.NextInventorySave = nil + end + end +end ) \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/sv_init.lua b/addons/itemstore/lua/itemstore/sv_init.lua new file mode 100644 index 0000000..c77edd8 --- /dev/null +++ b/addons/itemstore/lua/itemstore/sv_init.lua @@ -0,0 +1,102 @@ +include( "sv_commands.lua" ) + +include( "shared.lua" ) + +include( "sv_data.lua" ) +include( "sv_player.lua" ) +--include( "sv_statistics.lua" ) + +AddCSLuaFile( "shared.lua" ) +AddCSLuaFile( "language.lua" ) +AddCSLuaFile( "cl_player.lua" ) +AddCSLuaFile( "containers.lua" ) +AddCSLuaFile( "items.lua" ) +AddCSLuaFile( "gamemodes.lua" ) +AddCSLuaFile( "config.lua" ) +AddCSLuaFile( "admin.lua" ) +AddCSLuaFile( "trading.lua" ) + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "cl_gui.lua" ) + +AddCSLuaFile( "skins/" .. itemstore.config.Skin .. ".lua" ) + +for _, filename in ipairs( file.Find( "itemstore/vgui/*.lua", "LUA" ) ) do + AddCSLuaFile( "itemstore/vgui/" .. filename ) +end + +if itemstore.config.AntiDupe then + local meta = FindMetaTable( "Entity" ) + local oldRemove = meta.Remove + + function meta:Remove() + if IsValid( self ) then + self.__Deleted = true + end + + oldRemove( self ) + end +end + +function itemstore.Print( pl, text ) + if IsValid( pl ) then + pl:PrintMessage( HUD_PRINTCONSOLE, text ) + else + print( text ) + end +end + +RunConsoleCommand( "lua_log_sv", 1 ) + +concommand.Add( "itemstore_support", function( pl, cmd, args ) + if IsValid( pl ) and not pl:IsSuperAdmin() then return end + + local function respond( str ) + if IsValid( pl ) and false then + pl:PrintMessage( HUD_PRINTCONSOLE, str ) + else + print( str ) + end + end + + local token = args[ 1 ] + if not token then + respond( "Error: token not defined. Please create a support ticket and ask for one." ) + return + end + + local user = IsValid( pl ) and pl:Name() .. " (" .. pl:SteamID() .. ")" or "Console" + local ip, port = string.match( game.GetIPAddress(), "(%d.%d.%d.%d):(%d)" ) + local hostname = GetHostName() + local ws_addons, legacy_addons = file.Find( "addons/*", "GAME" ) + local config = file.Read( "itemstore/config.lua", "LUA" ) or "" + local errors = file.Read( "lua_errors_server.txt", "GAME" ) or "" + + respond( "Uploading support information..." ) + + http.Post( "https://uselessghost.me/itemstore/support.php", { + token = token, + user = user, + ip = ip, + port = port, + hostname = hostname, + ws_addons = util.TableToJSON( ws_addons ), + legacy_addons = util.TableToJSON( legacy_addons ), + config = config, + errors = errors, + }, function( data ) + local json = util.JSONToTable( data ) + + if not json then + respond( "Error: Invalid data received." ) + respond( data ) + return + end + + if json.success then + respond( "Support information uploaded." ) + else + respond( "Support information upload failed: " .. json.error ) + end + end ) +end ) \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/sv_player.lua b/addons/itemstore/lua/itemstore/sv_player.lua new file mode 100644 index 0000000..e0489d7 --- /dev/null +++ b/addons/itemstore/lua/itemstore/sv_player.lua @@ -0,0 +1,470 @@ +ITEMSTORE_TIMEOUT = 0.25 + +local meta = FindMetaTable( "Player" ) + +function meta:SetupInventory() + local usergroup = self:GetUserGroup() + + if serverguard then + -- suck my dick serverguard, why don't you use gmod's default rank stuff + usergroup = serverguard.player:GetRank( self ) + end + + local inv_size = itemstore.config.InventorySizes[ usergroup ] + + if not inv_size then + inv_size = itemstore.config.InventorySizes[ "default" ] + end + + self.Inventory = itemstore.Container( unpack( inv_size ) ) + self.Inventory:SetOwner( self ) + self.Inventory:SetPermissions( self, true, true ) + + self.Inventory:AddCallback( "set", function() + if IsValid( self ) then self:QueueInventorySave() end + end ) + + self.Inventory:AddCallback( "read", function( con, pl ) + if not IsValid( self ) then return end + + local wep = pl:GetActiveWeapon() + local dist = self:GetPos():Distance( pl:GetPos() ) + + if IsValid( wep ) and wep:GetClass() == "itemstore_checker" and dist < 250 then + return true + end + end ) + + local bank_size = itemstore.config.BankSizes[ usergroup ] + + if not bank_size then + bank_size = itemstore.config.BankSizes[ "default" ] + end + + self.Bank = itemstore.Container( unpack( bank_size ) ) + self.Bank:SetOwner( self ) + self.Bank:AddCallback( "set", function() + if IsValid( self ) then self:QueueInventorySave() end + end ) + + local function callback( con, pl ) + if self ~= pl then return end + + for _, ent in ipairs( ents.FindByClass( "itemstore_bank" ) ) do + if ent:GetPos():Distance( pl:EyePos() ) < 256 then + return true + end + end + + return false + end + + self.Bank:AddCallback( "read", callback ) + self.Bank:AddCallback( "write", callback ) + + itemstore.data.Load( self ) + + hook.Run( "ItemStoreInventoryCreated", self, self.Inventory, self.Bank ) + + if itemstore.config.MigrateOldData then + local path = "itemstore/" .. self:UniqueID() .. ".txt" + + if file.Exists( path, "DATA" ) then + local inv = util.JSONToTable( file.Read( path, "DATA" ) ) + + if inv then + for k, v in ipairs( inv ) do + self.Inventory:SetItem( k, itemstore.Item( v.UniqueName, v.Data ) ) + end + + file.Delete( path, "DATA" ) + end + end + + local path = "itemstore/banks/" .. self:UniqueID() .. ".txt" + + if file.Exists( path, "DATA" ) then + local inv = util.JSONToTable( file.Read( path, "DATA" ) ) + + if inv then + for k, v in ipairs( inv ) do + self.Bank:SetItem( k, itemstore.Item( v.UniqueName, v.Data ) ) + end + + file.Delete( path, "DATA" ) + end + end + end + + return self.Inventory, self.Bank +end + +function meta:PickupItem( ent ) + if not self:CanUseInventory() then return false end + + ent = ent or util.QuickTrace( self:GetShootPos(), + self:GetAimVector() * itemstore.config.PickupDistance, self ).Entity + + if not IsValid( ent ) or ent.__Deleted or not itemstore.items.Pickups[ ent:GetClass() ] then + return false end + + local class = itemstore.items.Pickups[ ent:GetClass() ] + if itemstore.config.DisabledItems[ class ] then return false end + + local item = itemstore.Item( class ) + if not item then return end + + item:SaveData( ent ) + + local con = itemstore.config.PickupsGotoBank and self.Bank or self.Inventory + + if not con:CanFit( item ) then return false end + if hook.Call( "ItemStoreCanPickup", GAMEMODE, self, item, ent ) == false then return false end + if not item:CanPickup( self, ent ) then return false end + + local slot = con:AddItem( item ) + self:QueueInventorySave() + + if slot then + item:Pickup( self, con, slot, ent ) + ent:Remove() + + --self:ChatPrint( itemstore.Translate( "picked_up", item:GetName() ) ) + self:EmitSound( "items/gunpickup2.wav" ) + + return true + end +end + +function meta:MoveItem( from_con_id, from_slot, to_con_id, to_slot ) + if not self:CanUseInventory() then return false end + + local from_con = itemstore.containers.Get( from_con_id ) + local to_con = itemstore.containers.Get( to_con_id ) + if not from_con or not to_con then return end + + local from_item = from_con:GetItem( from_slot ) + local to_item = to_con:GetItem( to_slot ) + if not from_item and not to_item then return end + + if from_con:CanWrite( self, "move", to_con, to_slot, to_item, from_slot, from_item ) ~= false and + to_con:CanWrite( self, "move", from_con, from_slot, from_item, to_slot, to_item ) ~= false then + + from_con:SetSuppressed( true ) + to_con:SetSuppressed( true ) + + from_con:SetItem( from_slot, to_item ) + to_con:SetItem( to_slot, from_item ) + + from_con:SetSuppressed( false ) + to_con:SetSuppressed( false ) + + from_con:QueueSync() + if from_con ~= to_con then + to_con:QueueSync() + end + end +end + +util.AddNetworkString( "ItemStoreMove" ) +net.Receive( "ItemStoreMove", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + local from_con_id = net.ReadUInt( 32 ) + local from_slot = net.ReadUInt( 32 ) + local to_con_id = net.ReadUInt( 32 ) + local to_slot = net.ReadUInt( 32 ) + + pl:MoveItem( from_con_id, from_slot, to_con_id, to_slot ) +end ) + +function meta:UseItem( con_id, slot, ... ) + if not self:CanUseInventory() then return false end + + local con = itemstore.containers.Get( con_id ) + if not con then return end + + local item = con:GetItem( slot ) + + if not item or not item.Use then return end + if con:CanWrite( self, "use", slot, item ) == false then return end + + if item:Use( self, con, slot, ... ) then + con:SetItem( slot, nil ) + end + + con:QueueSync() + self:QueueInventorySave() +end + +util.AddNetworkString( "ItemStoreUse" ) +net.Receive( "ItemStoreUse", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + local con_id = net.ReadUInt( 32 ) + local slot = net.ReadUInt( 32 ) + local args = {}--net.ReadTable() + + pl:UseItem( con_id, slot, unpack( args ) ) +end ) + +function meta:UseItemWith( from_con_id, from_slot, to_con_id, to_slot ) + if not self:CanUseInventory() then return false end + + local from_con = itemstore.containers.Get( from_con_id ) + local to_con = itemstore.containers.Get( to_con_id ) + + if not from_con or not to_con then return end + + local from_item = from_con:GetItem( from_slot ) + local to_item = to_con:GetItem( to_slot ) + if not from_item or not to_item or from_item == to_item then return end + + if not from_item.UseWith or not from_item:CanUseWith( to_item ) then return end + + if from_con:CanWrite( self, "usewith", from_slot, from_item, to_con, + to_slot, to_item ) == false then return end + + from_con:SetSuppressed( true ) + to_con:SetSuppressed( true ) + + if from_item:UseWith( self, to_item, from_con, from_slot, to_con, to_slot ) then + from_con:SetItem( from_slot, nil ) + end + + from_con:SetSuppressed( false ) + to_con:SetSuppressed( false ) + + from_con:QueueSync() + if from_con ~= to_con then to_con:QueueSync() end +end + +util.AddNetworkString( "ItemStoreUseWith" ) +net.Receive( "ItemStoreUseWith", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + local from_con_id = net.ReadUInt( 32 ) + local from_slot = net.ReadUInt( 32 ) + local to_con_id = net.ReadUInt( 32 ) + local to_slot = net.ReadUInt( 32 ) + + pl:UseItemWith( from_con_id, from_slot, to_con_id, to_slot ) +end ) + +function meta:DropItem( con_id, slot ) + if not self:CanUseInventory() then return false end + + local con = itemstore.containers.Get( con_id ) + if not con then return end + + local item = con:GetItem( slot ) + if not item then return end + + if con:CanWrite( self, "drop", slot, item ) == false then return end + + local pos = util.QuickTrace( self:GetShootPos(), + self:GetAimVector() * itemstore.config.DropDistance, self ).HitPos + local ent = item:CreateEntity( pos ) + item:Drop( self, con, slot, ent ) + + if CPPI then + ent:CPPISetOwner( self ) + end + + if item.DropStack or item:TakeOne() then + con:SetItem( slot, nil ) + end + + con:QueueSync() + self:QueueInventorySave() + + self:EmitSound( "items/ammocrate_open.wav" ) +end + +util.AddNetworkString( "ItemStoreDrop" ) +net.Receive( "ItemStoreDrop", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + local con_id = net.ReadUInt( 32 ) + local slot = net.ReadUInt( 32 ) + + pl:DropItem( con_id, slot ) +end ) + +function meta:DestroyItem( con_id, slot ) + if not self:CanUseInventory() then return false end + + local con = itemstore.containers.Get( con_id ) + if not con then return end + + local item = con:GetItem( slot ) + if not item then return end + + if item and con:CanWrite( self, "destroy", slot, item ) ~= false then + item:Destroy( self, con, slot ) + con:SetItem( slot, nil ) + + self:EmitSound( "physics/concrete/concrete_break2.wav" ) + end +end + +util.AddNetworkString( "ItemStoreDestroy" ) +net.Receive( "ItemStoreDestroy", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + local con_id = net.ReadUInt( 32 ) + local slot = net.ReadUInt( 32 ) + + pl:DestroyItem( con_id, slot ) +end ) + +function meta:MergeItem( from_con_id, from_slot, to_con_id, to_slot ) + if not self:CanUseInventory() then return false end + + local from_con = itemstore.containers.Get( from_con_id ) + local to_con = itemstore.containers.Get( to_con_id ) + if not from_con or not to_con then return end + + local from_item = from_con:GetItem( from_slot ) + local to_item = to_con:GetItem( to_slot ) + if not from_item or not to_item then return end + + if not ( from_con == to_con and from_slot == to_slot ) and + from_con:CanWrite( self, "merge", to_con_id, from_slot, from_item, to_slot, to_item ) ~= false and + to_con:CanWrite( self, "merge", from_con_id, from_slot, from_item, to_slot, to_item ) ~= false and + to_item:CanMerge( from_item ) then + + from_con:SetSuppressed( true ) + to_con:SetSuppressed( true ) + + to_item:Merge( from_item ) + from_con:SetItem( from_slot, nil ) + + from_con:SetSuppressed( false ) + to_con:SetSuppressed( false ) + + from_con:QueueSync() + if from_con ~= to_con then to_con:QueueSync() end + end +end + +util.AddNetworkString( "ItemStoreMerge" ) +net.Receive( "ItemStoreMerge", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + local from_con_id = net.ReadUInt( 32 ) + local from_slot = net.ReadUInt( 32 ) + local to_con_id = net.ReadUInt( 32 ) + local to_slot = net.ReadUInt( 32 ) + + pl:MergeItem( from_con_id, from_slot, to_con_id, to_slot ) +end ) + +function meta:SplitItem( con_id, slot, amount ) + if not self:CanUseInventory() then return false end + + local con = itemstore.containers.Get( con_id ) + if not con then return end + + local item = con:GetItem( slot ) + if not item then return end + + if con:FirstEmptySlot() and item:CanSplit( amount ) and con:CanWrite( self, "split", slot, item, amount ) ~= false then + con:AddItem( item:Split( amount ), true ) + end +end + +util.AddNetworkString( "ItemStoreSplit" ) +net.Receive( "ItemStoreSplit", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + local con_id = net.ReadUInt( 32 ) + local slot = net.ReadUInt( 32 ) + local amount = math.max( net.ReadUInt( 16 ), 1 ) + + pl:SplitItem( con_id, slot, amount ) +end ) + +util.AddNetworkString( "ItemStoreOpen" ) +function meta:OpenContainer( id, name, hideinv ) + if not self:CanUseInventory() then return false end + + net.Start( "ItemStoreOpen" ) + net.WriteUInt( id, 32 ) + net.WriteString( name ) + net.WriteBit( hideinv ) + net.Send( self ) +end + +function meta:QueueInventorySave() + self.NextInventorySave = CurTime() + 1 +end + +util.AddNetworkString( "ItemStoreSyncInventory" ) +net.Receive( "ItemStoreSyncInventory", function( len, pl ) + if pl.ItemStoreTimeout and pl.ItemStoreTimeout > CurTime() then return end + pl.ItemStoreTimeout = CurTime() + ITEMSTORE_TIMEOUT + + pl.Inventory:Sync() + + net.Start( "ItemStoreSyncInventory" ) + net.WriteUInt( pl.Inventory:GetID(), 32 ) + net.Send( pl ) +end ) + +hook.Add( "PlayerInitialSpawn", "ItemStoreLoadIn=ventory", function( pl ) + -- since the player loads quickly in sp, don't bother with the delay. + -- this is only important in mp, since it's unlikely a player would be using mysql ranks in sp + local delay = game.SinglePlayer() and 0 or 1 + + timer.Simple( delay, function() + if IsValid( pl ) then pl:SetupInventory() end + end ) +end ) + +hook.Add( "PlayerUse", "ItemStorePickup", function( pl, ent ) + -- fuck you wiremod + -- why would you EVER replace the player parameter with some random entity + -- what is your fucking damage + if not IsValid( pl ) or not pl:IsPlayer() then return end + + if ent.__Deleted then + -- Anti-dupe, never let anything marked as deleted to be used + return false + end + + -- thanks brax + if itemstore.config.PickupKey > -1 and pl:KeyDown( itemstore.config.PickupKey ) + and pl:PickupItem( ent ) then + + return false + end +end ) + +hook.Add( "PlayerDeath", "ItemStoreDeathLoot", function( pl ) + if not itemstore.config.DeathLoot then return end + if table.Count( pl.Inventory:GetItems() ) <= 0 then return end + if not pl:CanUseInventory() then return false end -- don't drop items if they're not usable + + local ent = ents.Create( "itemstore_deathloot" ) + ent:SetPos( pl:GetPos() ) + ent:Spawn() + + ent.Container:SetWidth( pl.Inventory:GetWidth() ) + ent.Container:SetHeight( pl.Inventory:GetHeight() ) + ent.Container:SetPages( pl.Inventory:GetPages() ) + + for k, v in pairs( pl.Inventory:GetItems() ) do + ent.Container:SetItem( k, v ) + pl.Inventory:SetItem( k, nil ) + end + + ent.Timeout = CurTime() + itemstore.config.DeathLootTimeout +end ) \ No newline at end of file diff --git a/addons/itemstore/lua/itemstore/trading.lua b/addons/itemstore/lua/itemstore/trading.lua new file mode 100644 index 0000000..72462c3 --- /dev/null +++ b/addons/itemstore/lua/itemstore/trading.lua @@ -0,0 +1,287 @@ +itemstore.trading = {} + +local Trade = {} + +function Trade:GetSide( pl ) + if self.Right.Player == pl then + return self.Right + elseif self.Left.Player == pl then + return self.Left + end +end + +function Trade:SetMoneyOffer( pl, offer ) + local side = self:GetSide( pl ) + + if side then + side.Money = math.Clamp( offer, 0, itemstore.gamemodes.GetMoney( pl ) ) + if SERVER then self:Sync() end + end +end + +function Trade:GetMoneyOffer( pl ) + local side = self:GetSide( pl ) + + if side then + return side.Money + end +end + +function Trade:SetReady( pl, ready ) + local side = self:GetSide( pl ) + + if side then + side.Ready = ready + if SERVER then self:Sync() end + end +end + +function Trade:GetReady( pl ) + local side = self:GetSide( pl ) + + if side then + return side.Ready + end +end + +function Trade:IsReady() + return self.Left.Ready and self.Right.Ready +end + + +if SERVER then + util.AddNetworkString( "ItemStoreTrade" ) + function Trade:Sync() + net.Start( "ItemStoreTrade" ) + net.WriteEntity( self.Left.Player ) + net.WriteUInt( self.Left.Money, 32 ) + net.WriteUInt( self.Left.Container:GetID(), 32 ) + net.WriteBit( self.Left.Ready ) + + net.WriteEntity( self.Right.Player ) + net.WriteUInt( self.Right.Money, 32 ) + net.WriteUInt( self.Right.Container:GetID(), 32 ) + net.WriteBit( self.Right.Ready ) + net.Send( { self.Left.Player, self.Right.Player } ) + end + + util.AddNetworkString( "ItemStoreCloseTrade" ) + function Trade:Close() + if IsValid( self.Left.Player ) then + for k, v in pairs( self.Left.Container:GetItems() ) do + self.Left.Player.Inventory:AddItem( v ) + end + end + + itemstore.containers.Remove( self.Left.Container:GetID() ) + + if IsValid( self.Right.Player ) then + for k, v in pairs( self.Right.Container:GetItems() ) do + self.Right.Player.Inventory:AddItem( v ) + end + end + + itemstore.containers.Remove( self.Right.Container:GetID() ) + + self.Left.Player.Trade = nil + self.Right.Player.Trade = nil + + net.Start( "ItemStoreCloseTrade" ) + net.Send( { self.Left.Player, self.Right.Player } ) + end + + function Trade:Accept() + if not self:IsReady() then return end + + if IsValid( self.Left.Player ) and IsValid( self.Right.Player ) then + local leftmoney = itemstore.gamemodes.GetMoney( self.Left.Player ) + local rightmoney = itemstore.gamemodes.GetMoney( self.Right.Player ) + + if leftmoney >= self.Left.Money and rightmoney >= self.Right.Money then + for k, v in pairs( self.Left.Container:GetItems() ) do + self.Left.Container:SetItem( k, nil ) + self.Right.Player.Inventory:AddItem( v ) + end + + for k, v in pairs( self.Right.Container:GetItems() ) do + self.Right.Container:SetItem( k, nil ) + self.Left.Player.Inventory:AddItem( v ) + end + + itemstore.gamemodes.GiveMoney( self.Left.Player, -self.Left.Money ) + itemstore.gamemodes.GiveMoney( self.Left.Player, self.Right.Money ) + + itemstore.gamemodes.GiveMoney( self.Right.Player, self.Left.Money ) + itemstore.gamemodes.GiveMoney( self.Right.Player, -self.Right.Money ) + else + self.Left.Player:ChatPrint( itemstore.Translate( "trade_failed" ) ) + self.Right.Player:ChatPrint( itemstore.Translate( "trade_failed" ) ) + end + end + + self:Close() + end + + util.AddNetworkString( "ItemStoreTradeMessage" ) + function Trade:Message( pl, message ) + net.Start( "ItemStoreTradeMessage" ) + net.WriteEntity( pl ) + net.WriteString( message ) + net.Send( { self.Left.Player, self.Right.Player } ) + end +else + function Trade:SendMessage( message ) + net.Start( "ItemStoreTradeMessage" ) + net.WriteString( message ) + net.SendToServer() + end +end + +function itemstore.Trade( left, right, con_left, con_right ) + local trade = { + Left = { + Player = left, + Money = 0, + Container = con_left or itemstore.Container( 4, 3, 1 ), + Ready = false + }, + + Right = { + Player = right, + Money = 0, + Container = con_right or itemstore.Container( 4, 3, 1 ), + Ready = false + } + } + + setmetatable( trade, { __index = Trade } ) + + left.Trade = trade + right.Trade = trade + + if SERVER then + trade.Left.Container:SetPermissions( left, true, true ) + trade.Left.Container:SetPermissions( right, true, false ) + + trade.Left.Container:AddCallback( "set", function( slot, item ) + trade:SetReady( trade.Left.Player, false ) + trade:SetReady( trade.Right.Player, false ) + end ) + + trade.Left.Container:Sync() + + trade.Right.Container:SetPermissions( left, true, false ) + trade.Right.Container:SetPermissions( right, true, true ) + + trade.Right.Container:AddCallback( "set", function( slot, item ) + trade:SetReady( trade.Left.Player, false ) + trade:SetReady( trade.Right.Player, false ) + end ) + + trade.Right.Container:Sync() + + trade:Sync() + end + + return trade +end + +if SERVER then + --[[ + util.AddNetworkString( "ItemStoreStartTrade" ) + net.Receive( "ItemStoreStartTrade", function( len, pl ) + local partner = net.ReadEntity() + + if not IsValid( partner ) or not partner:IsPlayer() then + itemstore.Trade( pl, partner ) + end + end ) + ]] + + util.AddNetworkString( "ItemStoreReadyTrade" ) + net.Receive( "ItemStoreReadyTrade", function( len, pl ) + if pl.Trade then + pl.Trade:SetReady( pl, net.ReadBit() == 1 ) + end + end ) + + util.AddNetworkString( "ItemStoreAcceptTrade" ) + net.Receive( "ItemStoreAcceptTrade", function( len, pl ) + if pl.Trade then + pl.Trade:Accept() + end + end ) + + util.AddNetworkString( "ItemStoreCloseTrade" ) + net.Receive( "ItemStoreCloseTrade", function( len, pl ) + if pl.Trade then + pl.Trade:Close() + end + end ) + + util.AddNetworkString( "ItemStoreTradeMoney" ) + net.Receive( "ItemStoreTradeMoney", function( len, pl ) + if pl.Trade then + pl.Trade:SetMoneyOffer( pl, net.ReadUInt( 32 ) ) + end + end ) + + util.AddNetworkString( "ItemStoreTradeMessage" ) + net.Receive( "ItemStoreTradeMessage", function( len, pl ) + if pl.Trade then + pl.Trade:Message( pl, net.ReadString() ) + end + end ) +else + itemstore.trading.Panel = nil + + net.Receive( "ItemStoreTrade", function() + left_pl = net.ReadEntity() + left_money = net.ReadUInt( 32 ) + left_con = itemstore.containers.Get( net.ReadUInt( 32 ) ) + left_ready = net.ReadBit() == 1 + + right_pl = net.ReadEntity() + right_money = net.ReadUInt( 32 ) + right_con = itemstore.containers.Get( net.ReadUInt( 32 ) ) + right_ready = net.ReadBit() == 1 + + local trade = itemstore.Trade( left_pl, right_pl, left_con, right_con ) + + trade.Left.Money = left_money + trade.Left.Ready = left_ready + + trade.Right.Money = right_money + trade.Right.Ready = right_ready + + LocalPlayer().Trade = trade + + if not IsValid( itemstore.trading.Panel ) then + if trade.Left.Player == LocalPlayer() then + itemstore.trading.Panel = vgui.Create( "ItemStoreTrade" ) + itemstore.trading.Panel:Center() + itemstore.trading.Panel:MakePopup() + else + itemstore.trading.Panel = vgui.Create( "ItemStoreTradeRequest" ) + itemstore.trading.Panel:SetSize( 200, 100 ) + itemstore.trading.Panel:SetPos( ScrW() - itemstore.trading.Panel:GetWide(), 0 ) + end + end + + itemstore.trading.Panel:Refresh() + end ) + + net.Receive( "ItemStoreCloseTrade", function() + LocalPlayer().Trade = nil + + if IsValid( itemstore.trading.Panel ) then + itemstore.trading.Panel:Close() + end + end ) + + net.Receive( "ItemStoreTradeMessage", function() + if IsValid( itemstore.trading.Panel ) then + itemstore.trading.Panel:ChatMessage( net.ReadEntity(), net.ReadString() ) + end + end ) +end diff --git a/addons/itemstore/lua/itemstore/vgui/Admin.lua b/addons/itemstore/lua/itemstore/vgui/Admin.lua new file mode 100644 index 0000000..b6f9add --- /dev/null +++ b/addons/itemstore/lua/itemstore/vgui/Admin.lua @@ -0,0 +1,37 @@ +local PANEL = {} + +function PANEL:Init() + self:SetTitle( itemstore.Translate( "admin_title" ) ) + self:SetSkin( "itemstore" ) + + self.Scroll = vgui.Create( "DScrollPanel", self ) + self.Scroll:Dock( FILL ) + + self.List = vgui.Create( "DListLayout", self.Scroll ) + self.List:Dock( FILL ) + for _, pl in ipairs( player.GetAll() ) do + local b = self.List:Add( "DButton" ) + b:SetText( pl:Name() ) + b:DockMargin( 0, 0, 0, 2 ) + + function b.DoClick() + local menu = DermaMenu() + + menu:AddOption( itemstore.Translate( "inventory" ), function() + net.Start( "ItemStoreAdminInventory" ) + net.WriteEntity( pl ) + net.SendToServer() + end ) + + menu:AddOption( itemstore.Translate( "bank" ), function() + net.Start( "ItemStoreAdminBank" ) + net.WriteEntity( pl ) + net.SendToServer() + end ) + + menu:Open() + end + end +end + +vgui.Register( "ItemStoreAdmin", PANEL, "DFrame" ) diff --git a/addons/itemstore/lua/itemstore/vgui/Container.lua b/addons/itemstore/lua/itemstore/vgui/Container.lua new file mode 100644 index 0000000..6504d67 --- /dev/null +++ b/addons/itemstore/lua/itemstore/vgui/Container.lua @@ -0,0 +1,69 @@ +local PANEL = {} + +function PANEL:Init() + self.Pages = {} + self.Slots = {} + + table.insert( itemstore.containers.Panels, self ) +end + +function PANEL:SetContainerID( id ) + self.ContainerID = id + self:Refresh() +end + +function PANEL:GetContainerID() + return self.ContainerID +end + +function PANEL:Refresh() + local id = self:GetContainerID() + local con = itemstore.containers.Get( id ) + + if con then + for i = 1, con:GetSize() do + local page_id = con:GetPageFromSlot( i ) + local page = self.Pages[ page_id ] + + if not page then + page = vgui.Create( "DIconLayout" ) + page:SetSpaceX( 1 ) + page:SetSpaceY( 1 ) + + self.Pages[ page_id ] = page + + self:AddSheet( itemstore.Translate( "page", page_id ), page ) + end + + local slot = self.Slots[ i ] + + if not slot then + slot = page:Add( "ItemStoreSlot" ) + slot:SetSize( 40, 40 ) + slot:SetContainerID( self:GetContainerID() ) + slot:SetSlot( i ) + + self.Slots[ i ] = slot + end + + slot:SetItem( con:GetItem( i ) ) + slot:Refresh() + end + end + + self:SizeToContents() +end + +function PANEL:SizeToContents() + local id = self:GetContainerID() + local con = itemstore.containers.Get( id ) + + if con then + local w = con:GetWidth() * 41 + 15 + local h = con:GetHeight() * 41 + 35 + + self:SetSize( w, h ) + end +end + +vgui.Register( "ItemStoreContainer", PANEL, "DPropertySheet" ) diff --git a/addons/itemstore/lua/itemstore/vgui/ContainerWindow.lua b/addons/itemstore/lua/itemstore/vgui/ContainerWindow.lua new file mode 100644 index 0000000..187187a --- /dev/null +++ b/addons/itemstore/lua/itemstore/vgui/ContainerWindow.lua @@ -0,0 +1,29 @@ +local PANEL = {} + +function PANEL:Init() + self:SetSkin( "itemstore" ) + + self.Container = vgui.Create( "ItemStoreContainer", self ) + self.Container:SizeToContents() +end + +function PANEL:PerformLayout() + self:SetSize( self.Container:GetWide() + 10, self.Container:GetTall() + 32 ) + self.Container:SetPos( 5, 27 ) + + self.BaseClass.PerformLayout( self ) +end + +function PANEL:Refresh() + self.Container:Refresh() +end + +function PANEL:SetContainerID( id ) + self.Container:SetContainerID( id ) +end + +function PANEL:GetContainerID() + return self.Container:GetContainerID() +end + +vgui.Register( "ItemStoreContainerWindow", PANEL, "DFrame" ) diff --git a/addons/itemstore/lua/itemstore/vgui/ItemTooltip.lua b/addons/itemstore/lua/itemstore/vgui/ItemTooltip.lua new file mode 100644 index 0000000..e76fc55 --- /dev/null +++ b/addons/itemstore/lua/itemstore/vgui/ItemTooltip.lua @@ -0,0 +1,95 @@ +DEFINE_BASECLASS( "DListLayout" ) + +local PANEL = {} + +AccessorFunc( PANEL, "ContainerID", "ContainerID", FORCE_NUMBER ) +AccessorFunc( PANEL, "Slot", "Slot", FORCE_NUMBER ) +AccessorFunc( PANEL, "Item", "Item" ) + +function PANEL:Init() + self:SetWide( 200 ) + self:SetDrawOnTop( true ) + self:DockPadding( 5, 5, 5, 5 ) + + self.Name = self:Add( "DLabel" ) + self.Name:SetFont( "DermaDefaultBold" ) + self.Name:SetWrap( true ) + + self.Model = self:Add( "DModelPanel" ) + self.Model:SetSize( 125, 125 ) + + self.Description = self:Add( "DLabel" ) + self.Description:SetWrap( true ) +end + +PANEL.Blur = Material( "pp/blurscreen" ) +function PANEL:Paint( w, h ) + self.Blur:SetFloat( "$blur", 8 ) + self.Blur:Recompute() + render.UpdateScreenEffectTexture() + + local x, y = self:LocalToScreen( 0, 0 ) + + surface.SetDrawColor( 255, 255, 255 ) + surface.SetMaterial( self.Blur ) + surface.DrawTexturedRect( x * -1, y * -1, ScrW(), ScrH() ) + + surface.SetDrawColor( Color( 30, 30, 30, 200 ) ) + surface.DrawRect( 0, 0, w, h ) +end + +function PANEL:PerformLayout() + self.Name:SizeToContents() + self.Description:SizeToContents() + + BaseClass.PerformLayout( self ) +end + +function PANEL:Refresh() + local item = self:GetItem() + + if not item then + self.Model.Entity:Remove() + self.Name:SetText( "" ) + self.Description:SetText( "" ) + + return + end + + local name = item:GetName() + local desc = item:GetDescription() or "" + + if item:GetAmount() > 1 then + name = name .. " x" .. item:GetAmount() + end + + if self:GetSlot() then + desc = desc .. "\n\n" .. itemstore.Translate( "dragtomove" ) + desc = desc .. "\n" .. itemstore.Translate( "mclicktodrop" ) + desc = desc .. "\n" .. itemstore.Translate( "rclickforoptions" ) + + if item.Use then + desc = desc .. "\n" .. itemstore.Translate( "dclicktouse" ) + end + end + + self.Name:SetText( name ) + self.Name:SizeToContents() + + self.Description:SetText( desc ) + self.Description:SizeToContents() + + self.Model:SetModel( item:GetModel() ) + + self.Model.Entity:SetMaterial( item:GetMaterial() ) + self.Model:SetColor( item:GetColor() or color_white ) + + min, max = self.Model.Entity:GetRenderBounds() + + self.Model:SetCamPos( Vector( 0.55, 0.55, 0.55 ) * min:Distance( max ) ) + self.Model:SetLookAt( ( min + max ) / 2 ) + + self:InvalidateLayout( true ) +end + +vgui.Register( "ItemStoreTooltip", PANEL, "DListLayout" ) diff --git a/addons/itemstore/lua/itemstore/vgui/Slot.lua b/addons/itemstore/lua/itemstore/vgui/Slot.lua new file mode 100644 index 0000000..dcc5940 --- /dev/null +++ b/addons/itemstore/lua/itemstore/vgui/Slot.lua @@ -0,0 +1,277 @@ +local PANEL = {} + +surface.CreateFont( "ItemStoreAmount", { + font = system.IsLinux() and "DejaVu Sans" or "Tahoma", + size = 11, + weight = 500 +} ) + +local GradientUp = Material( "gui/gradient_up" ) +local GradientDown = Material( "gui/gradient_down" ) + +AccessorFunc( PANEL, "Item", "Item" ) +AccessorFunc( PANEL, "ContainerID", "ContainerID", FORCE_NUMBER ) +AccessorFunc( PANEL, "Slot", "Slot", FORCE_NUMBER ) + +function PANEL:Init() + self.BaseClass.Init( self ) + + self:Droppable( "ItemStore" ) + self:Receiver( "ItemStore", function( receiver, droptable, dropped ) + local droppable = droptable[ 1 ] + + if not dropped then return end + + local droppable = droptable[ 1 ] + + local from_con = droppable:GetContainerID() + local to_con = droppable:GetContainerID() + + if not from_con then return end + if not to_con then return end + + local from_slot = droppable:GetSlot() + local to_slot = receiver:GetSlot() + + if not from_slot then return end + if not to_slot then return end + + local from_item = droppable:GetItem() + local to_item = receiver:GetItem() + + if from_item and to_item and ( from_item:CanMerge( to_item ) or + from_item:CanUseWith( to_item ) ) then + local menu = DermaMenu() + + if from_item:CanUseWith( to_item ) then + menu:AddOption( itemstore.Translate( "usewith" ), function() + LocalPlayer():UseItemWith( droppable:GetContainerID(), droppable:GetSlot(), + receiver:GetContainerID(), receiver:GetSlot() ) + end ):SetIcon( "icon16/wrench_orange.png" ) + + menu:AddSpacer() + end + + menu:AddOption( itemstore.Translate( "move" ), function() + LocalPlayer():MoveItem( droppable:GetContainerID(), droppable:GetSlot(), + receiver:GetContainerID(), receiver:GetSlot() ) + end ):SetIcon( "icon16/arrow_switch.png" ) + + if from_item:CanMerge( to_item ) then + menu:AddOption( itemstore.Translate( "merge" ), function() + LocalPlayer():MergeItem( droppable:GetContainerID(), droppable:GetSlot(), + receiver:GetContainerID(), receiver:GetSlot() ) + end ):SetIcon( "icon16/arrow_join.png" ) + end + + menu:Open() + else + LocalPlayer():MoveItem( droppable:GetContainerID(), droppable:GetSlot(), + receiver:GetContainerID(), receiver:GetSlot() ) + end + end ) +end + +function PANEL:Paint( w, h ) + if itemstore.config.HighlightStyle == "old" or itemstore.config.HighlightStyle == "border" then + surface.SetDrawColor( self.Hovered and itemstore.config.Colours.HoveredSlot or itemstore.config.Colours.Slot ) + else + surface.SetDrawColor( itemstore.config.Colours.Slot ) + end + + surface.DrawRect( 0, 0, w, h ) + + local item = self:GetItem() + + if item and item.HighlightColor then + local col = Color( item.HighlightColor.r, item.HighlightColor.g, item.HighlightColor.b ) + local bright = Color( col.r * 1.25, col.g * 1.25, col.b * 1.25 ) + local dark = Color( bright.r / 2, bright.g / 2, bright.b / 2 ) + + if itemstore.config.HighlightStyle == "full" then + surface.SetDrawColor( dark ) + surface.DrawRect( 0, 0, w, h ) + + surface.SetMaterial( GradientDown ) + surface.SetDrawColor( self.Hovered and bright or col ) + surface.DrawTexturedRect( 0, 0, w, h ) + elseif itemstore.config.HighlightStyle == "subtle" then + surface.SetMaterial( GradientUp ) + surface.SetDrawColor( self.Hovered and bright or col ) + surface.DrawTexturedRect( 0, 0, w, h ) + elseif itemstore.config.HighlightStyle == "corner" then + surface.SetMaterial( GradientUp ) + surface.SetDrawColor( self.Hovered and bright or col ) + surface.DrawTexturedRectRotated( w, h, w * 1.25, h * 1.25, 45 ) + elseif itemstore.config.HighlightStyle == "border" then + surface.SetDrawColor( col ) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + end + + if not itemstore.config.HighlightStyle ~= "border" or not item then + surface.SetDrawColor( itemstore.config.Colours.OuterBorder ) + surface.DrawOutlinedRect( 0, 0, w, h ) + end + + surface.SetDrawColor( itemstore.config.Colours.InnerBorder ) + surface.DrawOutlinedRect( 1, 1, w - 2, h - 2 ) + + self.BaseClass.Paint( self, w, h ) + + local item = self:GetItem() + if item and item:GetAmount() > 1 then + draw.SimpleTextOutlined( item:FormatAmount(), "ItemStoreAmount", 4, + h - 2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM, 1, color_black ) + end +end + +function PANEL:Refresh() + local item = self:GetItem() + + if item then + self:SetModel( item:GetModel() ) + self:SetColor( item:GetColor() or color_white ) + + if IsValid( self.Entity ) then + self.Entity:SetMaterial( item:GetMaterial() ) + + local min, max = self.Entity:GetRenderBounds() + + self:SetCamPos( Vector( 0.55, 0.55, 0.55 ) * min:Distance( max ) ) + self:SetLookAt( ( min + max ) / 2 ) + end + else + self.Entity = nil + self:SetTooltip( nil ) + end +end + +function PANEL:DoDoubleClick() + local con_id = self:GetContainerID() + local slot = self:GetSlot() + local item = self:GetItem() + + if not con_id then return end + if not slot then return end + if not item then return end + if not item.Use then return end + + LocalPlayer():UseItem( con_id, slot ) +end + +function PANEL:DoMiddleClick() + local con_id = self:GetContainerID() + local slot = self:GetSlot() + local item = self:GetItem() + + if not con_id then return end + if not slot then return end + if not item then return end + + LocalPlayer():DropItem( con_id, slot ) +end + +function PANEL:DoRightClick() + local con_id = self:GetContainerID() + local slot = self:GetSlot() + local item = self:GetItem() + + if not con_id then return end + if not slot then return end + if not item then return end + + local menu = DermaMenu() + + if item.Use then + menu:AddOption( itemstore.Translate( "use" ), function() + LocalPlayer():UseItem( con_id, slot ) + end ):SetIcon( "icon16/wrench.png" ) + + menu:AddSpacer() + end + + menu:AddOption( itemstore.Translate( "drop" ), function() + LocalPlayer():DropItem( con_id, slot ) + end ):SetIcon( "icon16/arrow_out.png" ) + + menu:AddOption( itemstore.Translate( "destroy" ), function() + Derma_Query( itemstore.Translate( "destroy_confirmation" ), itemstore.Translate( "destroy_title" ), itemstore.Translate( "ok" ), function() + LocalPlayer():DestroyItem( con_id, slot ) + end, itemstore.Translate( "cancel" ) ):SetSkin( "itemstore" ) + end ):SetIcon( "icon16/delete.png" ) + + if item:CanSplit( 1 ) then + menu:AddSpacer() + + local submenu, entry = menu:AddSubMenu( itemstore.Translate( "split" ) ) + entry:SetIcon( "icon16/arrow_divide.png" ) + + local half = math.floor( item:GetAmount() / 2 ) + + submenu:AddOption( itemstore.Translate( "split_half", half ), function() + LocalPlayer():SplitItem( con_id, slot, half ) + end ) + + submenu:AddSpacer() + + for _, amount in ipairs( { 1, 2, 5, 10, 25, 50, 100, 250, 1000 } ) do + if item:CanSplit( amount ) then + submenu:AddOption( amount, function() + LocalPlayer():SplitItem( con_id, slot, amount ) + end ) + end + end + + menu:Open() + end + + item:Run( "PopulateMenu", menu ) + + menu:Open() +end + +local Tooltip + +function PANEL:CreateTooltip() + if IsValid( Tooltip ) then + Tooltip:SetVisible( true ) + return + end + + Tooltip = vgui.Create( "ItemStoreTooltip" ) + self:UpdateTooltip() +end + +function PANEL:UpdateTooltip() + if not IsValid( Tooltip ) then return end + + Tooltip:SetContainerID( self:GetContainerID() ) + Tooltip:SetSlot( self:GetSlot() ) + Tooltip:SetItem( self:GetItem() ) + Tooltip:Refresh() +end + +function PANEL:HideTooltip() + if IsValid( Tooltip ) then Tooltip:SetVisible( false ) end +end + +function PANEL:OnCursorEntered() + if not self:GetItem() then return end + + self:CreateTooltip() + self:UpdateTooltip() +end + +function PANEL:OnCursorMoved() + if not IsValid( Tooltip ) then return end + + local x, y = gui.MousePos() + Tooltip:SetPos( x, y - Tooltip:GetTall() ) +end + +function PANEL:OnCursorExited() + self:HideTooltip() +end + +vgui.Register( "ItemStoreSlot", PANEL, "DModelPanel" ) diff --git a/addons/itemstore/lua/itemstore/vgui/Trade.lua b/addons/itemstore/lua/itemstore/vgui/Trade.lua new file mode 100644 index 0000000..afd5668 --- /dev/null +++ b/addons/itemstore/lua/itemstore/vgui/Trade.lua @@ -0,0 +1,158 @@ +local PANEL = {} + +function PANEL:Init() + self:SetSkin( "itemstore" ) + + self.LeftContainer = vgui.Create( "ItemStoreContainer", self ) + self.RightContainer = vgui.Create( "ItemStoreContainer", self ) + + self.LeftMoneyIcon = vgui.Create( "DImage", self ) + self.LeftMoneyIcon:SetImage( "icon16/money.png" ) + + self.RightMoneyIcon = vgui.Create( "DImage", self ) + self.RightMoneyIcon:SetImage( "icon16/money.png" ) + + self.LeftMoney = vgui.Create( "DTextEntry", self ) + self.LeftMoney.OnLoseFocus = function( money ) + net.Start( "ItemStoreTradeMoney" ) + net.WriteUInt( tonumber( money:GetText() ) or 0, 32 ) + net.SendToServer() + end + + self.RightMoney = vgui.Create( "DLabel", self ) + self.RightMoney:SetText( "0" ) + + self.LeftReady = vgui.Create( "DCheckBoxLabel", self ) + self.LeftReady:SetText( itemstore.Translate( "ready" ) ) + self.LeftReady.OnChange = function( ready ) + net.Start( "ItemStoreReadyTrade" ) + net.WriteBit( ready:GetChecked() ) + net.SendToServer() + end + + self.RightReady = vgui.Create( "DLabel", self ) + self.RightReady:SetText( itemstore.Translate( "not_ready" ) ) + + self.Accept = vgui.Create( "DButton", self ) + self.Accept:SetText( itemstore.Translate( "accept" ) ) + self.Accept:SetFont( "DermaLarge" ) + self.Accept:SetDisabled( true ) + self.Accept.DoClick = function() + net.Start( "ItemStoreAcceptTrade" ) net.SendToServer() + + surface.PlaySound( "buttons/button9.wav" ) + self:Remove() + end + + self.Chat = vgui.Create( "RichText", self ) + function self.Chat:Paint() + draw.RoundedBox( 4, 0, 0, self:GetWide(), self:GetTall(), Color( 230, 230, 230 ) ) + end + + self.ChatEntry = vgui.Create( "DTextEntry", self ) + function self.ChatEntry:OnEnter() + LocalPlayer().Trade:SendMessage( self:GetText() ) + + self:SetText( "" ) + self:RequestFocus() + end + + self.Inventory = vgui.Create( "ItemStoreContainerWindow" ) + self.Inventory:SetContainerID( LocalPlayer().InventoryID ) + self.Inventory:SetTitle( itemstore.Translate( "inventory" ) ) + self.Inventory:ShowCloseButton( false ) + self.Inventory:InvalidateLayout( true ) +end + +function PANEL:Think() + self.BaseClass.Think( self ) + + local x, y = self:GetPos() + self.Inventory:SetPos( x + self:GetWide() / 2 - self.Inventory:GetWide() / 2, y + self:GetTall() + 5 ) +end + +function PANEL:ChatMessage( pl, message ) + if pl == LocalPlayer() then + self.Chat:InsertColorChange( 255, 0, 0, 255 ) + else + self.Chat:InsertColorChange( 0, 0, 255, 255 ) + end + + self.Chat:AppendText( pl:Name() ) + self.Chat:InsertColorChange( 100, 100, 100, 255 ) + self.Chat:AppendText( ": " .. message .. "\n" ) +end + +function PANEL:Refresh() + local trade = LocalPlayer().Trade + + if trade then + local ourside = LocalPlayer() == trade.Left.Player and trade.Left or trade.Right + local otherside = LocalPlayer() == trade.Right.Player and trade.Left or trade.Right + + self:SetTitle( itemstore.Translate( "trading_with", otherside.Player:Name() ) ) + + self.LeftContainer:SetContainerID( ourside.Container:GetID() ) + self.RightContainer:SetContainerID( otherside.Container:GetID() ) + + self.LeftMoney:SetText( ourside.Money ) + self.RightMoney:SetText( otherside.Money ) + + self.LeftReady:SetChecked( ourside.Ready ) + self.RightReady:SetText( otherside.Ready and itemstore.Translate( "ready" ) + or itemstore.Translate( "not_ready" ) ) + + self.Accept:SetDisabled( not ( ourside.Ready and otherside.Ready ) ) + end +end + +-- it's me, i'm coding hitler. this is the grave of so many brave +-- functions that went to war and died on my behalf. +function PANEL:PerformLayout() + self.BaseClass.PerformLayout( self ) + + self.LeftContainer:SetPos( 5, 33 ) + self.LeftContainer:SizeToContents() + local left_w, left_h = self.LeftContainer:GetSize() + + self.RightContainer:SetPos( 5 + left_w + 24, 33 ) + self.RightContainer:SizeToContents() + local right_w, right_h = self.RightContainer:GetSize() + + self.LeftMoneyIcon:SetPos( 5, 33 + left_h + 5 ) + self.LeftMoneyIcon:SetSize( 16, 16 ) + + self.LeftMoney:SetPos( 5 + 16 + 5, 33 + left_h + 5 ) + self.LeftMoney:SetSize( left_w - 5 - 16 - 5, 16 ) + + self.RightMoneyIcon:SetPos( 5 + right_w + 24, 33 + right_h + 5 ) + self.RightMoneyIcon:SetSize( 16, 16 ) + + self.RightMoney:SetPos( 5 + left_w + 24 + 5 + 16 + 5, 33 + right_h + 5 ) + self.RightMoney:SetSize( right_w - 5 - 16 - 5, 16 ) + + self.LeftReady:SetPos( 5, 33 + left_h + 5 + 16 + 5 ) + self.LeftReady:SizeToContents() + + self.RightReady:SetPos( 5 + left_w + 24, 33 + right_h + 5 + 16 + 5 ) + + self.RightReady:SizeToContents() + + self.Accept:SetPos( 5, 33 + left_h + 5 + ( 16 + 5 ) * 2 ) + self.Accept:SetSize( 390, 37 ) + + self.Chat:SetPos( 400, 27 ) + self.Chat:SetSize( 195, 220 ) + + self.ChatEntry:SetPos( 400, 250 ) + self.ChatEntry:SetSize( 195, 25 ) + + self:SetSize( 600, 280 ) +end + +function PANEL:OnRemove() + net.Start( "ItemStoreCloseTrade" ) net.SendToServer() + self.Inventory:Close() +end + +vgui.Register( "ItemStoreTrade", PANEL, "DFrame" ) diff --git a/addons/itemstore/lua/itemstore/vgui/TradeRequest.lua b/addons/itemstore/lua/itemstore/vgui/TradeRequest.lua new file mode 100644 index 0000000..176fbce --- /dev/null +++ b/addons/itemstore/lua/itemstore/vgui/TradeRequest.lua @@ -0,0 +1,50 @@ +local PANEL = {} + +function PANEL:Init() + self:SetSkin( "itemstore" ) + self:SetTitle( itemstore.Translate( "trade_request" ) ) + + self:ShowCloseButton( false ) + + self.Label = vgui.Create( "DLabel", self ) + + self.Accept = vgui.Create( "DButton", self ) + self.Accept:SetText( itemstore.Translate( "accept" ) ) + + function self.Accept.DoClick() + itemstore.trading.Panel = vgui.Create( "ItemStoreTrade" ) + itemstore.trading.Panel:Refresh() + itemstore.trading.Panel:Center() + itemstore.trading.Panel:MakePopup() + + self:Remove() + end + + self.Deny = vgui.Create( "DButton", self ) + self.Deny:SetText( itemstore.Translate( "deny" ) ) + + function self.Deny.DoClick() + net.Start( "ItemStoreCloseTrade" ) net.SendToServer() + self:Remove() + end +end + +function PANEL:Refresh() + self.Label:SetText( itemstore.Translate( "wants_to_trade", + LocalPlayer().Trade.Left.Player:Name() ) ) +end + +function PANEL:PerformLayout() + self.BaseClass.PerformLayout( self ) + + self.Label:SizeToContents() + self.Label:SetPos( self:GetWide() / 2 - self.Label:GetWide() / 2, 30 ) + + self.Accept:SetSize( 75, 30 ) + self.Accept:SetPos( self:GetWide() / 2 - self.Accept:GetWide() - 15, self:GetTall() / 2 + 10 ) + + self.Deny:SetSize( 75, 30 ) + self.Deny:SetPos( self:GetWide() / 2 + 15, self:GetTall() / 2 + 10 ) +end + +vgui.Register( "ItemStoreTradeRequest", PANEL, "DFrame" ) diff --git a/addons/itemstore/lua/weapons/itemstore_checker.lua b/addons/itemstore/lua/weapons/itemstore_checker.lua new file mode 100644 index 0000000..ba0cd8d --- /dev/null +++ b/addons/itemstore/lua/weapons/itemstore_checker.lua @@ -0,0 +1,62 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.PrintName = "Чекер инвенторя" + +SWEP.Purpose = "Проверка инвентаря другого игрокаr" +SWEP.Instructions = "ЛКМ, Проверить инвентарь игрока перед вами" +SWEP.Category = "Scora" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.ViewModel = "models/weapons/cstrike/c_c4.mdl" +SWEP.WorldModel = "" +SWEP.UseHands = true + +SWEP.Primary.Clipsize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.Clipsize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Slot = 1 +SWEP.SlotPos = 10 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +SWEP.Range = 250 + +function SWEP:Initialize() + self:SetHoldType( "normal" ) +end + +function SWEP:OnDrop() + self:Remove() +end + +function SWEP:PrimaryAttack() + if CLIENT then return end + + local tr = util.TraceLine{ + start = self.Owner:GetShootPos(), + endpos = self.Owner:GetShootPos() + self.Owner:GetAimVector() * self.Range, + filter = self.Owner + } + + if not tr.Hit then return end + if not IsValid( tr.Entity ) or not tr.Entity:IsPlayer() then return end + + local inv = tr.Entity.Inventory + if not inv then return end + + inv:Sync( self.Owner ) + self.Owner:OpenContainer( inv:GetID(), itemstore.Translate( "inventory" ), true ) +end + +function SWEP:SecondaryAttack() +end diff --git a/addons/itemstore/lua/weapons/itemstore_pickup.lua b/addons/itemstore/lua/weapons/itemstore_pickup.lua new file mode 100644 index 0000000..06acb46 --- /dev/null +++ b/addons/itemstore/lua/weapons/itemstore_pickup.lua @@ -0,0 +1,51 @@ +if SERVER then + AddCSLuaFile() +end + +SWEP.PrintName = "Инвентарь" + +SWEP.Purpose = "Собираем вещи" +SWEP.Instructions = "ПКМ, Забрать предмет. ЛКМ, Открыть инвентарь" +SWEP.Category = "Scora" + +SWEP.Spawnable = true +SWEP.AdminSpawnable = true +SWEP.ViewModel = "models/weapons/c_arms.mdl" +SWEP.WorldModel = "" +SWEP.UseHands = true + +SWEP.Primary.Clipsize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.Clipsize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.Slot = 1 +SWEP.SlotPos = 10 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +function SWEP:Initialize() + self:SetHoldType( "normal" ) +end + +function SWEP:OnDrop() + self:Remove() +end + +function SWEP:PrimaryAttack() + if CLIENT then return end + + self.Owner:PickupItem() +end + +function SWEP:SecondaryAttack() + if CLIENT then return end + + self.Owner:OpenContainer( self.Owner.Inventory:GetID(), + itemstore.Translate( "inventory" ), true ) +end diff --git a/addons/mantle_darkfated_ultracode/LICENSE b/addons/mantle_darkfated_ultracode/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/addons/mantle_darkfated_ultracode/README.md b/addons/mantle_darkfated_ultracode/README.md new file mode 100644 index 0000000..b9c9ecb --- /dev/null +++ b/addons/mantle_darkfated_ultracode/README.md @@ -0,0 +1,112 @@ +# Mantle + +🎈 Универсальная библиотека GLua для Garry's Mod: создание интерфейсов и удобные утилиты. + +Весь код снабжён комментариями — изучайте и находите примеры прямо в исходниках. + +## Возможности + +- Кастомные VGUI-элементы +- Быстрый рендеринг через RNDX +- Загрузка материалов по ссылке +- Гибкая система цветовых тем +- Уведомления для игроков на сервера +- Модульная архитектура +- Поддержка кириллицы и UTF-8 +- Единое меню с документацией и настройками + +## Меню библиотеки + +Имеется меню с документацией и настройками. Для открытия используйте консольную команду: `mantle_menu`. + +## Примеры компонентов + +### Документация и элементы VGUI + +image + +### Лёгкий режим окна + +image + +### ComboBox + +image + +### SlideBox + +image + +### Таблицы + +image + +### Поле ввода + +image + +### Всплывающие элементы + +image + +### Круговое меню + +image + +### Опциональное меню + +image + +### Цветовые темы + +image +image +image +image +image +image + +### И главное - плавность и магия анимаций + +https://github.com/user-attachments/assets/6a813fd1-6da2-4c59-a84b-f78abfc20900 + +## Сторонние примеры + +### Отправка серверных уведомлений + +```lua +hook.Add('PlayerSpawn', 'Test', function(pl) + Mantle.notify(pl, Color(75, 0, 0), 'Заголовок', 'Привет, ' .. pl:Name() .. '!') + -- первым аргументом true, в случае отправки всем игрокам +end) +``` + +### Картинка через ссылку + +```lua +http.DownloadMaterial('https://i.imgur.com/eEnGbcp.jpeg', 'dog.png', function(your_mat) + hook.Add('HUDPaint', 'Test', function() + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(your_mat) + surface.DrawTexturedRect(5, 5, 250, 330) + end) +end) +``` + +### Преобразование символов кириллицы + +```lua +hook.Add('HUDPaint', 'test', function() + local txt = 'ПриВЕТ МИР Hello World' + -- default + draw.SimpleText(string.lower(txt), 'Fated.20', 15, 15, color_black) + -- mantle + draw.SimpleText(utf8.lower(txt), 'Fated.20', 15, 35, color_black) +end) +``` + +Сравнение default и mantle функции + +## Steam Workshop + +Для автообновления – [подпишитесь и добавьте аддон в серверную коллекцию](https://steamcommunity.com/sharedfiles/filedetails/?id=3126986993). Таким образом сможете всегда получать актуальную версию библиотеки ✅ diff --git a/addons/mantle_darkfated_ultracode/lua/autorun/mantle_autorun.lua b/addons/mantle_darkfated_ultracode/lua/autorun/mantle_autorun.lua new file mode 100644 index 0000000..4ed7ef0 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/autorun/mantle_autorun.lua @@ -0,0 +1,4 @@ +RNDX = include('mantle/modules/rndx.lua') + +AddCSLuaFile('mantle/init.lua') +include('mantle/init.lua') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/config/colors.lua b/addons/mantle_darkfated_ultracode/lua/mantle/config/colors.lua new file mode 100644 index 0000000..2828457 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/config/colors.lua @@ -0,0 +1,511 @@ +Mantle.color_dark = { + header = Color(40, 40, 40), -- верхняя панель + header_text = Color(100, 100, 100), -- цвет элементов в заголовке + background = Color(25, 25, 25), -- фон + background_alpha = Color(25, 25, 25, 210), -- фон с прозрачностью + background_panelpopup = Color(20, 20, 20, 150), -- фон для DermaMenu + + button = Color(54, 54, 54), -- кнопка + button_shadow = Color(0, 0, 0, 25), -- тень кнопки для градиента + button_hovered = Color(60, 60, 62), -- кнопка при наведении + + category = Color(50, 50, 50), -- категория + category_opened = Color(50, 50, 50, 0), -- категория открыта + + theme = Color(106, 108, 197), -- тема интерфейса + + panel = { -- варианты цветов для панели + Color(60, 60, 60), + Color(50, 50, 50), + Color(80, 80, 80) + }, + + toggle = Color(56, 56, 56), -- тумблер + + focus_panel = Color(46, 46, 46), -- универсальный цвет для элементов + hover = Color(60, 65, 80), -- универсальное выделение + + window_shadow = Color(0, 0, 0, 100), -- тень окна + + gray = Color(150, 150, 150, 220), + text = Color(255, 255, 255) +} +Mantle.color_dark.panel_alpha = { -- прозрачные панели + ColorAlpha(Mantle.color_dark.panel[1], 150), + ColorAlpha(Mantle.color_dark.panel[2], 150), + ColorAlpha(Mantle.color_dark.panel[3], 150) +} + +-- Тёмная палитра (монотонная) + +Mantle.color_dark_mono = table.Copy(Mantle.color_dark) +Mantle.color_dark_mono.theme = Color(121, 121, 121) + +-- Светлая палитра +Mantle.color_light = { + header = Color(240, 240, 240), + header_text = Color(150, 150, 150), + background = Color(255, 255, 255), + background_alpha = Color(255, 255, 255, 170), + background_panelpopup = Color(245, 245, 245, 150), + + button = Color(235, 235, 235), + button_shadow = Color(0, 0, 0, 15), + button_hovered = Color(196, 199, 218), + + category = Color(240, 240, 245), + category_opened = Color(240, 240, 245, 0), + + theme = Color(106, 108, 197), + + panel = { + Color(250, 250, 255), + Color(240, 240, 245), + Color(230, 230, 235) + }, + + toggle = Color(220, 220, 230), + + focus_panel = Color(245, 245, 255), + hover = Color(235, 240, 255), + + window_shadow = Color(0, 0, 0, 50), + + gray = Color(130, 130, 130, 220), + text = Color(20, 20, 20) +} +Mantle.color_light.panel_alpha = { + ColorAlpha(Mantle.color_light.panel[1], 120), + ColorAlpha(Mantle.color_light.panel[2], 120), + ColorAlpha(Mantle.color_light.panel[3], 120) +} + +-- Синяя палитра +Mantle.color_blue = { + header = Color(36, 48, 66), + header_text = Color(109, 129, 159), + background = Color(24, 28, 38), + background_alpha = Color(24, 28, 38, 210), + background_panelpopup = Color(20, 24, 32, 150), + + button = Color(38, 54, 82), + button_shadow = Color(18, 22, 32, 35), + button_hovered = Color(47, 69, 110), + + category = Color(34, 48, 72), + category_opened = Color(34, 48, 72, 0), + + theme = Color(80, 160, 220), + + panel = { + Color(34, 48, 72), + Color(38, 54, 82), + Color(70, 120, 180) + }, + + toggle = Color(34, 44, 66), + + focus_panel = Color(48, 72, 90), + hover = Color(80, 160, 220, 90), + + window_shadow = Color(18, 22, 32, 100), + + gray = Color(150, 170, 190, 200), + text = Color(210, 220, 235) +} +Mantle.color_blue.panel_alpha = { + ColorAlpha(Mantle.color_blue.panel[1], 110), + ColorAlpha(Mantle.color_blue.panel[2], 110), + ColorAlpha(Mantle.color_blue.panel[3], 110) +} + +-- Красная палитра +Mantle.color_red = { + header = Color(54, 36, 36), + header_text = Color(159, 109, 109), + background = Color(32, 24, 24), + background_alpha = Color(32, 24, 24, 210), + background_panelpopup = Color(28, 20, 20, 150), + + button = Color(66, 38, 38), + button_shadow = Color(32, 18, 18, 35), + button_hovered = Color(97, 50, 50), + + category = Color(62, 34, 34), + category_opened = Color(62, 34, 34, 0), + + theme = Color(180, 80, 80), + + panel = { + Color(62, 34, 34), + Color(66, 38, 38), + Color(140, 70, 70) + }, + + toggle = Color(60, 34, 34), + + focus_panel = Color(72, 48, 48), + hover = Color(180, 80, 80, 90), + + window_shadow = Color(32, 18, 18, 100), + + gray = Color(180, 150, 150, 200), + text = Color(235, 210, 210) +} +Mantle.color_red.panel_alpha = { + ColorAlpha(Mantle.color_red.panel[1], 110), + ColorAlpha(Mantle.color_red.panel[2], 110), + ColorAlpha(Mantle.color_red.panel[3], 110) +} + +-- Зелёная палитра +Mantle.color_green = { + header = Color(36, 54, 40), + header_text = Color(109, 159, 109), + background = Color(24, 32, 26), + background_alpha = Color(24, 32, 26, 210), + background_panelpopup = Color(20, 28, 22, 150), + + button = Color(38, 66, 48), + button_shadow = Color(18, 32, 22, 35), + button_hovered = Color(48, 88, 62), + + category = Color(34, 62, 44), + category_opened = Color(34, 62, 44, 0), + + theme = Color(80, 180, 120), + + panel = { + Color(34, 62, 44), + Color(38, 66, 48), + Color(70, 140, 90) + }, + + toggle = Color(34, 60, 44), + + focus_panel = Color(48, 72, 58), + hover = Color(80, 180, 120, 90), + + window_shadow = Color(18, 32, 22, 100), + + gray = Color(150, 180, 150, 200), + text = Color(210, 235, 210) +} +Mantle.color_green.panel_alpha = { + ColorAlpha(Mantle.color_green.panel[1], 110), + ColorAlpha(Mantle.color_green.panel[2], 110), + ColorAlpha(Mantle.color_green.panel[3], 110) +} + +-- Оранжевая палитра +Mantle.color_orange = { + header = Color(70, 35, 10), + header_text = Color(250, 230, 210), + background = Color(255, 250, 240), + background_alpha = Color(255, 250, 240, 220), + background_panelpopup = Color(255, 245, 235, 160), + + button = Color(184, 122, 64), + button_shadow = Color(20, 10, 0, 30), + button_hovered = Color(197, 129, 65), + + category = Color(255, 245, 235), + category_opened = Color(255, 245, 235, 0), + + theme = Color(245, 130, 50), + + panel = { + Color(255, 250, 240), + Color(250, 220, 180), + Color(235, 150, 90) + }, + + toggle = Color(143, 121, 104), + + focus_panel = Color(255, 240, 225), + hover = Color(255, 165, 80, 90), + + window_shadow = Color(20, 8, 0, 100), + gray = Color(180, 161, 150, 200), + text = Color(45, 20, 10) +} + +Mantle.color_orange.panel_alpha = { + ColorAlpha(Mantle.color_orange.panel[1], 120), + ColorAlpha(Mantle.color_orange.panel[2], 120), + ColorAlpha(Mantle.color_orange.panel[3], 120) +} + +-- Фиолетовая палитра +Mantle.color_purple = { + header = Color(40, 36, 56), + header_text = Color(150, 140, 180), + background = Color(25, 22, 30), + background_alpha = Color(25, 22, 30, 210), + background_panelpopup = Color(28, 24, 40, 150), + + button = Color(58, 52, 76), + button_shadow = Color(8, 6, 20, 30), + button_hovered = Color(74, 64, 105), + + category = Color(46, 40, 60), + category_opened = Color(46, 40, 60, 0), + + theme = Color(138, 114, 219), + + panel = { + Color(56, 48, 76), + Color(44, 36, 64), + Color(120, 90, 200) + }, + + toggle = Color(43, 39, 53), + + focus_panel = Color(48, 42, 62), + hover = Color(138, 114, 219, 90), + + window_shadow = Color(8, 6, 20, 100), + + gray = Color(140, 128, 148, 220), + text = Color(245, 240, 255) +} +Mantle.color_purple.panel_alpha = { + ColorAlpha(Mantle.color_purple.panel[1], 150), + ColorAlpha(Mantle.color_purple.panel[2], 150), + ColorAlpha(Mantle.color_purple.panel[3], 150) +} + +-- Кофейная палитра +Mantle.color_coffee = { + header = Color(67, 48, 36), + header_text = Color(210, 190, 170), + + background = Color(45, 32, 25), + background_alpha = Color(45, 32, 25, 215), + background_panelpopup = Color(38, 28, 22, 150), + + button = Color(84, 60, 45), + button_shadow = Color(20, 10, 5, 40), + button_hovered = Color(100, 75, 55), + + category = Color(72, 54, 42), + category_opened = Color(72, 54, 42, 0), + + theme = Color(150, 110, 75), + + panel = { + Color(68, 50, 40), + Color(90, 65, 50), + Color(150, 110, 75) + }, + + toggle = Color(53, 40, 31), + + focus_panel = Color(70, 55, 40), + hover = Color(150, 110, 75, 90), + + window_shadow = Color(15, 10, 5, 100), + + gray = Color(180, 150, 130, 200), + text = Color(235, 225, 210) +} +Mantle.color_coffee.panel_alpha = { + ColorAlpha(Mantle.color_coffee.panel[1], 110), + ColorAlpha(Mantle.color_coffee.panel[2], 110), + ColorAlpha(Mantle.color_coffee.panel[3], 110) +} + +-- Ледяная палитра +Mantle.color_ice = { + header = Color(190, 225, 250), + header_text = Color(68, 104, 139), + background = Color(235, 245, 255), + background_alpha = Color(235, 245, 255, 200), + background_panelpopup = Color(220, 235, 245, 150), + + button = Color(145, 185, 225), + button_shadow = Color(80, 110, 140, 40), + button_hovered = Color(170, 210, 255), + + category = Color(200, 225, 245), + category_opened = Color(200, 225, 245, 0), + + theme = Color(100, 170, 230), + + panel = { + Color(146, 186, 211), + Color(107, 157, 190), + Color(74, 132, 184) + }, + + toggle = Color(168, 194, 219), + + focus_panel = Color(205, 230, 245), + hover = Color(100, 170, 230, 80), + + window_shadow = Color(60, 100, 140, 100), + + gray = Color(92, 112, 133, 200), + text = Color(20, 35, 50) +} +Mantle.color_ice.panel_alpha = { + ColorAlpha(Mantle.color_ice.panel[1], 120), + ColorAlpha(Mantle.color_ice.panel[2], 120), + ColorAlpha(Mantle.color_ice.panel[3], 120) +} + +-- Винная палитра +Mantle.color_wine = { + header = Color(59, 42, 53), + header_text = Color(246, 242, 246), + background = Color(31, 23, 22), + background_alpha = Color(31, 23, 22, 210), + background_panelpopup = Color(36, 28, 28, 150), + + button = Color(79, 50, 60), + button_shadow = Color(10, 6, 18, 30), + button_hovered = Color(90, 52, 65), + + category = Color(79, 50, 60), + category_opened = Color(79, 50, 60, 0), + + theme = Color(148, 61, 91), + + panel = { + Color(79, 50, 60), + Color(63, 44, 48), + Color(160, 85, 143) + }, + + toggle = Color(63, 40, 47), + + focus_panel = Color(70, 48, 58), + hover = Color(192, 122, 217, 90), + + window_shadow = Color(10, 6, 20, 100), + + gray = Color(170, 150, 160, 200), + text = Color(246, 242, 246) +} +Mantle.color_wine.panel_alpha = { + ColorAlpha(Mantle.color_wine.panel[1], 150), + ColorAlpha(Mantle.color_wine.panel[2], 150), + ColorAlpha(Mantle.color_wine.panel[3], 150) +} + +-- Фиалковая палитра +Mantle.color_violet = { + header = Color(49, 50, 68), + header_text = Color(238, 244, 255), + background = Color(22, 24, 35), + background_alpha = Color(22, 24, 35, 210), + background_panelpopup = Color(36, 40, 56, 150), + + button = Color(58, 64, 84), + button_shadow = Color(8, 6, 18, 30), + button_hovered = Color(64, 74, 104), + + category = Color(58, 64, 84), + category_opened = Color(58, 64, 84, 0), + + theme = Color(159, 180, 255), + + panel = { + Color(58, 64, 84), + Color(48, 52, 72), + Color(109, 136, 255) + }, + + toggle = Color(46, 51, 66), + + focus_panel = Color(56, 62, 86), + hover = Color(159, 180, 255, 90), + + window_shadow = Color(8, 6, 20, 100), + + gray = Color(147, 147, 184, 200), + text = Color(238, 244, 255) +} +Mantle.color_violet.panel_alpha = { + ColorAlpha(Mantle.color_violet.panel[1], 150), + ColorAlpha(Mantle.color_violet.panel[2], 150), + ColorAlpha(Mantle.color_violet.panel[3], 150) +} + +-- Моховая палитра +Mantle.color_moss = { + header = Color(42, 50, 36), + header_text = Color(232, 244, 235), + background = Color(14, 16, 12), + background_alpha = Color(14, 16, 12, 210), + background_panelpopup = Color(24, 28, 22, 150), + + button = Color(64, 82, 60), + button_shadow = Color(6, 8, 6, 30), + button_hovered = Color(74, 99, 68), + + category = Color(46, 64, 44), + category_opened = Color(46, 64, 44, 0), + + theme = Color(110, 160, 90), + + panel = { + Color(40, 56, 40), + Color(66, 86, 66), + Color(110, 160, 90) + }, + + toggle = Color(35, 44, 34), + + focus_panel = Color(46, 58, 44), + hover = Color(110, 160, 90, 90), + + window_shadow = Color(0, 0, 0, 100), + + gray = Color(148, 165, 140, 220), + text = Color(232, 244, 235) +} +Mantle.color_moss.panel_alpha = { + ColorAlpha(Mantle.color_moss.panel[1], 150), + ColorAlpha(Mantle.color_moss.panel[2], 150), + ColorAlpha(Mantle.color_moss.panel[3], 150) +} + +-- Коралловая палитра +Mantle.color_coral = { + header = Color(52, 32, 36), + header_text = Color(255, 243, 242), + background = Color(18, 14, 16), + background_alpha = Color(18, 14, 16, 210), + background_panelpopup = Color(30, 22, 24, 150), + + button = Color(116, 66, 61), + button_shadow = Color(8, 4, 6, 30), + button_hovered = Color(134, 73, 68), + + category = Color(74, 40, 42), + category_opened = Color(74, 40, 42, 0), + + theme = Color(255, 120, 90), + + panel = { + Color(66, 38, 40), + Color(120, 60, 56), + Color(240, 120, 90) + }, + + toggle = Color(58, 39, 37), + + focus_panel = Color(72, 42, 44), + hover = Color(255, 120, 90, 90), + + window_shadow = Color(0, 0, 0, 100), + + gray = Color(167, 136, 136, 220), + text = Color(255, 243, 242) +} +Mantle.color_coral.panel_alpha = { + ColorAlpha(Mantle.color_coral.panel[1], 150), + ColorAlpha(Mantle.color_coral.panel[2], 150), + ColorAlpha(Mantle.color_coral.panel[3], 150) +} diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/func.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/func.lua new file mode 100644 index 0000000..f93cd9b --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/func.lua @@ -0,0 +1,326 @@ +Mantle.func = { + sw = ScrW(), + sh = ScrH(), + ents_scales = {}, +} + +local function CreateFonts() + local function CreateFont(name, font_name, size) + surface.CreateFont(name, { + font = font_name, + size = size, + extended = true + }) + end + + local old_surface_SetFont = surface.SetFont + local createdFonts = { + ['Fated.16'] = true + } + CreateFont('Fated.16', 'Montserrat Medium', 16) + + function surface.SetFont(font) + if type(font) != 'string' then + if font == nil then + ErrorNoHalt('surface.SetFont called with nil! Using fallback font') + old_surface_SetFont('DermaDefault') + return + end + old_surface_SetFont(font) + return + end + + if !createdFonts[font] and font:match('^Fated%.') then + local size, isBold = font:match('^Fated%.(%d+)(b?)$') + if size then + size = tonumber(size) + local fontFamily = isBold == 'b' and 'Montserrat Bold' or 'Montserrat Medium' + CreateFont(font, fontFamily, size) + createdFonts[font] = true + end + end + + old_surface_SetFont(font) + end +end + +local math_sin = math.sin +local math_clamp = math.Clamp +local math_abs = math.abs + +local function CreateFunc() + local mat_blur = Material('pp/blurscreen') + + --[[ + Отрисовка размытия у панели. + Применяется в функциях отрисовки (например Paint) + ]]-- + function Mantle.func.blur(panel) + local x, y = panel:LocalToScreen(0, 0) + + surface.SetDrawColor(color_white) + surface.SetMaterial(mat_blur) + + for i = 1, 6 do + if !mat_blur:GetFloat('$blur') then + mat_blur:SetFloat('$blur', i) + mat_blur:Recompute() + end + + render.UpdateScreenEffectTexture() + + surface.DrawTexturedRect(-x, -y, Mantle.func.sw, Mantle.func.sh) + end + end + + local listGradients = { + Material('vgui/gradient_up'), + Material('vgui/gradient_down'), + Material('vgui/gradient-l'), + Material('vgui/gradient-r') + } + + --[[ + Отрисовка градиента + ]]-- + function Mantle.func.gradient(_x, _y, _w, _h, direction, color_shadow, radius, flags) + radius = radius and radius or 0 + RNDX.DrawMaterial(radius, _x, _y, _w, _h, color_shadow, listGradients[direction], flags) + end + + function Mantle.func.sound(path) + surface.PlaySound(path or 'mantle/btn_click.ogg') + end + + Mantle.func.w_save = {} + Mantle.func.h_save = {} + + --[[ + Получение относительной ширины (на основе 1920) + При указании Mantle.func.w(20), 20 будет менять в меньшую сторону или большую в зависимости от ширины экрана + ]]-- + function Mantle.func.w(px) + if !Mantle.func.w_save[px] then + Mantle.func.w_save[px] = px / 1920 * Mantle.func.sw + end + + return Mantle.func.w_save[px] + end + + --[[ + Получение относительной высоты (на основе 1080) + ]]-- + function Mantle.func.h(px) + if !Mantle.func.h_save[px] then + Mantle.func.h_save[px] = px / 1080 * Mantle.func.sh + end + + return Mantle.func.h_save[px] + end + + local function EntText(text, y) + surface.SetFont('Fated.40') + local tw, th = surface.GetTextSize(text) + local bx, by = -tw * 0.5 - 18, y - 12 + local bw, bh = tw + 36, th + 24 + + RNDX().Rect(bx, by, bw, bh - 6) + :Radii(16, 16, 0, 0) + :Blur() + :Shape(RNDX.SHAPE_IOS) + :Draw() + RNDX().Rect(bx, by, bw, bh - 6) + :Radii(16, 16, 0, 0) + :Color(Mantle.color.background_alpha) + :Shape(RNDX.SHAPE_IOS) + :Draw() + RNDX().Rect(bx, by + bh - 6, bw, 6) + :Radii(0, 0, 16, 16) + :Color(Mantle.color.text) + :Draw() + + draw.SimpleText(text, 'Fated.40', 0, y - 2, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + + --[[ + Отрисовка текста на Entity. + Применяется в функциях отрисовки (например ENT:Draw) + ]]-- + function Mantle.func.draw_ent_text(ent, text, posY) + local distSqr = EyePos():DistToSqr(ent:GetPos()) + local maxDist = 380 + if distSqr > maxDist * maxDist then return end + + local dist = math.sqrt(distSqr) + local minDist = 20 + + local idx = ent:EntIndex() + local prev = Mantle.func.ents_scales[idx] or 0 + + local normalized = math.Clamp((maxDist - dist) / math.max(1, (maxDist - minDist)), 0, 1) + + local appearThreshold = 0.8 + local disappearThreshold = 0.01 + + local target + if normalized <= disappearThreshold then + target = 0 + elseif normalized >= appearThreshold then + target = 1 + else + target = (normalized - disappearThreshold) / (appearThreshold - disappearThreshold) + end + + local dt = FrameTime() or 0.016 + local appearSpeed = 18 + local disappearSpeed = 12 + local speed = (target > prev) and appearSpeed or disappearSpeed + + local cur = Mantle.func.approachExp(prev, target, speed, dt) + if math.abs(cur - target) < 0.0005 then cur = target end + Mantle.func.ents_scales[idx] = cur + + local eased = Mantle.func.easeInOutCubic(cur) + local alpha = eased + local baseScale = 0.13 + local camScale = baseScale * math.max(1e-4, eased) + + if eased < 0.01 then + surface.SetAlphaMultiplier(1) + return + end + + local _, max = ent:GetRotatedAABB(ent:OBBMins(), ent:OBBMaxs()) + local rot = (ent:GetPos() - EyePos()):Angle().yaw - 90 + local bob = math.sin(CurTime() + idx) / 3 + 0.5 + local center = ent:LocalToWorld(ent:OBBCenter()) + + surface.SetAlphaMultiplier(alpha) + cam.Start3D2D(center + Vector(0, 0, math.abs(max.z / 2) + 12 + bob), Angle(0, rot, 90), camScale) + EntText(text, posY) + cam.End3D2D() + surface.SetAlphaMultiplier(1) + end + + local scaleFactor = 0.8 + + function Mantle.func.animate_appearance(panel, target_w, target_h, duration, alpha_dur, callback, scale_factor) + if not IsValid(panel) then return end + duration = (duration and duration > 0) and duration or 0.18 + alpha_dur = (alpha_dur and alpha_dur > 0) and alpha_dur or duration + + local startTime = SysTime() + local targetX, targetY = panel:GetPos() + + local initialW = target_w * (scale_factor and scale_factor or scaleFactor) + local initialH = target_h * (scale_factor and scale_factor or scaleFactor) + local initialX = targetX + (target_w - initialW) / 2 + local initialY = targetY + (target_h - initialH) / 2 + + panel:SetSize(initialW, initialH) + panel:SetPos(initialX, initialY) + panel:SetAlpha(0) + + local curW, curH = initialW, initialH + local curX, curY = initialX, initialY + local curA = 0 + + local eps = 0.5 + local alpha_eps = 1 + + local speedSize = 3 / math.max(0.0001, duration) + local speedAlpha = 3 / math.max(0.0001, alpha_dur) + + panel.Think = function() + if not IsValid(panel) then return end + + local dt = FrameTime() + + curW = Mantle.func.approachExp(curW, target_w, speedSize, dt) + curH = Mantle.func.approachExp(curH, target_h, speedSize, dt) + curX = Mantle.func.approachExp(curX, targetX, speedSize, dt) + curY = Mantle.func.approachExp(curY, targetY, speedSize, dt) + curA = Mantle.func.approachExp(curA, 255, speedAlpha, dt) + + panel:SetSize(curW, curH) + panel:SetPos(curX, curY) + panel:SetAlpha(math.floor(curA + 0.5)) + + local doneSize = math.abs(curW - target_w) <= eps and math.abs(curH - target_h) <= eps + local donePos = math.abs(curX - targetX) <= eps and math.abs(curY - targetY) <= eps + local doneAlpha = math.abs(curA - 255) <= alpha_eps + + if doneSize and donePos and doneAlpha then + panel:SetSize(target_w, target_h) + panel:SetPos(targetX, targetY) + panel:SetAlpha(255) + panel.Think = nil + if callback then callback(panel) end + end + end + end + + --[[ + Плавное изменение цвета с одного на другой + ]]-- + function Mantle.func.LerpColor(frac, col1, col2) + local ft = FrameTime() * frac + + return Color( + Lerp(ft, col1.r, col2.r), + Lerp(ft, col1.g, col2.g), + Lerp(ft, col1.b, col2.b), + Lerp(ft, col1.a, col2.a) + ) + end + + --[[ + Функции анимации + ]]-- + function Mantle.func.approachExp(current, target, speed, dt) + local t = 1 - math.exp(-speed * dt) + return current + (target - current) * t + end + + function Mantle.func.easeOutCubic(t) + return 1 - (1 - t) * (1 - t) * (1 - t) + end + + function Mantle.func.easeInOutCubic(t) + if t < 0.5 then + return 4 * t * t * t + else + return 1 - math.pow(-2 * t + 2, 3) / 2 + end + end + + --[[ + Умное позиционирование панели относительно экрана + ]]-- + function Mantle.func.ClampMenuPosition(panel) + if not IsValid(panel) then return end + local x, y = panel:GetPos() + local w, h = panel:GetSize() + local sw, sh = Mantle.func.sw, Mantle.func.sh + if x < 5 then x = 5 elseif x + w > sw - 5 then x = sw - 5 - w end + if y < 5 then y = 5 elseif y + h > sh - 5 then y = sh - 5 - h end + panel:SetPos(x, y) + end +end + +CreateFunc() +CreateFonts() + +hook.Add('OnScreenSizeChanged', 'Mantle', function() + local newW, newH = ScrW(), ScrH() + + if newW != Mantle.func.sw and newH != Mantle.func.sh then + Mantle.func.sw, Mantle.func.sh = newW, newH + + Mantle.func.w_save = {} + Mantle.func.h_save = {} + + CreateFunc() + -- CreateFonts() + end +end) diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/lang.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/lang.lua new file mode 100644 index 0000000..44521e9 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/lang.lua @@ -0,0 +1,25 @@ +function Mantle.lang.get(addon, key) + if !Mantle.lang.list[addon] then + print('Mantle.lang.get: addon "' .. addon .. '" not found!') + end + + local lang = GetConVar('gmod_language'):GetString() + local langTable = Mantle.lang.list[addon][lang] + + if !Mantle.lang.list[addon][lang] then + langTable = Mantle.lang.list[addon][Mantle.lang.default] + end + + if !langTable then + for _, v in pairs(Mantle.lang.list[addon]) do + langTable = v + break + end + if !langTable then + print('Mantle.lang.get: addon "' .. addon .. '" has no language tables!') + return key + end + end + + return langTable[key] or key +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/legacy_vgui.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/legacy_vgui.lua new file mode 100644 index 0000000..1e40767 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/legacy_vgui.lua @@ -0,0 +1,314 @@ +--[[ + Старые функции отрисовки ui-элементов +]]-- + +local color_gray = Color(200, 200, 200) +local color_red = Color(255, 50, 50) +local mat_close = Material('mantle/close_btn.png') + +function Mantle.ui.frame(s, title, width, height, close_bool, anim_bool) + s:SetSize(width, height) + s:SetTitle('') + s:ShowCloseButton(false) + s:DockPadding(6, 30, 6, 6) + s.f_title = title + s.center_title = '' + s.background_alpha = true + s.Paint = function(self, w, h) + local x, y = self:LocalToScreen() + + BShadows.BeginShadow() + draw.RoundedBoxEx(6, x, y, w, 24, Mantle.color.header, true, true) + draw.RoundedBoxEx(6, x, y + 24, w, h - 24, s.background_alpha and Mantle.color.background_alpha or Mantle.color.background, false, false, true, true) + draw.SimpleText(self.f_title, 'Fated.16', x + 6, y + 4, Mantle.color.text) + + if self.center_title then + draw.SimpleText(s.center_title, 'Fated.20b', x + w * 0.5, y + 11, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + BShadows.EndShadow(1, 2, 2, 255, 0, 0) + end + + if anim_bool then + Mantle.func.animate_appearance(s, width, height, 0.1, 0.2) + end + + if close_bool then + s.cls = vgui.Create('DButton', s) + s.cls:SetSize(20, 20) + s.cls:SetPos(width - 22, 2) + s.cls:SetText('') + s.cls.Paint = function(_, w, h) + surface.SetDrawColor(color_white) + surface.SetMaterial(mat_close) + surface.DrawTexturedRect(0, 0, w, h) + end + s.cls.DoClick = function() + s:AlphaTo(0, 0.1, 0, function() + s:Remove() + end) + end + s.cls.DoRightClick = function() + local DM = Mantle.ui.derma_menu() + DM:AddOption('Закрыть окно', function() + s:Remove() + end, 'icon16/cross.png') + end + end +end + +function Mantle.ui.sp(s) + local vbar = s:GetVBar() + vbar:SetWide(12) + vbar:SetHideButtons(true) + vbar.Paint = nil + vbar.btnGrip.Paint = function(self, w, h) + if self.Depressed then + self:SetCursor('sizens') + end + + draw.RoundedBox(6, 6, 0, w - 6, h, Mantle.color.theme) + end +end + +function Mantle.ui.btn(s, icon, icon_size, col, rad, off_grad_bool, hov_color, off_hov_bool) + s:SetTall(32) + s.hoverStatus = 0 + s.btn_font = 'Fated.18' + s.Paint = function(self, w, h) + if !self.btn_text then + self.btn_text = self:GetText() + self:SetText('') + end + + if self:IsHovered() then + self.hoverStatus = math.Clamp(self.hoverStatus + 4 * FrameTime(), 0, 255) + else + self.hoverStatus = math.Clamp(self.hoverStatus - 8 * FrameTime(), 0, 255) + end + + draw.RoundedBox(rad and rad or 6, 0, 0, w, h, col and col or Mantle.color.button) + + if !off_hov_bool then + local color_hover = hov_color and hov_color or Mantle.color.button_hovered + color_hover = Color(color_hover.r, color_hover.g, color_hover.b, 255 * self.hoverStatus) + + draw.RoundedBox(rad and rad or 6, 0, 0, w, h, color_hover) + end + + if !off_grad_bool then + Mantle.func.gradient(0, 0, w, h, 1, Mantle.color.button_shadow) + end + + draw.SimpleText(self.btn_text, self.btn_font, w * 0.5 + (icon and icon_size * 0.5 - 2 or 0), h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if icon then + surface.SetDrawColor(color_white) + surface.SetMaterial(icon) + + local indent = (h - icon_size) * 0.5 + surface.DrawTexturedRect(indent, indent, icon_size, icon_size) + end + end +end + +function Mantle.ui.slidebox(parent, label, min_value, max_value, convar, decimals) + local slider = vgui.Create('DButton', parent) + slider:Dock(TOP) + slider:DockMargin(0, 6, 0, 0) + slider:SetTall(40) + slider:SetText('') + + local value = GetConVar(convar):GetFloat() + local sections = max_value - min_value + local smoothPos = 0 + local targetPos = 0 + + local function UpdateSliderPosition(new_value) + local progress = (new_value - min_value) / sections + targetPos = (slider:GetWide() - 16) * progress + LocalPlayer():ConCommand(convar .. ' ' .. new_value) + value = new_value + end + + UpdateSliderPosition(value) + + slider.Paint = function(self, w, h) + draw.RoundedBox(4, 0, h - 16, w, 6, Mantle.color.panel_alpha[1]) + + smoothPos = Lerp(FrameTime() * 10, smoothPos, targetPos) + + draw.RoundedBox(16, smoothPos, 18, 16, 16, Mantle.color.theme) + + draw.SimpleText(label, 'Fated.18', 4, 0, Mantle.color.text) + draw.SimpleText(math.Round(value, decimals), 'Fated.18', w - 4, 0, Mantle.color.text, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + end + + local function UpdateSliderByCursorPos(x) + local progress = math.Clamp(x / (slider:GetWide() - 16), 0, 1) + local new_value = math.Round(min_value + (progress * sections), decimals) + UpdateSliderPosition(new_value) + end + + slider.OnMousePressed = function(_, mcode) + if mcode == MOUSE_LEFT then + UpdateSliderByCursorPos(slider:CursorPos()) + slider:MouseCapture(true) + end + end + + slider.OnMouseReleased = function(_, mcode) + if mcode == MOUSE_LEFT then + slider:MouseCapture(false) + end + end + + slider.OnCursorMoved = function(_, x, _) + if input.IsMouseDown(MOUSE_LEFT) then + UpdateSliderByCursorPos(x) + end + end + + return slider +end + +function Mantle.ui.desc_entry(parent, title, placeholder, off_title_bool) + if !off_title_bool and title then + local label = vgui.Create('DLabel', parent) + label:Dock(TOP) + label:DockMargin(4, 0, 4, 0) + label:SetText(title) + label:SetFont('Fated.16') + end + + local entry_background = vgui.Create('DPanel', parent) + entry_background:Dock(TOP) + entry_background:DockMargin(4, 4, 4, 0) + entry_background:SetTall(24) + + local entry = vgui.Create('DTextEntry', entry_background) + entry:Dock(FILL) + entry:DockMargin(2, 4, 2, 4) + entry:SetPlaceholderText(placeholder) + entry:SetFont('Fated.16') + entry:SetDrawLanguageID(false) + entry:SetPaintBackground(false) + + return entry, entry_background +end + +function Mantle.ui.checkbox(parent, text, convar) + local panel = vgui.Create('DPanel', parent) + panel:Dock(TOP) + panel:DockMargin(4, 0, 4, 0) + panel:SetTall(28) + panel.Paint = function(_, w, h) + draw.RoundedBox(6, 0, 0, w, h, Mantle.color.panel_alpha[2]) + draw.SimpleText(text, 'Fated.18', 8, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + local option = vgui.Create('DButton', panel) + option:Dock(RIGHT) + option:SetWide(56) + option:SetText('') + option.enabled = convar and GetConVar(convar):GetBool() or false + option.Paint = function(self, w, h) + draw.RoundedBoxEx(6, 0, 0, w, h, Mantle.color.panel_alpha[1], false, true, false, true) + draw.SimpleText(self.enabled and 'ВКЛ' or 'ВЫКЛ', 'Fated.19', w * 0.5 - 1, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + option.DoClick = function() + if convar then + RunConsoleCommand(convar, option.enabled and 0 or 1) + end + + option.enabled = !option.enabled + end + + return panel, option +end + +function Mantle.ui.panel_tabs(parent) + local panel_tabs = vgui.Create('DPanel', parent) + panel_tabs:Dock(FILL) + panel_tabs.Paint = nil + panel_tabs.content = {} + panel_tabs.active_tab = '' + + panel_tabs.sp = vgui.Create('DHorizontalScroller', panel_tabs) + panel_tabs.sp:Dock(TOP) + panel_tabs.sp:DockMargin(0, 0, 0, 6) + panel_tabs.sp:SetTall(24) + panel_tabs.sp:SetOverlap(-6) + + panel_tabs.panel_content = vgui.Create('DPanel', panel_tabs) + panel_tabs.panel_content:Dock(FILL) + panel_tabs.panel_content.Paint = function(_, w, h) + if panel_tabs.active_tab == '' then + draw.SimpleText('Выберете вкладку', 'Fated.16', w * 0.5, h * 0.5 - panel_tabs.sp:GetTall() - 7, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + function panel_tabs:AddTab(title, panel, icon, col, col_hov) + panel_tabs.content[title] = panel + panel_tabs.content[title]:SetParent(panel_tabs.panel_content) + panel_tabs.content[title]:Dock(FILL) + panel_tabs.content[title]:SetVisible(false) + + local btn_tab = vgui.Create('DButton', panel_tabs.sp) + surface.SetFont('Fated.20') + btn_tab:SetSize(surface.GetTextSize(title) + 10 + (icon and 18 or 0), 20) + btn_tab:SetText('') + + if icon then + btn_tab.icon = Material(icon) + panel_tabs.content[title].icon = icon + end + + btn_tab.Paint = function(self, w, h) + draw.RoundedBox(6, 0, 0, w, h, panel_tabs.active_tab == title and (col_hov and col_hov or Mantle.color.panel[2]) or (col and col or Mantle.color.theme)) + + if self:IsHovered() then + draw.RoundedBox(6, 0, 0, w, h, Mantle.color.button_shadow) + end + + draw.SimpleText(title, 'Fated.20', w * 0.5 + (self.icon and 9 or 0), 11, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if self.icon then + surface.SetDrawColor(color_white) + surface.SetMaterial(self.icon) + surface.DrawTexturedRect(4, 4, 16, 16) + end + end + btn_tab.DoClick = function() + panel_tabs:ActiveTab(title) + end + btn_tab.DoRightClick = function() + local DM = Mantle.ui.derma_menu() + + for tab_name, tab in pairs(panel_tabs.content) do + DM:AddOption(tab_name, function() + panel_tabs:ActiveTab(tab_name) + end, tab.icon) + end + end + + panel_tabs.sp:AddPanel(btn_tab) + end + + function panel_tabs:ActiveTab(title) + if title == panel_tabs.active_tab then + return + end + + for tab_title, tab in pairs(panel_tabs.content) do + if tab_title != title then + tab:SetVisible(false) + else + tab:SetVisible(true) + + panel_tabs.active_tab = title + end + end + end + + return panel_tabs +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/menu.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/menu.lua new file mode 100644 index 0000000..971f66c --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/menu.lua @@ -0,0 +1,909 @@ +local function CreateMenu() + if IsValid(menuMantle) then + menuMantle:Remove() + end + + menuMantle = vgui.Create('MantleFrame') + menuMantle:SetSize(920, 640) + menuMantle:Center() + menuMantle:MakePopup() + menuMantle:SetTitle('Mantle') + menuMantle:SetCenterTitle('Основное меню библиотеки') + menuMantle:ShowAnimation() + + local tabs = vgui.Create('MantleTabs', menuMantle) + tabs:Dock(FILL) + + local function CreateTabHeader(title, subtitle, icon, pan) + local header = vgui.Create('Panel', pan) + header:Dock(TOP) + header:DockMargin(0, 0, 0, 8) + header:SetTall(56) + + header.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(8) + :Color(Mantle.color.panel_alpha[2]) + :Draw() + + RNDX().Rect(12, h * 0.5 - 12, 24, 24) + :Color(255, 255, 255) + :Material(icon) + :Draw() + + draw.SimpleText(title, 'Fated.20', 48, 10, Mantle.color.text) + draw.SimpleText(subtitle, 'Fated.16', 48, h - 10, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + end + + return header + end + + local function CreateCopyButton(parent, snippet) + local b = vgui.Create('DButton', parent) + b:SetText('') + b:SetWide(110) + b.Paint = function(me, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(6) + :Color(Mantle.color.panel_alpha[1]) + :Draw() + + draw.SimpleText('Скопировать', 'Fated.16', w / 2, h / 2, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + b.DoClick = function() + SetClipboardText(snippet) + menuMantle:Notify(snippet) + Mantle.func.sound() + end + return b + end + + local function CreateInfo(info, pan) + local panelInfo = vgui.Create('Panel') + panelInfo:Dock(TOP) + panelInfo:DockMargin(0, 0, 0, 6) + panelInfo:SetTall(50) + + panelInfo.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(6) + :Color(Mantle.color.panel_alpha[2]) + :Draw() + + Mantle.func.gradient(0, 0, 6, h, 3, Mantle.color.theme, 6) + + draw.SimpleText(info[1], 'Fated.20', 16, 7, Mantle.color.text) + draw.SimpleText(info[2], 'Fated.16', 16, h - 7, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + end + + local copyBtn = CreateCopyButton(panelInfo, info[1]) + copyBtn:Dock(RIGHT) + copyBtn:DockMargin(0, 10, 10, 10) + + pan:AddItem(panelInfo) + end + + local function CreateCategory(name, info_table, pan, ui_element, is_active) + local panel = vgui.Create('MantleCategory', pan) + panel:Dock(TOP) + panel:DockMargin(0, 0, 0, 6) + panel:SetText(name) + + if is_active then + panel:SetActive(true) + end + + for _, info in ipairs(info_table) do + CreateInfo(info, panel) + end + + if ui_element then + panel:AddItem(ui_element) + end + end + + local function CreateTabElements() + local panel = vgui.Create('MantleScrollPanel') + CreateTabHeader('UI Элементы', 'Демонстрация всех компонентов Mantle. Клик по элементу открывает пример.', Material('icon16/chart_pie.png'), panel) + + local menuWide = menuMantle:GetWide() + + -- Кнопка + local panelBtns = vgui.Create('Panel') + panelBtns:Dock(TOP) + panelBtns:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + panelBtns:SetTall(132) + + local btn1 = vgui.Create('MantleBtn', panelBtns) + btn1:Dock(TOP) + btn1:SetTall(40) + btn1:SetTxt('Стандартная кнопка') + + local btn2 = vgui.Create('MantleBtn', panelBtns) + btn2:Dock(TOP) + btn2:DockMargin(0, 6, 0, 0) + btn2:SetTall(40) + btn2:SetTxt('Эффект волны') + btn2:SetRipple(true) + + local btn3 = vgui.Create('MantleBtn', panelBtns) + btn3:Dock(TOP) + btn3:DockMargin(0, 6, 0, 0) + btn3:SetTall(40) + btn3:SetTxt('Кастомный цвет') + btn3:SetColor(Color(182, 65, 65)) + btn3:SetColorHover(Color(143, 57, 57)) + btn3:SetIcon(Material('icon16/delete.png'), 16) + + CreateCategory('Кнопка (MantleBtn)', { + {':SetHover(bool is_hover)', 'Включить/выключить цвет наведения (дефолт - true)'}, + {':SetFont(string font)', 'Установить шрифт'}, + {':SetRadius(int rad)', 'Установить размер закругления'}, + {':SetIcon(string icon, int icon_size)', 'Установить иконку'}, + {':SetTxt(string text)', 'Установить текст'}, + {':SetColor(color col)', 'Установить цвет кнопки'}, + {':SetColorHover(color col)', 'Установить цвет наведения'}, + {':SetGradient(bool is_grad)', 'Включить/выключить градиент (дефолт - true)'}, + {':SetRipple(bool is_ripple)', 'Включить/выключить эффект волн (дефолт - false)'} + }, panel, panelBtns) + + -- Чекбокс + local checkbox = vgui.Create('MantleCheckBox') + checkbox:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + checkbox:Dock(TOP) + checkbox:SetTxt('Отображение HUD') + checkbox:SetConvar('cl_drawhud') + CreateCategory('Тумблер (MantleCheckBox)', { + {':SetTxt(string text)', 'Установить текст'}, + {':SetValue(bool value)', 'Установить bool-значение тумблера'}, + {':GetBool()', 'Получить bool-значение тумблера'}, + {':SetConvar(string convar)', 'Установить ConVar'}, + {':SetDescription(string desc)', 'Установить описание для тумблера'}, + {':OnChange(bool new_value)', 'Вызывается при изменении значения тумблера'} + }, panel, checkbox) + + -- Ввод текста + local entry = vgui.Create('MantleEntry') + entry:Dock(TOP) + entry:DockMargin(menuWide * 0.35, 6, menuWide * 0.35, 0) + entry:SetTitle('Никнейм') + entry:SetPlaceholder('darkf') + CreateCategory('Ввод текста (MantleEntry)', { + {':SetTitle(string text)', 'Установить заголовок'}, + {':SetPlaceholder(string text)', 'Установить фоновый текст (появляется при пустом поле)'}, + {':GetValue()', 'Получить string-значение поля'} + }, panel, entry) + + -- Окно + local panelFrames = vgui.Create('Panel') + panelFrames:Dock(TOP) + panelFrames:SetTall(92) + + local btnFrame1 = vgui.Create('MantleBtn', panelFrames) + btnFrame1:Dock(TOP) + btnFrame1:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + btnFrame1:SetTxt('Обычное окно') + btnFrame1:SetTall(40) + btnFrame1.DoClick = function() + local frame = vgui.Create('MantleFrame') + frame:SetSize(400, 300) + frame:Center() + frame:MakePopup() + frame:SetCenterTitle('Центр') + end + + local btnFrame2 = vgui.Create('MantleBtn', panelFrames) + btnFrame2:Dock(TOP) + btnFrame2:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + btnFrame2:SetTxt('Lite-режим') + btnFrame2:SetTall(40) + btnFrame2.DoClick = function() + local frame = vgui.Create('MantleFrame') + frame:SetSize(400, 300) + frame:Center() + frame:MakePopup() + frame:LiteMode() + end + + CreateCategory('Окно (MantleFrame)', { + {':SetAlphaBackground(bool is_alpha)', 'Включить/выключить прозрачность окна (дефолт - false)'}, + {':SetTitle(string title)', 'Установить заголовок'}, + {':SetCenterTitle(string title)', 'Установить центральный заголовок'}, + {':ShowAnimation()', 'Активировать анимацию при появлении меню'}, + {':DisableCloseBtn()', 'Скрыть кнопку закрытия'}, + {':SetDraggable(bool is_draggable)', 'Включить/выключить перемещение окна'}, + {':LiteMode()', 'Активировать режим Lite (без верхней панели)'}, + {':Notify(string text, number duration, color col)', 'Показать уведомление внизу окна (дефолт времени - 2 сек., цвета - Mantle.color.theme)'} + }, panel, panelFrames) + + -- ScrollPanel + local sp = vgui.Create('MantleScrollPanel') + sp:Dock(TOP) + sp:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + sp:SetTall(150) + for spK = 1, 10 do + local spPanel = vgui.Create('DPanel', sp) + spPanel:Dock(TOP) + spPanel:DockMargin(0, 0, 0, 6) + spPanel:SetTall(24) + spPanel.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.panel_alpha[1]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + end + CreateCategory('Панель прокрутки (MantleScrollPanel)', { + {':SetScroll(number offset)', 'Установить смещение прокрутки'}, + {':GetScroll()', 'Получить текущее смещение прокрутки'}, + {':AddItem(object panel)', 'Добавить элемент в панель'}, + {':Clear()', 'Очистить панель от всего'}, + {':DisableVBarPadding()', 'Отключить отступ справа для скроллбара (по умолчанию имеется)'} + }, panel, sp) + + -- Вкладки + local panelTabs = vgui.Create('Panel') + panelTabs:Dock(TOP) + panelTabs:SetTall(280) + + local testTabs = vgui.Create('MantleTabs', panelTabs) -- modern стиль + testTabs:Dock(TOP) + testTabs:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + testTabs:SetTall(150) + local testTab1 = vgui.Create('DPanel') + testTab1.Paint = function(_, w, h) + RNDX().Rect(0, 0, w - 12, h) + :Rad(16) + :Color(53, 98, 40) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + testTabs:AddTab('Test1', testTab1) + local testTab2 = vgui.Create('DPanel') + testTab2.Paint = function(_, w, h) + RNDX().Rect(0, 0, w - 12, h) + :Rad(16) + :Color(108, 41, 45) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + testTabs:AddTab('Test2', testTab2) + + local testTabs2 = vgui.Create('MantleTabs', panelTabs) -- classic стиль + testTabs2:Dock(FILL) + testTabs2:DockMargin(menuWide * 0.3, 10, menuWide * 0.3, 0) + testTabs2:SetTabStyle('classic') + local testTab3 = vgui.Create('DPanel') + testTab3.Paint = function(_, w, h) + RNDX().Rect(0, 0, w - 12, h) + :Rad(16) + :Color(51, 61, 116) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + testTabs2:AddTab('Test3', testTab3) + local testTab4 = vgui.Create('DPanel') + testTab4.Paint = function(_, w, h) + RNDX().Rect(0, 0, w - 12, h) + :Rad(16) + :Color(138, 89, 43) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + testTabs2:AddTab('Test4', testTab4) + local testTab5 = vgui.Create('DPanel') + testTab5.Paint = function(_, w, h) + RNDX().Rect(0, 0, w - 12, h) + :Rad(16) + :Color(43, 138, 133) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + testTabs2:AddTab('С иконкой', testTab5, Material('icon16/folder.png')) + + CreateCategory('Вкладки (MantleTabs)', { + {':SetTabStyle(string style)', 'Установить стиль вкладок (modern или classic)'}, + {':SetTabHeight(int height)', 'Установить высоту вкладок'}, + {':SetIndicatorHeight(int height)', 'Установить высоту индикатора вкладок'}, + {':AddTab(string name, object panel, string icon)', 'Добавить вкладку'} + }, panel, panelTabs) + + -- Выбор варианта + local combo = vgui.Create('MantleComboBox') + combo:SetPlaceholder('Выберите вариант') + combo:AddChoice('Вариант 1', 'value1') + combo:AddChoice('Вариант 2', 'value2') + combo:AddChoice('Вариант 3', 'value3') + combo:AddChoice('Вариант 4', 'value4') + combo:AddChoice('Вариант 5', 'value5') + combo:AddChoice('Вариант 6', 'value6') + combo:AddChoice('Вариант 7', 'value7') + combo:AddChoice('Вариант 8', 'value8') + combo.OnSelect = function(idx, text, data) + chat.AddText(color_white, 'Вы выбрали: ', Mantle.color.theme, text, color_white, ' (', tostring(data), ')') + end + combo:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + combo:Dock(TOP) + CreateCategory('Выпадающий список (MantleComboBox)', { + {':AddChoice(string text, any data)', 'Добавить вариант в список (data — любое значение, связанное с пунктом)'}, + {':SetValue(string text)', 'Установить выбранное значение по тексту'}, + {':GetValue()', 'Получить выбранное значение (текст)'}, + {':SetPlaceholder(string text)', 'Установить текст-заполнитель (placeholder)'}, + {':OnSelect(idx, text, data)', 'Вызывается при выборе варианта: idx — индекс, text — текст, data — значение'} + }, panel, combo) + + -- Таблица + local tableExample = vgui.Create('MantleTable') + tableExample:Dock(TOP) + tableExample:DockMargin(menuWide * 0.2, 6, menuWide * 0.2, 0) + tableExample:SetTall(250) + + tableExample:AddColumn('Название', 200, TEXT_ALIGN_LEFT, true) + tableExample:AddColumn('Тип', 120, TEXT_ALIGN_CENTER, true) + tableExample:AddColumn('Качество', 100, TEXT_ALIGN_CENTER, true) + tableExample:AddColumn('Цена', 110, TEXT_ALIGN_RIGHT, true) + + local products = { + {'Молоко "Домик в деревне"', 'Молочка', 'Высшее', 89}, + {'Хлеб "Бородинский"', 'Выпечка', 'Стандарт', 45}, + {'Сок "Добрый"', 'Напитки', 'Премиум', 120}, + {'Шоколад "Аленка"', 'Конфеты', 'Высшее', 95}, + {'Йогурт "Активиа"', 'Молочка', 'Премиум', 65}, + {'Пельмени "Сибирские"', 'Заморозка', 'Стандарт', 350}, + {'Колбаса "Докторская"', 'Мясо', 'Высшее', 450}, + {'Сыр "Российский"', 'Молочка', 'Стандарт', 380}, + {'Пицца "Пепперони"', 'Заморозка', 'Премиум', 450}, + {'Чай "Липтон"', 'Напитки', 'Стандарт', 180}, + {'Печенье "Юбилейное"', 'Выпечка', 'Стандарт', 85}, + {'Масло "Крестьянское"', 'Молочка', 'Высшее', 120}, + {'Сметана "Простоквашино"', 'Молочка', 'Стандарт', 65}, + {'Курица "Бройлер"', 'Мясо', 'Стандарт', 280}, + {'Рыба "Минтай"', 'Морепродукты', 'Стандарт', 320}, + {'Яблоки "Голден"', 'Фрукты', 'Высшее', 180}, + {'Картофель', 'Овощи', 'Стандарт', 45}, + {'Морковь', 'Овощи', 'Стандарт', 35}, + {'Бананы', 'Фрукты', 'Стандарт', 120}, + {'Апельсины', 'Фрукты', 'Премиум', 180} + } + + for _, product in ipairs(products) do + tableExample:AddItem(unpack(product)) + end + + tableExample:SetAction(function(row_data) + chat.AddText(color_white, 'Выбран продукт: ', Mantle.color.theme, row_data[1], color_white, ' (', row_data[2], ')') + end) + + CreateCategory('Таблица (MantleTable)', { + {':AddColumn(string name, number width, number align, bool sortable)', 'Добавить колонку'}, + {':AddItem(...)', 'Добавить строку. Количество аргументов должно соответствовать количеству колонок'}, + {':SetAction(function(table row_data))', 'Установить функцию, вызываемую при клике на строку. row_data — массив значений строки'}, + {':SetRightClickAction(function(table row_data))', 'Установить функцию, вызываемую при правом клике на строку'}, + {':Clear()', 'Очистить таблицу от всех строк'}, + {':GetSelectedRow()', 'Получить данные выбранной строки (массив значений)'}, + {':GetRowCount()', 'Получить количество строк в таблице'}, + {':RemoveRow(number index)', 'Удалить строку по индексу (начиная с 1)'} + }, panel, tableExample) + + -- Категория + local panelCat = vgui.Create('Panel') + panelCat:Dock(TOP) + panelCat:DockMargin(0, 6, 0, 0) + panelCat:SetTall(142) + panelCat.Paint = nil + + local cat = vgui.Create('MantleCategory', panelCat) + cat:Dock(TOP) + cat:SetCenterText(true) + cat:SetActive(true) + local panGreen = vgui.Create('DPanel') + panGreen:Dock(TOP) + panGreen:SetTall(50) + panGreen.Paint = function(_, w, h) + RNDX().Rect(0, 0, w - 12, h) + :Rad(16) + :Color(93, 179, 101) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + cat:AddItem(panGreen) + local panRed = vgui.Create('DPanel') + panRed:Dock(TOP) + panRed:DockMargin(0, 6, 0, 0) + panRed:SetTall(50) + panRed.Paint = function(_, w, h) + RNDX().Rect(0, 0, w - 12, h) + :Rad(16) + :Color(179, 110, 93) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + cat:AddItem(panRed) + CreateCategory('Категория (MantleCategory)', { + {':SetText(string name)', 'Установить название'}, + {':AddItem(object panel)', 'Добавить в категорию элемент'}, + {':SetColor(color col)', 'Установить кастомный цвет категории'}, + {':SetCenterText(bool is_centered)', 'Установить центрирование названия'}, + {':SetActive(bool is_active)', 'Установить активность категории (дефолт - false)'} + }, panel, panelCat) + + -- Слайдер + local slider = vgui.Create('MantleSlideBox') + slider:Dock(TOP) + slider:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + slider:SetRange(0, 4) + slider:SetConvar('net_graph') + slider:SetText('График') + CreateCategory('Слайдер (MantleSlideBox)', { + {':SetRange(int min_value, int max_value, int decimals)', 'Сделать диапазон слайдера с точностью (дефолт точность - 0)'}, + {':SetConvar(string convar)', 'Установить ConVar'}, + {':SetText(string text)', 'Установить текстовое обозначение'}, + {':SetValue(string val)', 'Установить значение'}, + {':GetValue()', 'Получить выбранное значение (число)'}, + {':OnValueChanged(string new_value)', 'Вызывается при изменении значения слайдера'} + }, panel, slider) + + local panelTexts = vgui.Create('Panel') + panelTexts:Dock(TOP) + panelTexts:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + panelTexts:DockPadding(8, 8, 8, 8) + panelTexts:SetTall(344) + panelTexts.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.panel_alpha[2]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + local panelText1 = vgui.Create('DPanel', panelTexts) + panelText1:Dock(TOP) + panelText1:DockMargin(0, 0, 0, 6) + panelText1:SetTall(74) + panelText1.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.panel_alpha[1]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + local text1 = vgui.Create('MantleText', panelText1) + text1:Dock(FILL) + text1:SetPadding(10) + text1:SetText('MantleText — компонент для аккуратного вывода многострочного текста. Текст автоматически переносится по ширине и сокращается троеточием') + + local panelText2 = vgui.Create('DPanel', panelTexts) + panelText2:Dock(TOP) + panelText2:DockMargin(0, 0, 0, 6) + panelText2:SetTall(100) + panelText2.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.panel_alpha[1]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + local text2 = vgui.Create('MantleText', panelText2) + text2:Dock(FILL) + text2:SetPadding(12) + text2:SetFont('Fated.20') + text2:SetText('Центрирование: горизонталь + вертикаль. Текст выровнен по центру блока.') + text2:SetAlign(TEXT_ALIGN_CENTER) + text2:SetVAlign('center') + + local panelText3 = vgui.Create('DPanel', panelTexts) + panelText3:Dock(TOP) + panelText3:DockMargin(0, 0, 0, 6) + panelText3:SetTall(54) + panelText3.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.panel_alpha[1]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + local text3 = vgui.Create('MantleText', panelText3) + text3:Dock(FILL) + text3:SetPadding(8) + text3:SetText('ОченьДлинноеСловоБезПробеловКотороеНужноОтделитьЧтобыНеПорвалосьОформление') + + local panelText4 = vgui.Create('DPanel', panelTexts) + panelText4:Dock(TOP) + panelText4:SetTall(82) + panelText4.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.panel_alpha[1]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + local longText = [[ + Это длинный пример текста, который занимает несколько строк. Если блок небольшой по высоте — последняя видимая строка будет усечена с троеточием, чтобы не порвать верстку и не выходить за пределы панели нашего меню. + ]] + + local text4 = vgui.Create('MantleText', panelText4) + text4:Dock(FILL) + text4:SetPadding(8) + text4:SetFont('Fated.16') + text4:SetText(longText) + + CreateCategory('Текст (MantleText)', { + {':SetText(string text)', 'Установить текст для отображения'}, + {':SetFont(string font)', 'Установить шрифт'}, + {':SetColor(color col)', 'Установить цвет текста'}, + {':SetAlign(number align)', 'Горизонтальное выравнивание (TEXT_ALIGN_*)'}, + {':SetVAlign(string valign)', 'Вертикальное выравнивание: top, center, bottom'}, + {':SetPadding(number px)', 'Внутренний отступ от краёв'} + }, panel, panelTexts) + + return panel + end + + tabs:AddTab('UI Элементы', CreateTabElements(), Material('icon16/chart_pie.png')) + + local function CreateShowMenus() + local panel = vgui.Create('MantleScrollPanel') + CreateTabHeader('Всплывающие', 'Палитра, derma-меню, radial и другие утилиты.', Material('icon16/application_double.png'), panel) + + local listMenus = { + {'Выбор цвета через палитру', function() + Mantle.ui.color_picker(function(col) + chat.AddText('Вы выбрали цвет: ', col, tostring(col)) + end, Color(25, 59, 102)) + end}, + {'Опциональное меню (Derma Menu)', function() + local DM = Mantle.ui.derma_menu() + for i = 1, 5 do + DM:AddOption('Опция ' .. i, function() + chat.AddText('Привет всем! ' .. i) + end) + end + DM:AddSpacer() + DM:AddOption('Узнать свою привилегию', function() + chat.AddText(LocalPlayer():GetUserGroup()) + end, 'icon16/status_online.png') + end}, + {'Опциональное с подменю (Derma Menu)', function() + local DM = Mantle.ui.derma_menu() + + local clothes = DM:AddOption('Одежда') + local subClothes = clothes:AddSubMenu() + subClothes:AddOption('Шапка', function() + chat.AddText('Вы выбрали: Шапка') + end) + subClothes:AddOption('Свитер', function() + chat.AddText('Вы выбрали: Свитер') + end) + + local food = DM:AddOption('Еда') + local subFood = food:AddSubMenu() + subFood:AddOption('Морковь', function() + chat.AddText('Вы выбрали: Морковь') + end) + subFood:AddOption('Яблоко', function() + chat.AddText('Вы выбрали: Яблоко') + end) + end}, + {'Выбор игрока', function() + Mantle.ui.player_selector(function(pl) + chat.AddText('Вы выбрали игрока: ', color_white, pl:Name()) + end) + end}, + {'Круговое меню', function() + --[[ + Имеется возможность настроить радиальное меню + + local configRadial = { + disable_background = true, -- отключает фон + hover_sound = 'buttons/button14.wav', -- звук при наведении + scale_animation = false, -- отключает анимацию масштабирования + radius = 300, -- радиус меню + inner_radius = 100 -- радиус внутреннего круга + } + + local rm = Mantle.ui.radial_menu(configRadial) + --]] + + local rm = Mantle.ui.radial_menu() + rm:SetCenterText('Действия', 'Выберите действие') + + local weaponsMenu = rm:CreateSubMenu('Оружие', 'Выберите оружие') + weaponsMenu:AddOption('Пистолет', function() + chat.AddText(Mantle.color.theme, 'Выбран пистолет') + end, 'icon16/gun.png', 'Обычный пистолет') + weaponsMenu:AddOption('Винтовка', function() + chat.AddText(Mantle.color.theme, 'Выбрана винтовка') + end, 'icon16/gun.png', 'Мощная винтовка') + rm:AddSubMenuOption('Оружие', weaponsMenu, 'icon16/gun.png', 'Выберите оружие') + + -- Обычные опции + rm:AddOption('Выбросить', function() + chat.AddText('Выбросить оружие') + end, 'icon16/gun.png', 'Выбросить оружие') + rm:AddOption('Кинуть кубик', function() + chat.AddText('Действие выполнено') + end, 'icon16/controller.png', 'Рандом кубика') + rm:AddOption('Погибнуть', function() + chat.AddText('Действие выполнено') + end, 'icon16/world.png', 'Попрощаться с миром') + rm:AddOption('Хакнуть', function() + chat.AddText('Действие выполнено') + end, 'icon16/server.png', 'Взломать сервер') + rm:AddOption('Посмотреть баланс', function() + chat.AddText('Действие выполнено') + end, 'icon16/money.png', 'Сколько у вас денег') + rm:AddOption('Нет иконки', function() + chat.AddText('Действие выполнено') + end, nil, 'Где иконка?') + end}, + {'Написание текста', function() + Mantle.ui.text_box('Заголовок', 'Описание того, что вводиться', function(s) + chat.AddText('Вы ввели: ', color_white, s) + end) + end}, + {'Вызов сообщения в Окне', function() + menuMantle:Notify('Тестовое сообщение!') + end} + } + + for _, elem in ipairs(listMenus) do + local btn = vgui.Create('MantleBtn', panel) + btn:Dock(TOP) + btn:DockMargin(0, 0, 0, 6) + btn:SetTall(30) + btn:SetTxt(elem[1]) + btn.DoClick = function() + elem[2]() + Mantle.func.sound() + end + end + + return panel + end + + tabs:AddTab('Всплывающие', CreateShowMenus(), Material('icon16/application_double.png')) + + local function CreateTabFunctions() + local panel = vgui.Create('MantleScrollPanel') + CreateTabHeader('Функции', 'Полный список утилитарных функций Mantle.func и других вспомогательных функций', Material('icon16/cog.png'), panel) + + local menuWide = menuMantle:GetWide() + + CreateCategory('Размытие панели', { + {'Mantle.func.blur(object panel)', 'Отрисовка размытия панели в Paint'} + }, panel) + + CreateCategory('Градиент', { + {'Mantle.func.gradient(int x, int y, int w, int h, int dir, color color_shadow, int radius, flags)', 'Отрисовка градиента (dir: 1 - вверх, 2 - вниз, 3 - влево, 4 - вправо)'} + }, panel) + + CreateCategory('Создание звука', { + {'Mantle.func.sound(string path)', 'Проигрывает звук (дефолт - mantle/btn_click.ogg)'} + }, panel) + + CreateCategory('Относительные единицы для адаптивного интерфейса', { + {'Mantle.func.w(int px)', 'Относительная ширина (от 1920)'}, + {'Mantle.func.h(int px)', 'Относительная высота (от 1080)'} + }, panel) + + CreateCategory('Отрисовка текста над энтити', { + {'Mantle.func.draw_ent_text(object ent, string text, int posY)', 'Рисует текст над энтити с плавным появлением (3D2D)'} + }, panel) + + CreateCategory('Анимация размера панели', { + {'Mantle.func.animate_appearance(object panel, int w, int h, int duration, int alpha_dur, func callback, int scale_factor)', 'Плавное изменение панели до нужного размера'} + }, panel) + + CreateCategory('Плавное изменение цвета', { + {'Mantle.func.LerpColor(int frac, color col1, color col2)', 'Плавный переход цвета от col1 → col2'} + }, panel) + + CreateCategory('Загрузка картинки', { + {'http.DownloadMaterial(string url, string path, func callback, int retry_count)', 'Скачивает материал по URL и кэширует его. Повторяет попытку при ошибке, возвращает через callback материал'} + }, panel) + + CreateCategory('Серверное уведомление', { + {'Mantle.notify(object pl, color header_color, string header, string text)', 'Отправка сообщений в чат игроку или всем (вместо pl указать true - тогда всем)'} + }, panel) + + CreateCategory('Изменение регистра букв', { + {'utf8.lower(string text)', 'Преобразует строку в нижний регистр с поддержкой русских букв'}, + {'utf8.upper(string text)', 'Преобразует строку в верхний регистр с поддержкой русских букв'} + }, panel) + + return panel + end + + tabs:AddTab('Функции', CreateTabFunctions(), Material('icon16/error.png')) + + local function CreateLegacyTest() + local panel = vgui.Create('MantleScrollPanel') + CreateTabHeader('Legacy UI', 'Набор legacy-утилит (Mantle.ui.*). Для совместимости и примеров.', Material('icon16/exclamation.png'), panel) + + local menuWide = menuMantle:GetWide() + + local btnFrame = vgui.Create('MantleBtn') + btnFrame:SetTxt('Открыть Legacy Frame') + btnFrame:SetTall(40) + btnFrame:DockMargin(menuWide * 0.3, 6, menuWide * 0.3, 0) + btnFrame:Dock(TOP) + btnFrame.DoClick = function() + local frame = vgui.Create('DFrame') + frame:SetSize(400, 300) + frame:Center() + frame:MakePopup() + Mantle.ui.frame(frame, 'Legacy Frame', 400, 300, true, true) + + local scroll = vgui.Create('DScrollPanel', frame) + scroll:Dock(FILL) + Mantle.ui.sp(scroll) + + -- Тест кнопок с разными параметрами + local btn1 = vgui.Create('DButton', scroll) + btn1:Dock(TOP) + btn1:DockMargin(10, 10, 10, 0) + btn1:SetText('Обычная кнопка') + Mantle.ui.btn(btn1) + + local btn2 = vgui.Create('DButton', scroll) + btn2:Dock(TOP) + btn2:DockMargin(10, 10, 10, 0) + btn2:SetText('Кнопка с иконкой') + Mantle.ui.btn(btn2, Material('icon16/accept.png'), 16) + + local btn3 = vgui.Create('DButton', scroll) + btn3:Dock(TOP) + btn3:DockMargin(10, 10, 10, 0) + btn3:SetText('Кнопка без градиента') + Mantle.ui.btn(btn3, nil, nil, nil, nil, true) + + local btn4 = vgui.Create('DButton', scroll) + btn4:Dock(TOP) + btn4:DockMargin(10, 10, 10, 0) + btn4:SetText('Кнопка без ховера') + Mantle.ui.btn(btn4, nil, nil, nil, nil, nil, nil, true) + + -- Тест слайдеров + local slider1 = Mantle.ui.slidebox(scroll, 'Слайдер (0-100)', 0, 100, 'net_graph', 0) + slider1:DockMargin(10, 20, 10, 0) + + local slider2 = Mantle.ui.slidebox(scroll, 'Слайдер (0-1)', 0, 1, 'cl_drawhud', 2) + slider2:DockMargin(10, 20, 10, 0) + + -- Тест полей ввода + local entry1, entry_bg1 = Mantle.ui.desc_entry(scroll, 'Поле с заголовком', 'Введите текст...') + entry_bg1:DockMargin(10, 20, 10, 0) + + local entry2, entry_bg2 = Mantle.ui.desc_entry(scroll, nil, 'Поле без заголовка') + entry_bg2:DockMargin(10, 20, 10, 0) + + -- Тест чекбоксов + local checkbox1, checkbox_btn1 = Mantle.ui.checkbox(scroll, 'Чекбокс с ConVar', 'cl_drawhud') + checkbox1:DockMargin(10, 20, 10, 0) + + local checkbox2, checkbox_btn2 = Mantle.ui.checkbox(scroll, 'Чекбокс без ConVar') + checkbox2:DockMargin(10, 20, 10, 0) + + -- Тест вкладок + local panelTabs = vgui.Create('DPanel', scroll) + panelTabs:Dock(TOP) + panelTabs:SetTall(250) + panelTabs.Paint = nil + + local tabs = Mantle.ui.panel_tabs(panelTabs) + tabs:DockMargin(10, 20, 10, 0) + + -- Добавляем вкладки с разными стилями + local tab1 = vgui.Create('DPanel') + tab1.Paint = function(_, w, h) + draw.SimpleText('Вкладка 1', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + tabs:AddTab('Вкладка 1', tab1, 'icon16/page_white.png') + + local tab2 = vgui.Create('DPanel') + tab2.Paint = function(_, w, h) + draw.SimpleText('Вкладка 2', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + tabs:AddTab('Вкладка 2', tab2, 'icon16/page_white_edit.png', Color(100, 200, 100)) + + local tab3 = vgui.Create('DPanel') + tab3.Paint = function(_, w, h) + draw.SimpleText('Вкладка 3', 'Fated.20', w/2, h/2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + tabs:AddTab('Вкладка 3', tab3, 'icon16/page_white_gear.png', nil, Color(200, 100, 100)) + + tabs:ActiveTab('Вкладка 1') + end + CreateCategory('Legacy Frame (не стоит использовать)', { + {'Mantle.ui.frame(object frame, string title, int w, int h, bool cls_btn, bool open_anim)', 'Оформление стандартное окна стилем Mantle'}, + {'Mantle.ui.sp(object scroll)', 'Оформление панели прокрутки элементов'}, + {'Mantle.ui.btn(object btn, mat icon, int icon_size, color col, int rad, bool off_grad, color hov, bool off_hov)', 'Оформление кнопки'}, + {'Mantle.ui.slidebox(object parent, string label, int min_value, int max_value, string convar, int decimals)', 'Создание слайдера на родительном элементе'}, + {'Mantle.ui.desc_entry(object parent, string title, string placeholder, bool off_title)', 'Создание поля ввода'}, + {'Mantle.ui.checkbox(object parent, string text, string convar)', 'Создание чекбокса'}, + {'Mantle.ui.panel_tabs(object parent)', 'Создание панели с вкладками. В дальнейшем использовать :AddTab() и :ActiveTab() для настройки'} + }, panel, btnFrame, true) + + return panel + end + + tabs:AddTab('Legacy UI', CreateLegacyTest(), Material('icon16/exclamation.png')) + + local function CreateSettings() + local panel = vgui.Create('MantleScrollPanel') + CreateTabHeader('Настройки', 'Глобальные настройки Mantle: темы, эффекты и глубины элементов.', Material('icon16/cog.png'), panel) + + local menuWide = menuMantle:GetWide() + + local checkboxDepth = vgui.Create('MantleCheckBox', panel) + checkboxDepth:Dock(TOP) + checkboxDepth:SetTxt('Глубины элементов') + checkboxDepth:SetConvar('mantle_depth_ui') + + local checkboxBlur = vgui.Create('MantleCheckBox', panel) + checkboxBlur:Dock(TOP) + checkboxBlur:DockMargin(0, 6, 0, 0) + checkboxBlur:SetTxt('Размытие фона') + checkboxBlur:SetConvar('mantle_blur') + + local categoryTheme = vgui.Create('MantleCategory', panel) + categoryTheme:Dock(TOP) + categoryTheme:DockMargin(0, 6, 0, 0) + categoryTheme:SetText('Изменение цветовой темы') + categoryTheme:SetActive(true) + + local comboboxTheme = vgui.Create('MantleComboBox') + comboboxTheme:Dock(TOP) + comboboxTheme:SetPlaceholder('Выберите тему интерфейса') + comboboxTheme:AddChoice('Тёмная (dark)', 'dark') + comboboxTheme:AddChoice('Тёмная монотонная (dark_mono)', 'dark_mono') + comboboxTheme:AddChoice('Светлая (light)', 'light') + comboboxTheme:AddChoice('Синяя (blue)', 'blue') + comboboxTheme:AddChoice('Красная (red)', 'red') + comboboxTheme:AddChoice('Зелёная (green)', 'green') + comboboxTheme:AddChoice('Оранжевая (orange)', 'orange') + comboboxTheme:AddChoice('Фиолетовый (purple)', 'purple') + comboboxTheme:AddChoice('Кофейная (coffee)', 'coffee') + comboboxTheme:AddChoice('Ледяная (ice)', 'ice') + comboboxTheme:AddChoice('Винная (wine)', 'wine') + comboboxTheme:AddChoice('Фиалковая (violet)', 'violet') + comboboxTheme:AddChoice('Моховая (moss)', 'moss') + comboboxTheme:AddChoice('Коралловая (coral)', 'coral') + comboboxTheme.OnSelect = function(_, _, data) + RunConsoleCommand('mantle_theme', data) + end + categoryTheme:AddItem(comboboxTheme) + + local listThemeColors = vgui.Create('DIconLayout') + listThemeColors:Dock(TOP) + listThemeColors:DockMargin(6, 8, 6, 0) + listThemeColors:SetTall(164) + listThemeColors:SetSpaceX(8) + listThemeColors:SetSpaceY(8) + categoryTheme:AddItem(listThemeColors) + + for colId, _ in pairs(Mantle.color) do + local panCol = vgui.Create('DPanel', listThemeColors) + panCol:SetSize(80, 80) + panCol.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color[colId]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + draw.SimpleText(colId, 'Fated.12', w * 0.5, h * 0.5, color_black, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + + return panel + end + + tabs:AddTab('Настройки', CreateSettings(), Material('icon16/cog.png')) +end + +concommand.Add('mantle_menu', CreateMenu) diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui.lua new file mode 100644 index 0000000..0586c19 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui.lua @@ -0,0 +1,106 @@ +CreateClientConVar('mantle_depth_ui', 1, true, false) +CreateClientConVar('mantle_theme', 'dark', true, false) +CreateClientConVar('mantle_blur', 1, true, false) + +Mantle.ui = { + convar = { + depth_ui = GetConVar('mantle_depth_ui'):GetBool(), + theme = GetConVar('mantle_theme'):GetString(), + blur = GetConVar('mantle_blur'):GetBool() + } +} + +local themeMap = { + dark = Mantle.color_dark, + dark_mono = Mantle.color_dark_mono, + graphite = Mantle.color_graphite, + light = Mantle.color_light, + blue = Mantle.color_blue, + red = Mantle.color_red, + green = Mantle.color_green, + orange = Mantle.color_orange, + purple = Mantle.color_purple, + coffee = Mantle.color_coffee, + ice = Mantle.color_ice, + wine = Mantle.color_wine, + violet = Mantle.color_violet, + moss = Mantle.color_moss, + coral = Mantle.color_coral +} + +local function isColor(v) + return type(v) == 'table' and type(v.r) == 'number' +end + +local transition = { + active = false, + to = nil, + progress = 0, + speed = 3, + colorBlend = 8 +} + +local function startThemeTransition(name) + transition.to = table.Copy(themeMap[name] or Mantle.color_dark) + transition.active = true + transition.progress = 0 + + if !hook.GetTable().MantleThemeTransition then + hook.Add('Think', 'MantleThemeTransition', function() + if !transition.active then return end + + local dt = FrameTime() + transition.progress = Mantle.func.approachExp(transition.progress, 1, transition.speed, dt) + local eased = Mantle.func.easeOutCubic(transition.progress) + + local to = transition.to + if !to then + transition.active = false + hook.Remove('Think', 'MantleThemeTransition') + return + end + + for k, v in pairs(to) do + if isColor(v) then + Mantle.color[k] = Mantle.func.LerpColor(transition.colorBlend, Mantle.color[k] or v, v) + elseif type(v) == 'table' and #v > 0 then + Mantle.color[k] = Mantle.color[k] or {} + for i = 1, #v do + local vi = v[i] + if isColor(vi) then + Mantle.color[k][i] = Mantle.func.LerpColor(transition.colorBlend, (Mantle.color[k] and Mantle.color[k][i]) or vi, vi) + else + Mantle.color[k][i] = vi + end + end + end + end + + if transition.progress >= 0.999 then + Mantle.color = table.Copy(transition.to) + transition.active = false + hook.Remove('Think', 'MantleThemeTransition') + end + end) + end +end + +local function ApplyInitialTheme() + local theme = Mantle.ui.convar.theme + Mantle.color = table.Copy(themeMap[theme] or Mantle.color_dark) +end + +ApplyInitialTheme() + +cvars.AddChangeCallback('mantle_depth_ui', function(_, _, newValue) + Mantle.ui.convar.depth_ui = newValue == '1' +end) + +cvars.AddChangeCallback('mantle_theme', function(_, _, newValue) + Mantle.ui.convar.theme = newValue + startThemeTransition(newValue) +end) + +cvars.AddChangeCallback('mantle_blur', function(_, _, newValue) + Mantle.ui.convar.blur = newValue == '1' +end) diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/button.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/button.lua new file mode 100644 index 0000000..99f4129 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/button.lua @@ -0,0 +1,191 @@ +local PANEL = {} + +function PANEL:Init() + self._activeShadowTimer = 0 + self._activeShadowMinTime = 0.03 -- минимальная длительность (сек) + self._activeShadowLerp = 0 + self.hover_status = 0 + self.bool_hover = true + self.font = 'Fated.18' + self.radius = 16 + self.icon = '' + self.icon_size = 16 + self.text = Mantle.lang.get('mantle', 'btn_default') + self.col = Mantle.color.button + self.col_hov = Mantle.color.button_hovered + self.bool_gradient = true + self.click_alpha = 0 + self.click_x = 0 + self.click_y = 0 + self.ripple_speed = 4 + self.enable_ripple = false + self.ripple_color = Color(255, 255, 255, 30) + + --[[ + TODO: тень, которая не вылезает за окно при прокрутке + ]]-- + -- local parent = self:GetParent() + -- local grandParent = IsValid(parent:GetParent()) and parent:GetParent() or parent + -- self.clipParent = IsValid(parent) and grandParent or nil + + self:SetText('') +end + +function PANEL:SetHover(is_hover) + self.bool_hover = is_hover +end + +function PANEL:SetFont(font) + self.font = font +end + +function PANEL:SetRadius(rad) + self.radius = rad +end + +function PANEL:SetIcon(icon, icon_size) + self.icon = type(icon) == 'IMaterial' and icon or Material(icon) + self.icon_size = icon_size +end + +function PANEL:SetTxt(text) + self.text = text +end + +function PANEL:SetColor(col) + self.col = col +end + +function PANEL:SetColorHover(col) + self.col_hov = col +end + +function PANEL:SetGradient(is_grad) + self.bool_gradient = is_grad +end + +function PANEL:SetRipple(enable) + self.enable_ripple = enable +end + +function PANEL:OnMousePressed(mousecode) + self.BaseClass.OnMousePressed(self, mousecode) + + if self.enable_ripple and mousecode == MOUSE_LEFT then + self.click_alpha = 1 + self.click_x, self.click_y = self:CursorPos() + end +end + +local math_clamp = math.Clamp +local btnFlags = RNDX.SHAPE_IOS + +function PANEL:Paint(w, h) + if self:IsHovered() then + self.hover_status = math_clamp(self.hover_status + 4 * FrameTime(), 0, 1) + else + self.hover_status = math_clamp(self.hover_status - 8 * FrameTime(), 0, 1) + end + + -- Минимальный порог длительности для активной тени + local isActive = (self:IsDown() or self.Depressed) and self.hover_status > 0.8 + if isActive then + self._activeShadowTimer = SysTime() + self._activeShadowMinTime + end + local showActiveShadow = isActive or (self._activeShadowTimer > SysTime()) + + -- Плавная анимация дополнительной тени при зажатии + local activeTarget = showActiveShadow and 10 or 0 + local activeSpeed = (activeTarget > 0) and 7 or 3 -- скорость появления/затухания + self._activeShadowLerp = Lerp(FrameTime() * activeSpeed, self._activeShadowLerp, activeTarget) + + -- Обычная тень + -- if Mantle.ui.convar.depth_ui then + -- RNDX().Rect(0, 0, w, h) + -- :Rad(self.radius) + -- :Color(Mantle.color.window_shadow) + -- :Shape(RNDX.SHAPE_IOS) + -- :Shadow(5, 20) + -- :Clip(self.clipParent) + -- :Draw() + -- end + + -- Дополнительная тень при зажатии + if self._activeShadowLerp > 0 and Mantle.ui.convar.depth_ui then + local col = Color(self.col_hov.r, self.col_hov.g, self.col_hov.b, math.Clamp(self.col_hov.a * 1.5, 0, 255)) + RNDX().Rect(0, 0, w, h) + :Rad(self.radius) + :Color(col) + :Shape(btnFlags) + :Shadow(self._activeShadowLerp * 1.5, 24) + :Draw() + end + + RNDX().Rect(0, 0, w, h) + :Rad(self.radius) + :Color(self.col) + :Shape(btnFlags) + :Draw() + + if self.bool_gradient then + Mantle.func.gradient(0, 0, w, h, 1, Mantle.color.button_shadow, self.radius, btnFlags) + end + + if self.bool_hover then + RNDX().Rect(0, 0, w, h) + :Rad(self.radius) + :Color(Color(self.col_hov.r, self.col_hov.g, self.col_hov.b, self.hover_status * 255)) + :Shape(btnFlags) + :Draw() + end + + if self.click_alpha > 0 then + self.click_alpha = math_clamp(self.click_alpha - FrameTime() * self.ripple_speed, 0, 1) + + local ripple_size = (1 - self.click_alpha) * math.max(w, h) * 2 + local ripple_color = Color( + self.ripple_color.r, + self.ripple_color.g, + self.ripple_color.b, + self.ripple_color.a * self.click_alpha + ) + + RNDX().Rect(self.click_x - ripple_size * 0.5, self.click_y - ripple_size * 0.5, ripple_size, ripple_size) + :Rad(100) + :Color(ripple_color) + :Shape(btnFlags) + :Draw() + end + + if self.text != '' then + draw.SimpleText( + self.text, + self.font, + w * 0.5 + (self.icon ~= '' and self.icon_size * 0.5 + 2 or 0), + h * 0.5, + Mantle.color.text, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER + ) + if self.icon != '' then + surface.SetFont(self.font) + local posX = (w - surface.GetTextSize(self.text) - self.icon_size) * 0.5 - 2 + local posY = (h - self.icon_size) * 0.5 + RNDX().Rect(posX, posY, self.icon_size, self.icon_size) + :Material(self.icon) + :Color(color_white) + :Shape(btnFlags) + :Draw() + end + elseif self.icon != '' then + local posX = (w - self.icon_size) * 0.5 + local posY = (h - self.icon_size) * 0.5 + RNDX().Rect(posX, posY, self.icon_size, self.icon_size) + :Material(self.icon) + :Color(color_white) + :Shape(btnFlags) + :Draw() + end +end + +vgui.Register('MantleBtn', PANEL, 'Button') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/category.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/category.lua new file mode 100644 index 0000000..fe94377 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/category.lua @@ -0,0 +1,130 @@ +local PANEL = {} + +function PANEL:Init() + self:SetTall(30) + self:DockPadding(0, 36, 0, 0) + self.name = 'Категория' + self.bool_opened = false + self.bool_header_centered = false + self.content_size = 0 + self.header_color = Mantle.color.category + self.header_color_standard = self.header_color + self.header_color_opened = Mantle.color.category_opened + + self._childHeights = {} + + self._anim = 0 + self._animTarget = 0 + self._animSpeed = 12 + self._animEased = 0 + + self.header = vgui.Create('Button', self) + self.header:SetText('') + self.header.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(self.header_color) + :Shape(RNDX.SHAPE_IOS) + :Draw() + local posX = self.bool_header_centered and w * 0.5 or 8 + local alignX = self.bool_header_centered and TEXT_ALIGN_CENTER or TEXT_ALIGN_LEFT + draw.SimpleText(self.name, 'Fated.20', posX, 4, Mantle.color.text, alignX) + + self.header_color = Mantle.func.LerpColor(8, self.header_color, self.bool_opened and self.header_color_opened or self.header_color_standard) + end + self.header.DoClick = function() + self.bool_opened = !self.bool_opened + self._animTarget = self.bool_opened and 1 or 0 + end +end + +function PANEL:SetText(name) + self.name = name +end + +function PANEL:SetCenterText(is_centered) + self.bool_header_centered = is_centered +end + +local function getTopBottomMargin(pnl) + if !pnl.GetDockMargin then return 0, 0 end + local ok, l, t, r, b = pcall(function() + return pnl:GetDockMargin() + end) + if !ok or !l then return 0, 0 end + return t or 0, b or 0 +end + +function PANEL:AddItem(panel) + panel:SetParent(self) + + local top, bottom = getTopBottomMargin(panel) + local contribution = (panel.GetTall and panel:GetTall() or 0) + top + bottom + + self._childHeights[panel] = contribution + self.content_size = (self.content_size or 0) + contribution + + if self.bool_opened then + self:SetTall(30 + self.content_size + 12) + end + + local old = panel.OnSizeChanged + panel.OnSizeChanged = function(...) + if old then pcall(old, ...) end + if !IsValid(self) then return end + + local nt, nb = getTopBottomMargin(panel) + local newContribution = (panel.GetTall and panel:GetTall() or 0) + nt + nb + local oldContribution = self._childHeights[panel] or 0 + local delta = newContribution - oldContribution + if delta != 0 then + self._childHeights[panel] = newContribution + self.content_size = math.max(0, (self.content_size or 0) + delta) + end + end + + return panel +end + +function PANEL:SetColor(col) + self.header_color_standard = col + if !self.bool_opened then + self.header_color = self.header_color_standard + end +end + +function PANEL:SetActive(is_active) + is_active = tobool(is_active) + if self.bool_opened == is_active then return end + self.bool_opened = is_active + self._animTarget = is_active and 1 or 0 + self.header_color = is_active and self.header_color_opened or self.header_color_standard +end + +function PANEL:PerformLayout(w, h) + self.header:SetSize(w, 30) +end + +function PANEL:Think() + local ft = FrameTime() + + self._anim = Mantle.func.approachExp(self._anim, self._animTarget, self._animSpeed, ft) + self._animEased = Mantle.func.easeOutCubic(self._anim) + + local currentContentTall = (self.content_size or 0) * self._animEased + + local padded = 12 * self._animEased + + local totalTall = 30 + currentContentTall + padded + self:SetTall(math.max(30, math.floor(totalTall + 0.5))) + + local alphaVal = math.floor(255 * self._animEased + 0.5) + + for _, c in ipairs(self:GetChildren()) do + if IsValid(c) and c != self.header then + if c.SetAlpha then c:SetAlpha(alphaVal) end + end + end +end + +vgui.Register('MantleCategory', PANEL, 'Panel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/checkbox.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/checkbox.lua new file mode 100644 index 0000000..d16ed78 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/checkbox.lua @@ -0,0 +1,136 @@ +local PANEL = {} + +function PANEL:Init() + self.text = '' + self.convar = '' + self.value = false + + self:SetText('') + self:SetCursor('hand') + self:SetTall(36) + + self._circle = 0 + self._circleEased = 0 + self._circleColor = table.Copy(Mantle.color.gray) + + self.toggle = vgui.Create('Button', self) + self.toggle:Dock(RIGHT) + self.toggle:SetWide(48) + self.toggle:DockMargin(0, 0, 14, 0) + self.toggle:SetText('') + self.toggle:SetCursor('hand') + self.toggle.Paint = nil + + self.toggle.DoClick = function() + if self.convar ~= '' then + LocalPlayer():ConCommand(self.convar .. ' ' .. (self.value and 0 or 1)) + end + + self:SetValue(not self.value) + self:OnChange(self.value) + + Mantle.func.sound() + end +end + +function PANEL:OnMousePressed(mcode) + if mcode == MOUSE_LEFT then + self.toggle:DoClick() + end +end + +function PANEL:SetTxt(text) + self.text = text +end + +function PANEL:SetValue(val) + self.value = tobool(val) +end + +function PANEL:GetBool() + return self.value +end + +function PANEL:SetConvar(convar) + local c = GetConVar(convar) + if c then self.value = c:GetBool() end + self.convar = convar +end + +function PANEL:OnChange(new_value) +end + +function PANEL:Paint(w, h) + if Mantle.ui and Mantle.ui.convar and Mantle.ui.convar.depth_ui then + RNDX().Rect(0, 0, w, h) + :Rad(12) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(6, 22) + :Draw() + end + + RNDX().Rect(0, 0, w, h) + :Rad(12) + :Color(Mantle.color.focus_panel) + :Shape(RNDX.SHAPE_IOS) + :Draw() + + local textX = 14 + draw.SimpleText(self.text, 'Fated.18', textX, h * 0.5, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) +end + +function PANEL:PaintOver(w, h) + local tw, th = self.toggle:GetWide(), self.toggle:GetTall() + local tx, ty = self.toggle:GetPos() + local ft = FrameTime() + + local target = self.value and 1 or 0 + local circleSpeed = 8 + self._circle = Mantle.func.approachExp(self._circle, target, circleSpeed, ft) + if math.abs(self._circle - target) < 0.001 then self._circle = target end + self._circleEased = Mantle.func.easeInOutCubic(self._circle) + + local trackW = tw - 10 + local trackH = 18 + local trackX = tx + (tw - trackW) / 2 + local trackY = ty + (th - trackH) / 2 + + RNDX().Rect(trackX, trackY + 1, trackW, trackH - 2) + :Rad(trackH / 2) + :Color(Mantle.color.toggle) + :Shape(RNDX.SHAPE_IOS) + :Draw() + + local circleSize = 20 + local pad = 0 + local textMargin = 14 + + local x0_base = trackX + pad - (circleSize * 0.5) + 0.5 + local x1 = trackX + trackW - pad - (circleSize * 0.5) - 0.5 + local x0_align = textMargin - (circleSize * 0.5) + local x0 = math.max(x0_base, x0_align) + + local circleXPrec = x0 + (x1 - x0) * self._circleEased + local circleCenterX = circleXPrec + circleSize * 0.5 + local circleCenterY = trackY + trackH * 0.5 + + local baseCircle = self.value and Mantle.color.theme or Mantle.color.gray + local circleCol = table.Copy(baseCircle) + circleCol.a = 255 + self._circleColor = Mantle.func.LerpColor(12, self._circleColor, circleCol) + RNDX().Circle(circleCenterX, circleCenterY, circleSize) + :Color(self._circleColor) + :Draw() + + RNDX().Circle(circleCenterX, circleCenterY + 2, circleSize * 1.05) + :Color(Color(0, 0, 0, 30)) + :Draw() +end + +function PANEL:PerformLayout(w, h) + self.toggle:SetWide(48) + self.toggle:DockMargin(0, 0, 14, 0) +end + +vgui.Register('MantleCheckBox', PANEL, 'Panel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/color_picker.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/color_picker.lua new file mode 100644 index 0000000..961c59b --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/color_picker.lua @@ -0,0 +1,222 @@ +local color_close = Color(210, 65, 65) +local color_accept = Color(44, 124, 62) +local color_outline = Color(30, 30, 30) +local color_target = Color(255, 255, 255, 200) + +function Mantle.ui.color_picker(func, color_standart) + if IsValid(Mantle.ui.menu_color_picker) then + Mantle.ui.menu_color_picker:Remove() + end + + local selected_color = color_standart or Color(255, 255, 255) + local hue = 0 + local saturation = 1 + local value = 1 + + if color_standart then + local r, g, b = color_standart.r / 255, color_standart.g / 255, color_standart.b / 255 + local h, s, v = ColorToHSV(Color(r * 255, g * 255, b * 255)) + hue = h + saturation = s + value = v + end + + Mantle.ui.menu_color_picker = vgui.Create('MantleFrame') + Mantle.ui.menu_color_picker:SetSize(300, 378) + Mantle.ui.menu_color_picker:Center() + Mantle.ui.menu_color_picker:MakePopup() + Mantle.ui.menu_color_picker:SetTitle('') + Mantle.ui.menu_color_picker:SetCenterTitle(Mantle.lang.get('mantle', 'color_title')) + + local container = vgui.Create('Panel', Mantle.ui.menu_color_picker) + container:Dock(FILL) + container:DockMargin(10, 10, 10, 10) + container.Paint = nil + + local preview = vgui.Create('Panel', container) + preview:Dock(TOP) + preview:SetTall(40) + preview:DockMargin(0, 0, 0, 10) + preview.Paint = function(self, w, h) + if Mantle.ui.convar.depth_ui then + RNDX().Rect(2, 2, w - 4, h - 4) + :Rad(16) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(5, 20) + :Draw() + end + RNDX.Draw(16, 2, 2, w - 4, h - 4, selected_color, RNDX.SHAPE_IOS) + end + + local colorField = vgui.Create('Panel', container) + colorField:Dock(TOP) + colorField:SetTall(200) + colorField:DockMargin(0, 0, 0, 10) + + local colorCursor = { x = 0, y = 0 } + local isDraggingColor = false + + colorField.OnMousePressed = function(self, keyCode) + if keyCode == MOUSE_LEFT then + isDraggingColor = true + self:OnCursorMoved(self:CursorPos()) + Mantle.func.sound() + end + end + + colorField.OnMouseReleased = function(self, keyCode) + if keyCode == MOUSE_LEFT then + isDraggingColor = false + end + end + + colorField.OnCursorMoved = function(self, x, y) + if isDraggingColor then + local w, h = self:GetSize() + x = math.Clamp(x, 0, w) + y = math.Clamp(y, 0, h) + + colorCursor.x = x + colorCursor.y = y + + saturation = x / w + value = 1 - (y / h) + + selected_color = HSVToColor(hue, saturation, value) + end + end + + colorField.Paint = function(self, w, h) + local segments = 80 + local segmentSize = w / segments + + if Mantle.ui.convar.depth_ui then + RNDX().Rect(0, 0, w, h) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(5, 20) + :Draw() + end + + for x = 0, segments do + for y = 0, segments do + local s = x / segments + local v = 1 - (y / segments) + local segX = x * segmentSize + local segY = y * segmentSize + + surface.SetDrawColor(HSVToColor(hue, s, v)) + surface.DrawRect(segX, segY, segmentSize + 1, segmentSize + 1) + end + end + + RNDX().Circle(colorCursor.x, colorCursor.y, 12) + :Outline(2) + :Color(color_target) + :Draw() + end + + local hueSlider = vgui.Create('Panel', container) + hueSlider:Dock(TOP) + hueSlider:SetTall(20) + hueSlider:DockMargin(0, 0, 0, 10) + + local huePos = 0 + local isDraggingHue = false + + hueSlider.OnMousePressed = function(self, keyCode) + if keyCode == MOUSE_LEFT then + isDraggingHue = true + self:OnCursorMoved(self:CursorPos()) + Mantle.func.sound() + end + end + + hueSlider.OnMouseReleased = function(self, keyCode) + if keyCode == MOUSE_LEFT then + isDraggingHue = false + end + end + + hueSlider.OnCursorMoved = function(self, x, y) + if isDraggingHue then + local w = self:GetWide() + x = math.Clamp(x, 0, w) + + huePos = x + hue = (x / w) * 360 + + selected_color = HSVToColor(hue, saturation, value) + end + end + + hueSlider.Paint = function(self, w, h) + local segments = 100 + local segmentWidth = w / segments + + if Mantle.ui.convar.depth_ui then + RNDX().Rect(0, 0, w, h) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(5, 20) + :Draw() + end + + for i = 0, segments - 1 do + local hueVal = (i / segments) * 360 + local x = i * segmentWidth + + surface.SetDrawColor(HSVToColor(hueVal, 1, 1)) + surface.DrawRect(x, 1, segmentWidth + 1, h - 2) + end + + RNDX().Rect(huePos - 2, 0, 4, h) + :Color(color_target) + :Draw() + end + + local rgbContainer = vgui.Create('Panel', container) + rgbContainer:Dock(TOP) + rgbContainer:SetTall(60) + rgbContainer:DockMargin(0, 0, 0, 10) + rgbContainer.Paint = nil + + local btnContainer = vgui.Create('Panel', container) + btnContainer:Dock(BOTTOM) + btnContainer:SetTall(30) + btnContainer.Paint = nil + + local btnClose = vgui.Create('MantleBtn', btnContainer) + btnClose:Dock(LEFT) + btnClose:SetWide(90) + btnClose:SetTxt(Mantle.lang.get('mantle', 'color_cancel')) + btnClose:SetColorHover(color_close) + btnClose.DoClick = function() + Mantle.ui.menu_color_picker:Remove() + Mantle.func.sound() + end + + local btnSelect = vgui.Create('MantleBtn', btnContainer) + btnSelect:Dock(RIGHT) + btnSelect:SetWide(90) + btnSelect:SetTxt(Mantle.lang.get('mantle', 'color_select')) + btnSelect:SetColorHover(color_accept) + btnSelect.DoClick = function() + Mantle.func.sound() + func(selected_color) + Mantle.ui.menu_color_picker:Remove() + end + + timer.Simple(0, function() + if IsValid(colorField) and IsValid(hueSlider) then + colorCursor.x = saturation * colorField:GetWide() + colorCursor.y = (1 - value) * colorField:GetTall() + huePos = (hue / 360) * hueSlider:GetWide() + end + end) + + timer.Simple(0.1, function() + Mantle.ui.menu_color_picker:SetAlpha(255) + end) +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/combobox.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/combobox.lua new file mode 100644 index 0000000..4a783a2 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/combobox.lua @@ -0,0 +1,267 @@ +local PANEL = {} + +function PANEL:Init() + self.choices = {} + self.selected = nil + self.opened = false + self:SetTall(26) + self:SetText('') + self.font = 'Fated.18' + self.hoverAnim = 0 + self.OnSelect = function(_, _, _) end + + self.btn = vgui.Create('DButton', self) + self.btn:Dock(FILL) + self.btn:SetText('') + self.btn:SetCursor('hand') + self.btn.Paint = function(_, w, h) + if self.btn:IsHovered() then + self.hoverAnim = math.Clamp(self.hoverAnim + FrameTime() * 4, 0, 1) + else + self.hoverAnim = math.Clamp(self.hoverAnim - FrameTime() * 8, 0, 1) + end + + if Mantle.ui.convar.depth_ui then + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(5, 20) + :Draw() + end + RNDX.Draw(16, 0, 0, w, h, Mantle.color.focus_panel, RNDX.SHAPE_IOS) + if self.hoverAnim > 0 then + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Color(Mantle.color.button_hovered.r, Mantle.color.button_hovered.g, Mantle.color.button_hovered.b, self.hoverAnim * 255)) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + draw.SimpleText( + self.selected or self.placeholder or 'Выберите...', + self.font, + 12, + h * 0.5, + Mantle.color.text, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + + local arrowSize = 6 + local arrowX = w - 16 + local arrowY = h / 2 + local arrowColor = ColorAlpha(Mantle.color.text, 180 + self.hoverAnim * 75) + + surface.SetDrawColor(arrowColor) + draw.NoTexture() + if !self.opened then + surface.DrawPoly({ + {x = arrowX - arrowSize, y = arrowY - arrowSize/2}, + {x = arrowX + arrowSize, y = arrowY - arrowSize/2}, + {x = arrowX, y = arrowY + arrowSize/2} + }) + end + end + self.btn.DoClick = function() + if self.opened then + self:CloseMenu() + else + self:OpenMenu() + Mantle.func.sound() + end + end +end + +function PANEL:AddChoice(text, data) + table.insert(self.choices, {text = text, data = data}) +end + +function PANEL:SetValue(val) + self.selected = val +end + +function PANEL:GetValue() + return self.selected +end + +function PANEL:SetPlaceholder(text) + self.placeholder = text +end + +function PANEL:OpenMenu() + if IsValid(self.menu) then + self.menu:Remove() + end + + local menuPadding = 6 + local itemHeight = 26 + local menuHeight = (#self.choices * (itemHeight + 2)) + (menuPadding * 2) + 2 + + local x, y = self:LocalToScreen(0, self:GetTall()) + + self.menu = vgui.Create('DPanel') + self.menu:SetSize(self:GetWide(), menuHeight) + if y + menuHeight > ScrH() - 10 then + y = y - menuHeight - self:GetTall() + end + self.menu:SetPos(x, y) + self.menu:SetDrawOnTop(true) + self.menu:MakePopup() + self.menu:SetKeyboardInputEnabled(false) + self.menu:DockPadding(menuPadding, menuPadding, menuPadding, menuPadding) + + self.menu._anim = 0 + self.menu._animTarget = 1 + self.menu._animSpeed = 18 + self.menu._animEased = 0 + self.menu._closing = false + self.menu._disableBlur = false + self.menu:SetAlpha(0) + + self.menu.Paint = function(s, w, h) + local aMul = s._animEased or ((s:GetAlpha() or 255) / 255) + + local blurMul + if s._closing or s._disableBlur or s._animTarget == 0 then + blurMul = 0 + else + local fadeStart = 0.3 + blurMul = math.Clamp((aMul - fadeStart) / (1 - fadeStart), 0, 1) + end + + local shadowSpread = math.max(0, math.floor(10 * blurMul)) + local shadowIntensity = math.max(0, math.floor(16 * blurMul)) + + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(shadowSpread, shadowIntensity) + :Draw() + + if not s._disableBlur then + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Shape(RNDX.SHAPE_IOS) + :Blur(blurMul) + :Draw() + end + + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.background_panelpopup) + :Shape(RNDX.SHAPE_IOS) + :Draw() + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.background_panelpopup) + :Shape(RNDX.SHAPE_IOS) + :Outline(1) + :Draw() + end + + surface.SetFont(self.font) + for i, choice in ipairs(self.choices) do + local option = vgui.Create('DButton', self.menu) + option:SetText('') + option:Dock(TOP) + option:DockMargin(2, 2, 2, 0) + option:SetTall(itemHeight) + option:SetCursor('hand') + option.Paint = function(s, w, h) + if s:IsHovered() then + RNDX.Draw(16, 0, 0, w, h, Mantle.color.hover, RNDX.SHAPE_IOS) + end + + draw.SimpleText( + choice.text, + 'Fated.18', + 14, + h * 0.5, + Mantle.color.text, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER + ) + + if self.selected == choice.text then + RNDX.Draw(0, 4, h * 0.5 - 1, 4, 2, Mantle.color.theme) + end + end + option.DoClick = function() + self.selected = choice.text + if self.menu and IsValid(self.menu) then + self.menu._closing = true + self.menu._animTarget = 0 + self.menu._disableBlur = true + end + + if self.OnSelect then + self.OnSelect(i, choice.text, choice.data) + end + Mantle.func.sound() + end + end + + self.opened = true + local oldMouseDown = false + self.menu.Think = function(s) + local ft = FrameTime() + s._anim = Mantle.func.approachExp(s._anim or 0, s._animTarget or 1, s._animSpeed or 18, ft) + s._animEased = s._anim + s:SetAlpha(math.floor(255 * s._animEased + 0.5)) + + if s._targetX and s._targetY then + local offsetY = 6 * (1 - s._animEased) + s:SetPos(s._targetX, s._targetY + offsetY) + end + + if s._closing and s._animEased <= 0.005 then + s:Remove() + return + end + + if not s:IsVisible() then return end + local mouseDown = input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT) + if mouseDown and not oldMouseDown then + local mx, my = gui.MousePos() + local x, y = s:LocalToScreen(0, 0) + if not (mx >= x and mx <= x + s:GetWide() and my >= y and my <= y + s:GetTall()) then + s._closing = true + s._animTarget = 0 + s._disableBlur = true + end + end + oldMouseDown = mouseDown + end + + self.menu.OnRemove = function() + if IsValid(self) then + self.opened = false + end + end + + Mantle.func.ClampMenuPosition(self.menu) + self.menu._targetX, self.menu._targetY = self.menu:GetPos() + if not self.menu._initPosSet then + self.menu:SetPos(self.menu._targetX, self.menu._targetY + 6) + self.menu._initPosSet = true + end +end + +function PANEL:CloseMenu() + if IsValid(self.menu) then + if not self.menu._closing then + self.menu._closing = true + self.menu._animTarget = 0 + self.menu._disableBlur = true + end + end + self.opened = false +end + +function PANEL:OnRemove() + self:CloseMenu() +end + +vgui.Register('MantleComboBox', PANEL, 'Panel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/derma_menu.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/derma_menu.lua new file mode 100644 index 0000000..abf09ac --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/derma_menu.lua @@ -0,0 +1,335 @@ +local PANEL = {} + +function PANEL:Init() + self.Items = {} + self:SetSize(160, 0) + self:DockPadding(4, 5, 4, 5) + self:MakePopup() + self:SetKeyboardInputEnabled(false) + self:SetDrawOnTop(true) + self.MaxTextWidth = 0 + + self._anim = 0 + self._animTarget = 1 + self._animSpeed = 18 + self._animEased = 0 + self._initPosSet = false + self._closing = false + self._disableBlur = false + + self._openTime = CurTime() + self:SetAlpha(0) + + self.Think = function() + local ft = FrameTime() + + if not self._initPosSet then + local tx, ty = self:GetPos() + Mantle.func.ClampMenuPosition(self) + self._targetX, self._targetY = self:GetPos() + self:SetPos(self._targetX, self._targetY + 6) + self._initPosSet = true + end + + if CurTime() - self._openTime >= 0.08 then + if input.IsMouseDown(MOUSE_LEFT) or input.IsMouseDown(MOUSE_RIGHT) then + if not self:IsChildHovered() then + self:CloseMenu() + end + end + end + + self._anim = Mantle.func.approachExp(self._anim, self._animTarget, self._animSpeed, ft) + self._animEased = self._anim + local a = math.floor(255 * self._animEased + 0.5) + self:SetAlpha(a) + + if self._targetX and self._targetY then + local offsetY = 6 * (1 - self._animEased) + self:SetPos(self._targetX, self._targetY + offsetY) + end + + if self._closing and self._animEased <= 0.005 then + return self:Remove() + end + end +end + +function PANEL:Paint(w, h) + local aMul = (self._animEased ~= nil) and self._animEased or ((self:GetAlpha() or 255) / 255) + local blurMul + + if self._closing or self._disableBlur or self._animTarget == 0 then + blurMul = 0 + else + local fadeStart = 0.3 + blurMul = math.Clamp((aMul - fadeStart) / (1 - fadeStart), 0, 1) + end + + local shadowSpread = math.max(0, math.floor(10 * blurMul)) + local shadowIntensity = math.max(0, math.floor(16 * blurMul)) + + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Color(Mantle.color.window_shadow.r, Mantle.color.window_shadow.g, Mantle.color.window_shadow.b, math.floor(100 * aMul))) + :Shape(RNDX.SHAPE_IOS) + :Shadow(shadowSpread, shadowIntensity) + :Draw() + + if !self._disableBlur then + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Shape(RNDX.SHAPE_IOS) + :Blur(blurMul) + :Draw() + end + + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math.floor(150 * aMul))) + :Shape(RNDX.SHAPE_IOS) + :Draw() + + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math.floor(150 * aMul))) + :Shape(RNDX.SHAPE_IOS) + :Outline(1) + :Draw() +end + +function PANEL:AddOption(text, func, icon, optData) + surface.SetFont('Fated.18') + local textW = select(1, surface.GetTextSize(text)) + self.MaxTextWidth = math.max(self.MaxTextWidth or 0, textW) + + local option = vgui.Create('DButton', self) + option:SetText('') + option:Dock(TOP) + option:DockMargin(2, 2, 2, 0) + option:SetTall(26) + option.sumTall = 28 + option.Icon = icon + option.Text = text + + option._submenu = nil + option._submenu_open = false + + option.DoClick = function() + if option._submenu then + if option._submenu_open then + option:CloseSubMenu() + else + option:OpenSubMenu() + end + return + end + if func then func() end + Mantle.func.sound() + local function closeAllMenus(panel) + while IsValid(panel) do + if panel.GetName and panel:GetName() == 'MantleDermaMenu' then + local parent = panel:GetParent() + panel:CloseMenu() + panel = parent + else + panel = panel:GetParent() + end + end + end + closeAllMenus(option) + end + + function option:AddSubMenu() + if IsValid(option._submenu) then option._submenu:Remove() end + local submenu = vgui.Create('MantleDermaMenu') + submenu:SetDrawOnTop(true) + submenu:SetParent(self:GetParent()) + submenu:SetVisible(false) + option._submenu = submenu + option._submenu_open = false + + option.OnRemove = function() + if IsValid(submenu) then submenu:Remove() end + end + + function option:OpenSubMenu() + if not IsValid(submenu) then return end + for _, sibling in ipairs(self:GetParent().Items or {}) do + if sibling ~= self and sibling.CloseSubMenu then sibling:CloseSubMenu() end + end + local x, y = self:LocalToScreen(self:GetWide(), 0) + submenu:SetPos(x, y) + Mantle.func.ClampMenuPosition(submenu) + submenu._targetX, submenu._targetY = submenu:GetPos() + submenu:SetVisible(true) + submenu:MakePopup() + submenu:SetKeyboardInputEnabled(false) + option._submenu_open = true + end + + function option:CloseSubMenu() + if IsValid(submenu) then submenu:SetVisible(false) end + option._submenu_open = false + if submenu.Items then + for _, item in ipairs(submenu.Items) do + if item.CloseSubMenu then item:CloseSubMenu() end + end + end + end + + local function isAnySubmenuHovered(opt) + if not IsValid(opt) then return false end + if opt:IsHovered() then return true end + if opt._submenu and IsValid(opt._submenu) and opt._submenu:IsVisible() then + if isAnySubmenuHovered(opt._submenu) then return true end + for _, item in ipairs(opt._submenu.Items or {}) do + if isAnySubmenuHovered(item) then return true end + end + end + return false + end + + option.OnCursorExited = function(pnl) + timer.Simple(0.15, function() + if not isAnySubmenuHovered(pnl) then + if IsValid(pnl) then + pnl:CloseSubMenu() + end + end + end) + end + submenu.OnCursorExited = function(pnl) + timer.Simple(0.15, function() + if not isAnySubmenuHovered(option) then + if IsValid(pnl) then + option:CloseSubMenu() + end + end + end) + end + + return submenu + end + + option.AddSubMenu = option.AddSubMenu + + if optData then + for k, v in pairs(optData) do + option[k] = v + end + end + + local iconMat + + if option.Icon then + iconMat = type(option.Icon) == 'IMaterial' and option.Icon or Material(option.Icon) + end + + option.Paint = function(pnl, w, h) + w = w or pnl:GetWide() + h = h or pnl:GetTall() + + if pnl:IsHovered() then + if Mantle.ui.convar.depth_ui then + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(5, 20) + :Draw() + end + RNDX.Draw(16, 0, 0, w, h, Mantle.color.hover, RNDX.SHAPE_IOS) + + if pnl._submenu and not pnl._submenu_open then + pnl:OpenSubMenu() + end + end + + if iconMat then + local iconSize = 16 + RNDX.DrawMaterial(0, 10, (h - iconSize) / 2, iconSize, iconSize, color_white, iconMat) + end + + draw.SimpleText(pnl.Text, 'Fated.18', pnl.Icon and 32 or 14, h * 0.5, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + table.insert(self.Items, option) + + self:UpdateSize() + + return option +end + +function PANEL:AddSpacer() + local spacer = vgui.Create('DPanel', self) + spacer:Dock(TOP) + spacer:DockMargin(8, 6, 8, 6) + spacer:SetTall(1) + spacer.sumTall = 13 + spacer.Paint = function(_, w, h) + RNDX.Draw(0, 0, 0, w, h, Mantle.color.focus_panel) + end + + table.insert(self.Items, spacer) + self:UpdateSize() + + return spacer +end + +function PANEL:UpdateSize() + local height = 12 + for _, item in ipairs(self.Items) do + if IsValid(item) then + height = height + (item.sumTall or item:GetTall()) + end + end + + local maxWidth = math.max(160, self.MaxTextWidth + 56) + self:SetSize(maxWidth, math.min(height, ScrH() * 0.8)) + + if not self._targetX or not self._targetY then + Mantle.func.ClampMenuPosition(self) + self._targetX, self._targetY = self:GetPos() + if not self._initPosSet then + self:SetPos(self._targetX, self._targetY + 6) + end + else + Mantle.func.ClampMenuPosition(self) + self._targetX, self._targetY = self:GetPos() + end +end + +function PANEL:Open() + -- Clear +end + +function PANEL:CloseMenu() + if self._closing then return end + self._closing = true + self._disableBlur = true + self._animTarget = 0 +end + +function PANEL:GetDeleteSelf() + return true +end + +vgui.Register('MantleDermaMenu', PANEL, 'DPanel') + +function Mantle.ui.derma_menu() + if IsValid(Mantle.ui.menu_derma_menu) then + Mantle.ui.menu_derma_menu:CloseMenu() + end + + local mouseX, mouseY = input.GetCursorPos() + local m = vgui.Create('MantleDermaMenu') + m:SetPos(mouseX, mouseY) + + Mantle.func.ClampMenuPosition(m) + + m._targetX, m._targetY = m:GetPos() + Mantle.ui.menu_derma_menu = m + + return m +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/entry.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/entry.lua new file mode 100644 index 0000000..7515f6e --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/entry.lua @@ -0,0 +1,98 @@ +local PANEL = {} + +function PANEL:Init() + self.title = nil + self.placeholder = Mantle.lang.get('mantle', 'entry_default_placeholder') + self:SetTall(26) + self.action = function() end + + local font = 'Fated.18' + + self.textEntry = vgui.Create('DTextEntry', self) + self.textEntry:Dock(FILL) + self.textEntry:SetText('') + self.textEntry.OnCloseFocus = function() + self.action(self:GetValue()) + end + + self._text_offset = 0 + self._shadowLerp = 5 + + self.textEntry.Paint = nil + self.textEntry.PaintOver = function(s, w, h) + local ft = FrameTime() + + if Mantle.ui.convar.depth_ui then + local target = s:IsEditing() and 10 or 5 + self._shadowLerp = Mantle.func.approachExp(self._shadowLerp, target, 12, ft) + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.window_shadow) + :Shape(RNDX.SHAPE_IOS) + :Shadow(self._shadowLerp, 20) + :Draw() + end + + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.focus_panel) + :Shape(RNDX.SHAPE_IOS) + :Draw() + + local value = self:GetValue() or '' + surface.SetFont(font) + local padding = 6 + local available_w = w - padding * 2 + + local caret = #value + local before_caret = string.sub(value, 1, caret) + local caret_x = surface.GetTextSize(before_caret) + local text_w = surface.GetTextSize(value) + + local desired_offset = 0 + if caret_x > available_w then + desired_offset = caret_x - available_w + end + if text_w - desired_offset < available_w then + desired_offset = math.max(0, text_w - available_w) + end + + self._text_offset = Mantle.func.approachExp(self._text_offset or 0, desired_offset, 24, ft) + + local text = self.placeholder + local col = Mantle.color.gray + if value != '' then + text = value + col = Mantle.color.text + end + + draw.SimpleText(text, font, padding - self._text_offset, h * 0.5, col, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end +end + +function PANEL:SetTitle(title) + self.title = title + self:SetTall(52) + + if IsValid(self.titlePanel) then + self.titlePanel:Remove() + end + + self.titlePanel = vgui.Create('DPanel', self) + self.titlePanel:Dock(TOP) + self.titlePanel:DockMargin(0, 0, 0, 6) + self.titlePanel:SetTall(18) + self.titlePanel.Paint = function(_, w, h) + draw.SimpleText(self.title, 'Fated.18', 0, 0, Mantle.color.text) + end +end + +function PANEL:SetPlaceholder(placeholder) + self.placeholder = placeholder +end + +function PANEL:GetValue() + return self.textEntry:GetText() +end + +vgui.Register('MantleEntry', PANEL, 'EditablePanel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/frame.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/frame.lua new file mode 100644 index 0000000..26e5203 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/frame.lua @@ -0,0 +1,194 @@ +local PANEL = {} + +local mat_close = Material('mantle/close_btn_new.png') + +function PANEL:Init() + self.bool_alpha = true + self.bool_lite = false + self.title = Mantle.lang.get('mantle', 'frame_title') + self.center_title = '' + + self:DockPadding(6, 30, 6, 6) + + self.top_panel = vgui.Create('DButton', self) + self.top_panel:SetText('') + self.top_panel:SetCursor('sizeall') + self.top_panel.Paint = nil + self.top_panel.OnMousePressed = function(s, key) + if key == MOUSE_LEFT then + self.Dragging = {gui.MouseX() - self.x, gui.MouseY() - self.y} + s:MouseCapture(true) + self:SetAlpha(200) + end + end + self.top_panel.OnMouseReleased = function(s, key) + if key == MOUSE_LEFT then + self.Dragging = nil + s:MouseCapture(false) + self:SetAlpha(255) + end + end + self.top_panel.Think = function(s) + if self.Dragging then + local mouseX, mouseY = gui.MousePos() + local newPosX, newPosY = mouseX - self.Dragging[1], mouseY - self.Dragging[2] + + self:SetPos(newPosX, newPosY) + end + end + + self.cls = vgui.Create('Button', self) + self.cls:SetText('') + self.cls.Paint = function(_, w, h) + RNDX().Rect(2, 2, w - 4, h - 4) + :Color(Mantle.color.header_text) + :Material(mat_close) + :Draw() + end + self.cls.DoClick = function() + self:AlphaTo(0, 0.1, 0, function() + self:Remove() + end) + + Mantle.func.sound() + end + self.cls.DoRightClick = function() + local DM = Mantle.ui.derma_menu() + + DM:AddOption(Mantle.lang.get('mantle', 'frame_alpha'), function() + self.bool_alpha = !self.bool_alpha + end, self.bool_alpha and 'icon16/bullet_green.png' or 'icon16/bullet_red.png') + + local boolInput = self:IsKeyboardInputEnabled() + DM:AddOption(Mantle.lang.get('mantle', 'frame_move_from_menu'), function() + self:SetKeyBoardInputEnabled(!boolInput) + end, !boolInput and 'icon16/bullet_green.png' or 'icon16/bullet_red.png') + + DM:AddOption(Mantle.lang.get('mantle', 'frame_close_window'), function() + self:Remove() + end, 'icon16/cross.png') + end +end + +function PANEL:SetAlphaBackground(is_alpha) + self.bool_alpha = is_alpha +end + +function PANEL:SetTitle(title) + self.title = title +end + +function PANEL:SetCenterTitle(center_title) + self.center_title = center_title +end + +function PANEL:ShowAnimation() + Mantle.func.animate_appearance(self, self:GetWide(), self:GetTall(), 0.3, 0.2) +end + +function PANEL:DisableCloseBtn() + self.cls:SetVisible(false) +end + +function PANEL:SetDraggable(is_draggable) + self.top_panel:SetVisible(is_draggable) +end + +function PANEL:LiteMode() + self.bool_lite = true + self:DockPadding(6, 6, 6, 6) + + self.cls:SetZPos(2) +end + +function PANEL:Notify(text, duration, col) + if IsValid(self.messagePanel) then self.messagePanel:Remove() end + duration = duration or 2 + col = col or Mantle.color.theme + + surface.SetFont('Fated.20') + local tw, th = surface.GetTextSize(text) + + local mp = vgui.Create('DPanel', self) + mp:SetSize(tw + 16, th + 8) + mp:SetMouseInputEnabled(false) + local startY = self:GetTall() + mp:GetTall() + local endY = self:GetTall() - mp:GetTall() - 16 + mp:SetPos((self:GetWide() - mp:GetWide()) * 0.5, startY) + mp:SetAlpha(0) + mp.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(col) + :Shadow(7, 20) + :Outline(3) + :Clip(self) + :Draw() + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(col) + :Draw() + draw.SimpleText(text, 'Fated.20', w * 0.5, h * 0.5 - 1, Mantle.color.text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + mp:MoveTo(mp.x, endY, 0.3, 0, 0.7) + mp:AlphaTo(255, 0.3, 0, function() + timer.Simple(duration, function() + if !IsValid(mp) then return end + mp:AlphaTo(0, 0.25, 0, function() + if IsValid(mp) then + mp:Remove() + end + end) + end) + end) + + self.messagePanel = mp +end + +local flagsHeader = RNDX.NO_BL + RNDX.NO_BR +local flagsBackground = RNDX.NO_TL + RNDX.NO_TR + +function PANEL:Paint(w, h) + RNDX().Rect(0, 0, w, h) + :Rad(6) + :Color(Mantle.color.window_shadow) + :Shadow(10, 16) + :Shape(RNDX.SHAPE_IOS) + :Draw() + if !self.bool_lite then + RNDX().Rect(0, 0, w, 24) + :Radii(6, 6, 0, 0) + :Color(Mantle.color.header) + :Draw() + end + + local headerTall = self.bool_lite and 0 or 24 + if self.bool_alpha and Mantle.ui.convar.blur then + RNDX().Rect(0, headerTall, w, h - headerTall) + :Radii(self.bool_lite and 6 or 0, self.bool_lite and 6 or 0, 6, 6) + :Blur() + :Draw() + end + RNDX().Rect(0, headerTall, w, h - headerTall) + :Radii(self.bool_lite and 6 or 0, self.bool_lite and 6 or 0, 6, 6) + :Color((self.bool_alpha and Mantle.ui.convar.blur) and Mantle.color.background_alpha or Mantle.color.background) + :Draw() + + if !self.bool_lite then + if self.center_title != '' then + draw.SimpleText(self.center_title, 'Fated.20b', w * 0.5, 12, Mantle.color.header_text, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + draw.SimpleText(self.title, 'Fated.16', 6, 4, Mantle.color.header_text) + end +end + +function PANEL:PerformLayout(w, h) + self.top_panel:SetSize(w, 24) + + self.cls:SetSize(20, 20) + self.cls:SetPos(w - 22, 2) +end + +vgui.Register('MantleFrame', PANEL, 'EditablePanel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/player_selector.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/player_selector.lua new file mode 100644 index 0000000..03e773c --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/player_selector.lua @@ -0,0 +1,115 @@ +local color_disconnect = Color(210, 65, 65) +local color_bot = Color(70, 150, 220) +local color_online = Color(120, 180, 70) + +function Mantle.ui.player_selector(do_click, func_check) + if IsValid(Mantle.ui.menu_player_selector) then + Mantle.ui.menu_player_selector:Remove() + end + + Mantle.ui.menu_player_selector = vgui.Create('MantleFrame') + Mantle.ui.menu_player_selector:SetSize(340, 398) + Mantle.ui.menu_player_selector:Center() + Mantle.ui.menu_player_selector:MakePopup() + Mantle.ui.menu_player_selector:SetTitle('') + Mantle.ui.menu_player_selector:SetCenterTitle(Mantle.lang.get('mantle', 'player_title')) + Mantle.ui.menu_player_selector:ShowAnimation() + + local contentPanel = vgui.Create('Panel', Mantle.ui.menu_player_selector) + contentPanel:Dock(FILL) + contentPanel:DockMargin(8, 0, 8, 8) + Mantle.ui.menu_player_selector.sp = vgui.Create('MantleScrollPanel', contentPanel) + Mantle.ui.menu_player_selector.sp:Dock(FILL) + + local CARD_HEIGHT = 44 + local AVATAR_SIZE = 32 + local AVATAR_X = 14 + + local function CreatePlayerCard(pl) + local card = vgui.Create('DButton', Mantle.ui.menu_player_selector.sp) + card:Dock(TOP) + card:DockMargin(0, 5, 0, 0) + card:SetTall(CARD_HEIGHT) + card:SetText('') + card.hover_status = 0 + card.OnCursorEntered = function(self) + self:SetCursor('hand') + end + card.OnCursorExited = function(self) + self:SetCursor('arrow') + end + card.Think = function(self) + local target = self:IsHovered() and 1 or 0 + self.hover_status = Mantle.func.approachExp(self.hover_status, target, 8, FrameTime()) + end + card.DoClick = function() + if IsValid(pl) then + Mantle.func.sound() + do_click(pl) + end + Mantle.ui.menu_player_selector:Remove() + end + card.pl_color = team.GetColor(pl:Team()) or color_online + card.Paint = function(self, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(10) + :Color(Mantle.color.panel[1]) + :Shape(RNDX.SHAPE_IOS) + :Draw() + if self.hover_status > 0 then + RNDX().Rect(0, 0, w, h) + :Rad(10) + :Color(Color(0, 0, 0, 40 * self.hover_status)) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + local infoX = AVATAR_X + AVATAR_SIZE + 10 + + if !IsValid(pl) then + draw.SimpleText(Mantle.lang.get('mantle', 'player_offline'), 'Fated.18', infoX, h * 0.5, color_disconnect, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + return + end + + draw.SimpleText(pl:Name(), 'Fated.18', infoX, 6, Mantle.color.text, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + local group = pl:GetUserGroup() or 'user' + group = string.upper(string.sub(group, 1, 1)) .. string.sub(group, 2) + draw.SimpleText(group, 'Fated.14', infoX, h - 6, Mantle.color.gray, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) + draw.SimpleText(pl:Ping() .. ' ' .. Mantle.lang.get('mantle', 'player_ping'), 'Fated.16', w - 20, h - 6, Mantle.color.gray, TEXT_ALIGN_RIGHT, TEXT_ALIGN_BOTTOM) + + local statusColor = color_disconnect + if pl:IsBot() then + statusColor = color_bot + else + statusColor = self.pl_color + end + + RNDX.DrawCircle(w - 24, 14, 12, statusColor) + end + + local avatarImg = vgui.Create('AvatarImage', card) + avatarImg:SetSize(AVATAR_SIZE, AVATAR_SIZE) + avatarImg:SetPos(AVATAR_X, (CARD_HEIGHT - AVATAR_SIZE) * 0.5) + avatarImg:SetSteamID(pl:SteamID64(), 64) + avatarImg:SetMouseInputEnabled(false) + avatarImg:SetKeyboardInputEnabled(false) + avatarImg.PaintOver = function() end + avatarImg:SetPos(AVATAR_X, (card:GetTall() - AVATAR_SIZE) * 0.5) + + return card + end + + for _, pl in player.Iterator() do + CreatePlayerCard(pl) + end + + Mantle.ui.menu_player_selector.btn_close = vgui.Create('MantleBtn', Mantle.ui.menu_player_selector) + Mantle.ui.menu_player_selector.btn_close:Dock(BOTTOM) + Mantle.ui.menu_player_selector.btn_close:DockMargin(16, 8, 16, 12) + Mantle.ui.menu_player_selector.btn_close:SetTall(36) + Mantle.ui.menu_player_selector.btn_close:SetTxt(Mantle.lang.get('mantle', 'player_close')) + Mantle.ui.menu_player_selector.btn_close:SetColorHover(color_disconnect) + Mantle.ui.menu_player_selector.btn_close.DoClick = function() + Mantle.ui.menu_player_selector:Remove() + end +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/radialpanel.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/radialpanel.lua new file mode 100644 index 0000000..dbaca3b --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/radialpanel.lua @@ -0,0 +1,458 @@ +local PANEL = {} + +local pi = math.pi +local math_cos = math.cos +local math_sin = math.sin +local math_atan2 = math.atan2 +local math_sqrt = math.sqrt +local math_floor = math.floor +local math_min = math.min +local math_max = math.max +local FrameTime = FrameTime +local SysTime = SysTime +local CurTime = CurTime +local Lerp = Lerp + +local EPS = 1e-6 +local EPS_ANGLE = 1e-4 + +local function GetSectorIndexFromAngle(angle, cnt) + if !angle or cnt <= 0 then return nil end + local sector = (2 * pi) / cnt + local raw = angle / sector + local idx = (math_floor(raw + EPS) % cnt) + 1 + return idx +end + +local function ClampEndAngle(a) + if a >= 360 then + return 360 - EPS_ANGLE + end + return a +end + +function PANEL:Init(options) + options = options or {} + + self.options = {} + self.rootMenu = { title = 'Меню', desc = 'Выберите опцию', options = self.options } + self.menuStack = {} + self.currentMenu = self.rootMenu + + local baseRadius = options.radius or 320 + local baseInner = options.inner_radius or 110 + + local minW, minH = 1366, 768 + local scale = 1 + if Mantle.func.sw > minW and Mantle.func.sh > minH then + scale = math_min(math_min(Mantle.func.sw / 1920, Mantle.func.sh / 1080), 1.15) + end + + self.radius = Mantle.func.w(baseRadius) * scale + self.innerRadius = Mantle.func.w(baseInner) * scale + self.scale = scale + + self.titleFont = 'Fated.28' + self.font = 'Fated.20' + self.descFont = 'Fated.14' + + self.fadeInTime = 0.18 + self.openTime = SysTime() + self.currentAlpha = 0 + self.scaleAnim = 0.96 + self.scale_animation = options.scale_animation != false + + self.disable_background = options.disable_background or false + self.hover_sound = options.hover_sound or 'mantle/ratio_btn.ogg' + + self.hoverOption = nil + self.hoverAnim = 0 + self.selectedOption = nil + + self._hotkeyCooldown = {} + + self.optionHover = {} + + self:SetSize(Mantle.func.sw, Mantle.func.sh) + self:SetPos(0, 0) + self:MakePopup() + self:SetKeyboardInputEnabled(true) + self:SetDrawOnTop(true) + self:SetMouseInputEnabled(true) + + self._mouseWasDown = false + + self.Think = function() + if self.currentAlpha < 255 then + self.currentAlpha = math.Clamp(255 * ((SysTime() - self.openTime) / self.fadeInTime), 0, 255) + if self.scale_animation then + local t = math.Clamp((SysTime() - self.openTime) / self.fadeInTime, 0, 1) + self.scaleAnim = 0.96 + (1 - (1 - t)^2) * 0.04 + else + self.scaleAnim = 1 + end + end + + local curOuter = self.radius * self.scaleAnim + local curInner = self.innerRadius * self.scaleAnim + + local mouseDown = input.IsMouseDown(MOUSE_LEFT) + if mouseDown and !self._mouseWasDown then + local mx, my = self:CursorPos() + local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2 + local dist = math_sqrt((mx - cx)^2 + (my - cy)^2) + + if dist > curInner and dist < curOuter then + local ang = math_atan2(my - cy, mx - cx) + if ang < 0 then ang = ang + 2 * pi end + local opts = self:GetCurrentOptions() + local cnt = #opts + if cnt > 0 then + local idx = GetSectorIndexFromAngle(ang, cnt) + if idx and opts[idx] then + self:SelectOption(idx) + if self.hover_sound then surface.PlaySound(self.hover_sound) end + end + end + elseif dist <= curInner then + if #self.menuStack > 0 then + self:GoBack() + if self.hover_sound then surface.PlaySound(self.hover_sound) end + else + self:Remove() + end + else + if dist >= curOuter then + self:Remove() + end + end + end + + local mx, my = self:CursorPos() + local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2 + local dist = math_sqrt((mx - cx)^2 + (my - cy)^2) + local hovered = nil + if dist > curInner and dist < curOuter then + local ang = math_atan2(my - cy, mx - cx) + if ang < 0 then ang = ang + 2 * pi end + local opts = self:GetCurrentOptions() + local cnt = #opts + if cnt > 0 then + hovered = GetSectorIndexFromAngle(ang, cnt) + end + end + + if self.hoverOption != hovered and hovered and self.hover_sound then + surface.PlaySound(self.hover_sound) + end + + self.hoverOption = hovered + self.hoverAnim = math.Clamp(self.hoverAnim + (self.hoverOption and 10 or -20) * FrameTime(), 0, 1) + self._mouseWasDown = mouseDown + + local dt = FrameTime() + local opts = self:GetCurrentOptions() + for i = 1, #opts do + local target = (self.hoverOption == i) and 1 or 0 + self.optionHover[i] = Mantle.func.approachExp(self.optionHover[i] or 0, target, 18, dt) + end + + for i = 1, math_min(9, #self:GetCurrentOptions()) do + local k = KEY_1 + (i-1) + if input.IsKeyDown(k) then + local last = self._hotkeyCooldown[k] or 0 + if CurTime() - last > 0.18 then + self._hotkeyCooldown[k] = CurTime() + self:SelectOption(i) + if self.hover_sound then surface.PlaySound(self.hover_sound) end + end + end + end + end +end + +function PANEL:OnMousePressed(k) + local mx, my = self:CursorPos() + local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2 + local curOuter = self.radius * self.scaleAnim + local dist = math_sqrt((mx - cx)^2 + (my - cy)^2) + if dist <= curOuter then return self:MouseCapture(true) end + self:Remove() + return true +end +function PANEL:OnMouseReleased(k) self:MouseCapture(false) end + +function PANEL:CreateSubMenu(title, desc) + local submenu = { title = title or 'Подменю', desc = desc or '', options = {} } + function submenu:AddOption(text, func, icon, desc) + table.insert(submenu.options, { text = text, func = func, icon = icon, desc = desc }) + return #submenu.options + end + return submenu +end + +function PANEL:AddSubMenuOption(text, submenu, icon, desc) + return self:AddOption(text, nil, icon, desc, submenu) +end + +function PANEL:AddOption(text, func, icon, desc, submenu) + table.insert(self.options, { text = text, func = func, icon = icon, desc = desc, submenu = submenu }) + return #self.options +end + +function PANEL:GetCurrentOptions() + if self.currentMenu and self.currentMenu.options then + return self.currentMenu.options + end + return self.options +end + +function PANEL:SelectOption(index) + local opts = self:GetCurrentOptions() + if !opts or !opts[index] then return end + local opt = opts[index] + if opt.submenu then + table.insert(self.menuStack, self.currentMenu) + self.currentMenu = opt.submenu + self:UpdateCenterText() + return + end + self.selectedOption = opt + if opt.func then + local ok, err = pcall(opt.func) + if !ok then ErrorNoHalt(tostring(err) .. '\n') end + end + self:Remove() +end + +function PANEL:GoBack() + if #self.menuStack > 0 then + self.currentMenu = table.remove(self.menuStack) + self:UpdateCenterText() + end +end + +function PANEL:SetCenterText(title, desc) + self.rootMenu.title = title or self.rootMenu.title + self.rootMenu.desc = desc or self.rootMenu.desc + self:UpdateCenterText() +end + +function PANEL:UpdateCenterText() + if self.currentMenu then + self.centerText = self.currentMenu.title or self.rootMenu.title + self.centerDesc = self.currentMenu.desc or self.rootMenu.desc + else + self.centerText = self.rootMenu.title + self.centerDesc = self.rootMenu.desc + end +end + +function PANEL:IsMouseOver() + local mx, my = self:CursorPos() + local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2 + local curOuter = self.radius * self.scaleAnim + return math_sqrt((mx - cx)^2 + (my - cy)^2) <= curOuter +end + +function PANEL:OnCursorMoved(x,y) + if !self:IsMouseOver() then self.hoverOption = nil end +end + +function PANEL:OnRemove() + if Mantle.ui.menu_radial == self then Mantle.ui.menu_radial = nil end +end + +function PANEL:Paint(w,h) + local cx, cy = Mantle.func.sw / 2, Mantle.func.sh / 2 + local alpha = math.Clamp(self.currentAlpha / 255, 0, 1) + local opts = self:GetCurrentOptions() + local cnt = #opts + + if !self.disable_background then + RNDX().Rect(0, 0, w, h) + :Radii(0, 0, 0, 0) + :Color(Color(0, 0, 0, 140 * alpha)) + :Draw() + end + + local outerR = self.radius * self.scaleAnim + local innerR = self.innerRadius * self.scaleAnim + local outerD = outerR * 2 + local innerD = innerR * 2 + + RNDX().Circle(cx, cy, outerD + 12) + :Color(Mantle.color.window_shadow) + :Shadow(8, 24) + :Draw() + + RNDX().Circle(cx, cy, outerD) + :Color(Color(Mantle.color.background.r, Mantle.color.background.g, Mantle.color.background.b, math_floor(240 * alpha))) + :Draw() + + RNDX().Circle(cx, cy, outerD) + :Outline(2) + :Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(160 * alpha))) + :Draw() + + if cnt > 0 then + local sectorDeg = 360 / cnt + local baseCol = Mantle.color.background_panelpopup + local baseSectorCol = Color(baseCol.r, baseCol.g, baseCol.b, math_floor(255 * alpha)) + + for i = 1, cnt do + local startDeg = (i-1) * sectorDeg + local endDeg = i * sectorDeg + if startDeg < 0 then startDeg = 0 end + endDeg = ClampEndAngle(endDeg) + if endDeg > startDeg then + RNDX().Circle(cx, cy, outerD) + :StartAngle(startDeg) + :EndAngle(endDeg) + :Color(baseSectorCol) + :Draw() + + RNDX().Circle(cx, cy, outerD) + :StartAngle(startDeg) + :EndAngle(endDeg) + :Outline(2) + :Color(Color(Mantle.color.panel[1].r, Mantle.color.panel[1].g, Mantle.color.panel[1].b, math_floor(160 * alpha))) + :Draw() + end + end + + if self.hoverOption and opts[self.hoverOption] then + local i = self.hoverOption + local startDeg = (i-1) * sectorDeg + local endDeg = i * sectorDeg + if startDeg < 0 then startDeg = 0 end + endDeg = ClampEndAngle(endDeg) + if endDeg > startDeg then + local th = Mantle.color.theme + local hoverAlpha = math_floor(200 * self.hoverAnim * alpha) + RNDX().Circle(cx, cy, outerD) + :StartAngle(startDeg) + :EndAngle(endDeg) + :Color(Color(th.r, th.g, th.b, math_floor(22 * self.hoverAnim * alpha))) + :Draw() + + RNDX().Circle(cx, cy, outerD) + :StartAngle(startDeg) + :EndAngle(endDeg) + :Outline(2) + :Color(Color(th.r, th.g, th.b, hoverAlpha)) + :Draw() + end + end + + RNDX().Circle(cx, cy, innerD) + :Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math_floor(255 * alpha))) + :Draw() + + local tintA = math_floor(36 * alpha) + RNDX().Circle(cx, cy, innerD - 8) + :Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, tintA)) + :Draw() + + RNDX().Circle(cx, cy, innerD) + :Outline(2) + :Color(Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(80 * alpha))) + :Draw() + + local sectorRad = (2 * pi) / cnt + for i, option in ipairs(opts) do + local startA = (i - 1) * sectorRad + local midA = startA + sectorRad * 0.5 + + local hv = self.optionHover[i] or 0 + local eased = Mantle.func.easeOutCubic(math.Clamp(hv, 0, 1)) + + local labelR = innerR + (outerR - innerR) * (0.5 + 0.06 * eased) + local numberR = innerR + (labelR - innerR) * 0.35 + local lx = cx + labelR * math_cos(midA) + local ly = cy + labelR * math_sin(midA) + local nx = cx + numberR * math_cos(midA) + local ny = cy + numberR * math_sin(midA) + local isHovered = (self.hoverOption == i) + local txtAlpha = math_floor((isHovered and 255 or 220) * alpha) + local txtCol = Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, txtAlpha) + + if option.icon and option.icon != false and option.icon != nil then + local iconSize = Mantle.func.w(28) * self.scale * (1 + 0.06 * eased) + local iconX = lx - iconSize * 0.5 + local iconY = ly - iconSize * 0.5 - Mantle.func.h(6) * self.scale + local mat = Material(option.icon) + if mat and !mat:IsError() then + surface.SetDrawColor(255,255,255, math_floor(230 * alpha)) + surface.SetMaterial(mat) + surface.DrawTexturedRect(iconX, iconY, iconSize, iconSize) + end + + draw.SimpleText(option.text or '', self.font, lx, ly + iconSize*0.5 - Mantle.func.h(4)*self.scale, txtCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + + if option.desc and isHovered then + draw.SimpleText(option.desc, self.descFont, lx, ly + iconSize*0.5 + Mantle.func.h(16)*self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + + if i <= 9 then + draw.SimpleText(tostring(i), 'Fated.14', nx, ny, Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(200 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + else + draw.SimpleText(option.text or '', self.font, lx, ly - Mantle.func.h(4) * self.scale, txtCol, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if option.desc and isHovered then + draw.SimpleText(option.desc, self.descFont, lx, ly + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + end + + if i <= 9 then + draw.SimpleText(tostring(i), 'Fated.14', nx, ny, Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math_floor(200 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + else + RNDX().Circle(cx, cy, outerD) + :Color(Color(Mantle.color.background.r, Mantle.color.background.g, Mantle.color.background.b, math_floor(240 * alpha))) + :Draw() + RNDX().Circle(cx, cy, innerD) + :Color(Color(Mantle.color.background_panelpopup.r, Mantle.color.background_panelpopup.g, Mantle.color.background_panelpopup.b, math_floor(255 * alpha))) + :Draw() + end + + if self.selectedOption then + local opt = self.selectedOption + if opt.icon and opt.icon != false and opt.icon != nil then + local isz = Mantle.func.w(48) * self.scale + local mat = Material(opt.icon) + if mat and !mat:IsError() then + surface.SetDrawColor(255, 255, 255, math_floor(255 * alpha)) + surface.SetMaterial(mat) + surface.DrawTexturedRect(cx - isz/2, cy - isz/2 - Mantle.func.h(6)*self.scale, isz, isz) + end + draw.SimpleText(opt.text or '', self.titleFont, cx + isz*0.6, cy - Mantle.func.h(6) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + if opt.desc then + draw.SimpleText(opt.desc, self.descFont, cx + isz*0.6, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + else + draw.SimpleText(opt.text or '', self.titleFont, cx, cy - Mantle.func.h(6) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if opt.desc then + draw.SimpleText(opt.desc, self.descFont, cx, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(180 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + else + draw.SimpleText(self.centerText or self.rootMenu.title, self.titleFont, cx, cy - Mantle.func.h(8) * self.scale, Color(Mantle.color.text.r, Mantle.color.text.g, Mantle.color.text.b, math_floor(255 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(self.centerDesc or self.rootMenu.desc, self.descFont, cx, cy + Mantle.func.h(18) * self.scale, Color(Mantle.color.header_text.r, Mantle.color.header_text.g, Mantle.color.header_text.b, math_floor(160 * alpha)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end +end + +vgui.Register('MantleRadialPanel', PANEL, 'DPanel') + +function Mantle.ui.radial_menu(options) + if IsValid(Mantle.ui.menu_radial) then + Mantle.ui.menu_radial:Remove() + end + + local m = vgui.Create('MantleRadialPanel') + m:Init(options) + Mantle.ui.menu_radial = m + return m +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/scrollpanel.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/scrollpanel.lua new file mode 100644 index 0000000..77ab0db --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/scrollpanel.lua @@ -0,0 +1,498 @@ +local PANEL = {} + +function PANEL:Init() + self._vbarPadRight = 6 + + self.content = vgui.Create('Panel', self) + self.content:SetMouseInputEnabled(true) + + self.vbar = vgui.Create('Panel', self) + self.vbar:SetMouseInputEnabled(true) + + self.vbarDefaultWidth = 4 + self.vbarExpandedWidth = 6 + self.vbarWidthSpeed = 12 + self.vbarReserveWidth = self.vbarExpandedWidth + + self.vbar:SetWide(self.vbarDefaultWidth) + self.vbar.Dragging = false + self.vbar._press_off = 0 + self.vbar:Dock(RIGHT) + self.vbar:DockMargin(6, 0, 0, 0) + self.vbar.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.focus_panel) + :Draw() + end + + self.vbarHoverDelay = 1 + self.vbarUnhoverDelay = 0.5 + + self.vbar._hoverEnter = 0 + self.vbar._hoverExit = 0 + self.vbar._expanded = false + + self.vbar.btnGrip = vgui.Create('MantleBtn', self.vbar) + self.vbar.btnGrip:SetText('') + self.vbar.btnGrip._ShadowLerp = 0 + self.vbar.btnGrip.Paint = function(s, w, h) + s._ShadowLerp = Lerp(FrameTime() * 10, s._ShadowLerp, self.vbar.Dragging and 7 or 0) + + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.theme) + :Shadow(s._ShadowLerp, 20) + :Draw() + RNDX().Rect(0, 0, w, h) + :Rad(32) + :Color(Mantle.color.theme) + :Draw() + end + + self.vbar.btnGrip.OnMousePressed = function(s) + local _, my = s:GetParent():CursorPos() + s:GetParent().Dragging = true + s:GetParent()._press_off = my - s.y + s:MouseCapture(true) + s:GetParent()._springing = false + s:GetParent()._expanded = true + end + + self.vbar.btnGrip.OnMouseReleased = function(s) + s:GetParent().Dragging = false + s:MouseCapture(false) + + if !(s:GetParent():IsHovered() or s:IsHovered()) then + s:GetParent()._hoverExit = CurTime() + end + end + + self.vbar.OnMousePressed = function(pnl) + local _, my = pnl:CursorPos() + local gy, gh = pnl.btnGrip.y, pnl.btnGrip:GetTall() + if my < gy then + self:_nudge(-self:GetTall()) + elseif my > gy + gh then + self:_nudge(self:GetTall()) + end + self.lastInput = CurTime() + self._springing = false + end + + function self.vbar:AnimateTo(yPos) + self:GetParent():SetScroll(yPos) + end + + function self.vbar:GetScroll() + return self:GetParent():GetScroll() + end + + self.padL, self.padT, self.padR, self.padB = 0, 0, 0, 0 + self.offset = 0 + self.vel = 0 + self.drag = false + self.dragLast = 0 + self.lastInput = 0 + + self.scrollStep = 500 + self.overscroll = 90 + self.overscrollThreshold = 50 + self.friction = 8 + self.spring = 5 + self.dragRes = 0.35 + self.gripMin = 28 + self.vbarSmooth = 3 + + self._vb_gripH = nil + self._vb_gripY = nil + self._vb_width = nil + + self._needLayout = true + + self:SetMouseInputEnabled(true) + + self._springing = false + self._springTarget = 0 +end + +function PANEL:DockPadding(l,t,r,b) + self.padL, self.padT, self.padR, self.padB = l or 0, t or 0, r or 0, b or 0 + self:_markDirty() +end + +function PANEL:_markDirty() + self._needLayout = true +end + +function PANEL:GetCanvas() + return self.content +end + +function PANEL:GetVBar() + return self.vbar +end + +function PANEL:DisableVBarPadding() + if !IsValid(self.vbar) then return end + + self._vbarPadRight = 0 + + self.vbar:DockMargin(self._vbarPadRight, 0, 0, 0) + self:_markDirty() + self:InvalidateLayout(true) + self.content:InvalidateLayout(true) +end + +function PANEL:AddItem(pnl) + pnl:SetParent(self.content) + + local old = pnl.OnSizeChanged + pnl.OnSizeChanged = function(...) + if old then pcall(old, ...) end + if IsValid(self) then + self:_markDirty() + self:InvalidateLayout(true) + self.content:InvalidateLayout(true) + self.content:SizeToChildren(false, true) + end + end + + self:_markDirty() + return pnl +end + +function PANEL:Add(pnl) + return self:AddItem(pnl) +end + +function PANEL:OnChildAdded(child) + timer.Simple(0, function() + if child == self.content or child == self.vbar or child == self.vbar.btnGrip then return end + if !IsValid(child) or !IsValid(self) then return end + if child:GetParent() == self then + child:SetParent(self.content) + + local old = child.OnSizeChanged + child.OnSizeChanged = function(...) + if old then pcall(old, ...) end + if IsValid(self) then + self:_markDirty() + self:InvalidateLayout(true) + self.content:InvalidateLayout(true) + self.content:SizeToChildren(false, true) + end + end + + self:_markDirty() + end + end) +end + +function PANEL:Clear() + for _, c in ipairs(self.content:GetChildren()) do c:Remove() end + self.offset = 0 + self.vel = 0 + self:_markDirty() +end + +function PANEL:SetScroll(y) + self.offset = y or 0 +end + +function PANEL:GetScroll() + return self.offset +end + +function PANEL:_range() + if self._needLayout then + local w, h = self:GetWide(), self:GetTall() + local vbw = self.vbar:GetWide() + + self.content:DockPadding(0, 0, 0, 0) + + local vbReserve = self.vbarReserveWidth or self.vbarExpandedWidth + + self.content:SetPos(self.padL, self.padT - self.offset) + + local contentW = math.max(0, w - self.padL - self.padR - vbReserve - self._vbarPadRight) + self.content:SetWide(contentW) + self.content:InvalidateLayout(true) + self.content:SizeToChildren(false, true) + + local viewH = math.max(0, h - self.padT - self.padB) + local contentH = self.content:GetTall() + + if contentH <= viewH then + self.vbar:SetVisible(false) + self.content:SetWide(math.max(0, w - self.padL - self.padR)) + self.content:InvalidateLayout(true) + self.content:SizeToChildren(false, true) + contentH = self.content:GetTall() + else + self.vbar:SetVisible(true) + end + + self._needLayout = false + end + + local viewH = math.max(0, self:GetTall() - self.padT - self.padB) + local contentH = self.content:GetTall() + + return math.max(0, contentH - viewH), viewH, contentH +end + +function PANEL:_nudge(px) + self.vel = self.vel + px * 10 + self.lastInput = CurTime() +end + +function PANEL:OnMouseWheeled(delta) + local _, _, contentH = self:_range() + if contentH <= 0 then return end + + self._springing = false + + self.vel = self.vel - delta * self.scrollStep + self.lastInput = CurTime() + return true +end + +function PANEL:OnMousePressed(mc) + if mc != MOUSE_LEFT then return end + + local hovered = vgui.GetHoveredPanel() + if IsValid(hovered) and hovered != self and hovered:IsDescendantOf(self.content) then return end + + self.drag = true + self.dragLast = select(2, self:CursorPos()) + self.vel = 0 + self.lastInput = CurTime() + self:MouseCapture(true) + + self._springing = false +end + +function PANEL:OnMouseReleased(mc) + if mc != MOUSE_LEFT then return end + self.drag = false + self:MouseCapture(false) + + local maxScrollDF = select(1, self:_range()) or 0 + + local extraTop = math.max(0, -self.offset) + local extraBottom = math.max(0, self.offset - maxScrollDF) + + if extraTop > self.overscrollThreshold then + self:_startSpring(0) + elseif extraBottom > self.overscrollThreshold then + self:_startSpring(maxScrollDF) + end +end + +function PANEL:OnCursorMoved(_, y) + if !self.drag then return end + + local dy = y - self.dragLast + self.dragLast = y + + local maxScrollDF = self:_range() + local next = self.offset - dy + if next < 0 then + self.offset = self.offset - dy * self.dragRes + elseif next > maxScrollDF then + self.offset = self.offset - dy * self.dragRes + else + self.offset = next + end + + self.lastInput = CurTime() +end + +function PANEL:SetVBarPaddingRight(enabled) + if !IsValid(self.vbar) then return end + + self.vbar:DockMargin(enabled and 6 or 0, 0, 0, 0) + + self:_markDirty() +end + +function PANEL:PerformLayout(w, h) + self:_markDirty() +end + +function PANEL:_startSpring(target) + self._springing = true + self._springTarget = target + self.vel = 0 +end + +function PANEL:Think() + local ft = FrameTime() + local maxScrollDF, viewH, contentH = self:_range() + + local extraTop = math.max(0, -self.offset) + local extraBottom = math.max(0, self.offset - maxScrollDF) + + if self._springing then + if CurTime() - self.lastInput < 0.02 then + self._springing = false + end + end + + if self._springing then + local t = math.min(1, ft * self.spring) + self.offset = Lerp(t, self.offset, self._springTarget) + self.vel = 0 + if math.abs(self.offset - self._springTarget) < 0.5 then + self.offset = self._springTarget + self._springing = false + end + else + if !self.drag then + self.offset = self.offset + self.vel * ft + + if self.offset < -self.overscroll then + self.offset = -self.overscroll + self.vel = 0 + elseif self.offset > maxScrollDF + self.overscroll then + self.offset = maxScrollDF + self.overscroll + self.vel = 0 + else + self.vel = self.vel * math.max(0, 1 - ft * self.friction) + if math.abs(self.vel) < 2 then self.vel = 0 end + end + + if CurTime() - self.lastInput > 0.09 and self.vel == 0 then + if extraTop > self.overscrollThreshold then + self:_startSpring(0) + elseif extraBottom > self.overscrollThreshold then + self:_startSpring(maxScrollDF) + end + end + end + end + + self.content:SetPos(self.padL, self.padT - math.floor(self.offset)) + + local vb = self.vbar + if !vb:IsVisible() then return end + + local hoveredNow = vb:IsHovered() or vb.btnGrip:IsHovered() + if hoveredNow then + if vb._hoverEnter == 0 then vb._hoverEnter = CurTime() end + vb._hoverExit = 0 + else + if vb._hoverExit == 0 then vb._hoverExit = CurTime() end + vb._hoverEnter = 0 + end + + if vb.Dragging then vb._expanded = true end + + if vb._hoverEnter > 0 and CurTime() - vb._hoverEnter >= self.vbarHoverDelay then + vb._expanded = true + end + if vb._hoverExit > 0 and CurTime() - vb._hoverExit >= self.vbarUnhoverDelay and !vb.Dragging then + vb._expanded = false + end + + local targetW = (vb._expanded and self.vbarExpandedWidth) or self.vbarDefaultWidth + if vb.Dragging then targetW = self.vbarExpandedWidth end + + if self._vb_width == nil then + self._vb_width = targetW + else + self._vb_width = Mantle.func.approachExp(self._vb_width, targetW, self.vbarWidthSpeed, ft) + if math.abs(self._vb_width - targetW) < 0.25 then self._vb_width = targetW end + end + + local newW = math.max(1, math.floor(self._vb_width)) + if vb:GetWide() != newW then + vb:SetWide(newW) + self:_markDirty() + end + + local trackH = vb:GetTall() + local clampedOffset = math.Clamp(self.offset, 0, maxScrollDF) + local ratio = (contentH <= 0) and 1 or math.min(1, viewH / contentH) + local gripH = math.max(self.gripMin, math.floor(trackH * ratio)) + local scroll01 = (maxScrollDF <= 0) and 0 or (clampedOffset / maxScrollDF) + + local topFrac = math.Clamp(extraTop / self.overscroll, 0, 1) + local bottomFrac = math.Clamp(extraBottom / self.overscroll, 0, 1) + local maxFrac = math.max(topFrac, bottomFrac) + local overscrollFrac = math.Clamp(math.max(extraTop, extraBottom) / self.overscroll, 0, 1) + + local gripRatio = gripH / math.max(1, trackH) + local weight = math.Clamp((1 - gripRatio) * 1.5, 0, 1) + + local contentToTrack = trackH / math.max(1, contentH) + local extraShift = 0 + if extraTop > 0 then extraShift = -extraTop * contentToTrack + elseif extraBottom > 0 then extraShift = extraBottom * contentToTrack end + + local proportionalY = (trackH - gripH) * scroll01 + + local desiredY = proportionalY + extraShift * weight * overscrollFrac + + if clampedOffset <= 0.001 then + desiredY = 0 + elseif maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then + desiredY = trackH - gripH + end + + local maxShrink = 0.7 + local visualGripH = gripH * (1 - maxShrink * overscrollFrac * weight) + visualGripH = math.max(6, visualGripH) + + local gripSpeed = 14 + if vb.Dragging then + local _, my = vb:CursorPos() + local newY = math.Clamp(my - vb._press_off, 0, trackH - visualGripH) + local s01 = (trackH - visualGripH) <= 0 and 0 or (newY / (trackH - visualGripH)) + self.offset = s01 * maxScrollDF + self.vel = 0 + + self._vb_gripH = visualGripH + self._vb_gripY = newY + else + if self._vb_gripH == nil then + self._vb_gripH = visualGripH + else + self._vb_gripH = Mantle.func.approachExp(self._vb_gripH, visualGripH, gripSpeed, ft) + if math.abs(self._vb_gripH - visualGripH) < 0.25 then self._vb_gripH = visualGripH end + end + + if self._vb_gripY == nil then + self._vb_gripY = desiredY + else + local speedY = gripSpeed * (1 + maxFrac * 0.5) + self._vb_gripY = Mantle.func.approachExp(self._vb_gripY, desiredY, speedY, ft) + if math.abs(self._vb_gripY - desiredY) < 0.25 then self._vb_gripY = desiredY end + end + + local maxY = math.max(0, trackH - self._vb_gripH) + if self._vb_gripY < 0 then self._vb_gripY = 0 end + if self._vb_gripY > maxY then self._vb_gripY = maxY end + + if clampedOffset <= 0.001 then + self._vb_gripY = 0 + elseif maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then + self._vb_gripY = trackH - self._vb_gripH + end + + if math.abs(self._vb_gripH - visualGripH) < 0.25 then self._vb_gripH = visualGripH end + if math.abs((self._vb_gripY or 0) - desiredY) < 0.25 then self._vb_gripY = desiredY end + end + + local finalH = math.max(1, math.floor(self._vb_gripH)) + local finalY = math.floor(math.Clamp(self._vb_gripY or 0, 0, math.max(0, trackH - finalH))) + + if clampedOffset <= 0.001 then finalY = 0 end + if maxScrollDF > 0 and clampedOffset >= maxScrollDF - 0.001 then finalY = trackH - finalH end + + vb.btnGrip:SetSize(vb:GetWide(), finalH) + vb.btnGrip:SetPos(0, finalY) +end + +vgui.Register('MantleScrollPanel', PANEL, 'EditablePanel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/slidebox.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/slidebox.lua new file mode 100644 index 0000000..3cdc68c --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/slidebox.lua @@ -0,0 +1,206 @@ +local PANEL = {} + +function PANEL:Init() + self.text = '' + self.min_value = 0 + self.max_value = 1 + self.decimals = 0 + self.convar = nil + self.value = 0 + self.smoothPos = 0 + self.targetPos = 0 + self.dragging = false + self.hover = false + self:SetTall(60) + self.OnValueChanged = function() end + self._convar_last = nil + self._convar_timer = self:CreateConVarSyncTimer() + self._dragAlpha = 255 +end + +function PANEL:CreateConVarSyncTimer() + local name = 'MantleSlideBoxSync' .. tostring(self) + timer.Create(name, 0.1, 0, function() + if not IsValid(self) or not self.convar then return end + local cvar = GetConVar(self.convar) + if not cvar then return end + local val = cvar:GetFloat() + if self._convar_last ~= val then + self._convar_last = val + self:SetValue(val, true) + end + end) + return name +end + +function PANEL:OnRemove() + if self._convar_timer then + timer.Remove(self._convar_timer) + self._convar_timer = nil + end +end + +function PANEL:SetRange(min_value, max_value, decimals) + self.min_value = min_value + self.max_value = max_value + self.decimals = decimals or 0 + self:SetValue(self.value or min_value) +end + +function PANEL:SetConvar(convar) + self.convar = convar + local cvar = GetConVar(convar) + if cvar then + self:SetValue(cvar:GetFloat(), true) + self._convar_last = cvar:GetFloat() + end +end + +function PANEL:SetText(text) + self.text = text +end + +function PANEL:SetValue(val, fromConVar) + if self.max_value == self.min_value then + val = self.min_value + else + val = math.Clamp(val, self.min_value, self.max_value) + end + + if self.decimals > 0 then + val = tonumber(string.format('%.' .. tostring(self.decimals) .. 'f', val)) or val + else + val = math.Round(val) + end + + self.value = val + local denom = (self.max_value - self.min_value) + local progress = denom == 0 and 0 or (val - self.min_value) / denom + local w = math.max(0, self:GetWide() - 32) + self.targetPos = math.Clamp(w * progress, 0, w) + if self.convar and not fromConVar then + RunConsoleCommand(self.convar, tostring(val)) + self._convar_last = val + end + if self.OnValueChanged then self:OnValueChanged(val) end +end + +function PANEL:GetValue() + return self.value +end + +function PANEL:UpdateSliderByCursorPos(x) + local w = math.max(0, self:GetWide() - 32) + local progress = math.Clamp(x / w, 0, 1) + local new_value = self.min_value + (progress * (self.max_value - self.min_value)) + if self.decimals > 0 then + new_value = tonumber(string.format('%.' .. tostring(self.decimals) .. 'f', new_value)) + else + new_value = math.Round(new_value) + end + self:SetValue(new_value) +end + +function PANEL:Paint(w, h) + local ft = FrameTime() + local padX = 16 + local padTop = 2 + local barY = 32 + local barH = 6 + local barR = barH / 2 + local handleW, handleH = 14, 14 + local handleR = handleH / 2 + local textFont = 'Fated.18' + local minmaxFont = 'Fated.14' + local valueFont = 'Fated.16' + local minmaxPadY = 12 + + -- Текст сверху + draw.SimpleText(self.text, textFont, padX, padTop, Mantle.color.text) + + -- Линия + local barStart = padX + handleW / 2 + local barEnd = w - padX - handleW / 2 + local barW = math.max(0, barEnd - barStart) + + local denom = (self.max_value - self.min_value) + local progress = denom == 0 and 0 or (self.value - self.min_value) / denom + progress = math.Clamp(progress, 0, 1) + local activeW = barW * progress + + -- Тень под линией + if Mantle.ui.convar.depth_ui then + RNDX().Rect(barStart, barY, barW, barH) + :Rad(barR) + :Color(Mantle.color.window_shadow) + :Shadow(5, 20) + :Draw() + end + + -- Фон линии + RNDX.Draw(barR, barStart, barY, barW, barH, Mantle.color.focus_panel) + RNDX.Draw(barR, barStart, barY, barW, barH, Mantle.color.button_shadow) + + -- Активная линия + self.smoothPos = Mantle.func.approachExp(self.smoothPos or 0, activeW, 14, ft) + if math.abs(self.smoothPos - activeW) < 0.5 then self.smoothPos = activeW end + + RNDX.Draw(barR, barStart, barY, self.smoothPos, barH, Mantle.color.theme) + + local handleX = barStart + self.smoothPos + local handleY = barY + barH / 2 + + -- Тень под ручкой + RNDX.DrawShadows(handleR, handleX - handleW / 2, handleY - handleH / 2, handleW, handleH, Mantle.color.window_shadow, 3, 10) + + local targetAlpha = self.dragging and 100 or 255 + self._dragAlpha = Mantle.func.approachExp(self._dragAlpha or 255, targetAlpha, 24, ft) + if math.abs(self._dragAlpha - targetAlpha) < 1 then self._dragAlpha = targetAlpha end + local colorText = Color(Mantle.color.theme.r, Mantle.color.theme.g, Mantle.color.theme.b, math.floor(self._dragAlpha)) + + -- Ручка + RNDX.Draw(handleR, handleX - handleW / 2, handleY - handleH / 2, handleW, handleH, colorText) + + -- Значение справа от линии + local valText = (self.decimals > 0) and tostring(self.value) or tostring(self.value) + draw.SimpleText(valText, valueFont, barEnd + handleW / 2 + 4, barY + barH / 2, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- min/max под линией + draw.SimpleText(self.min_value, minmaxFont, barStart, barY + barH + minmaxPadY - 4, Mantle.color.gray, TEXT_ALIGN_LEFT) + draw.SimpleText(self.max_value, minmaxFont, barEnd, barY + barH + minmaxPadY - 4, Mantle.color.gray, TEXT_ALIGN_RIGHT) +end + +function PANEL:OnMousePressed(mcode) + if mcode == MOUSE_LEFT then + local x = self:CursorPos() + self:UpdateSliderByCursorPos(x) + self.dragging = true + self:MouseCapture(true) + self.ripple_x = x + self.ripple_anim = 0 + self.ripple_active = true + end +end + +function PANEL:OnMouseReleased(mcode) + if mcode == MOUSE_LEFT then + self.dragging = false + self:MouseCapture(false) + end +end + +function PANEL:OnCursorMoved(x) + if self.dragging then + self:UpdateSliderByCursorPos(x) + end +end + +function PANEL:OnCursorEntered() + self.hover = true +end + +function PANEL:OnCursorExited() + self.hover = false +end + +vgui.Register('MantleSlideBox', PANEL, 'Panel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/table.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/table.lua new file mode 100644 index 0000000..d600ad1 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/table.lua @@ -0,0 +1,513 @@ +local PANEL = {} + +local FrameTime = FrameTime +local CurTime = CurTime +local Lerp = Lerp +local math_max = math.max +local math_floor = math.floor + +function PANEL:Init() + self.columns = {} + self.rows = {} + self.headerHeight = 36 + self.rowHeight = 32 + self.font = 'Fated.18' + self.rowFont = 'Fated.16' + self.selectedRow = nil + self.sortColumn = nil + self.sortDesc = true + self.sortState = 0 + self._originalRows = nil + self.hoverAnim = 0 + self.padding = 8 + + self.sidePadding = 12 + self.vbarRightPadding = 6 + self.vbarLeftExtra = 0 + + self.header = vgui.Create('Panel', self) + self.header:Dock(TOP) + self.header:SetTall(self.headerHeight) + + self.scrollPanel = vgui.Create('MantleScrollPanel', self) + self.scrollPanel:Dock(FILL) + self.scrollPanel:DisableVBarPadding() + + self.content = vgui.Create('Panel', self.scrollPanel) + self.content:Dock(TOP) + self.content.Paint = nil + + self._rowPanels = {} + self._headerButtons = {} + self._colWidthsTarget = {} + self._colWidthsCurrent = {} + self._lastVBarVis = nil + self._headerPrevActive = {} + + self.OnAction = function() end + self.OnRightClick = function() end + + self.Think = function() + local dt = FrameTime() + + self:UpdateColumnWidthTargets() + + for i = 1, #self.columns do + local tgt = self._colWidthsTarget[i] or (self.columns[i] and self.columns[i].width or 100) + self._colWidthsCurrent[i] = Mantle.func.approachExp(self._colWidthsCurrent[i] or tgt, tgt, 20, dt) + end + + local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0) + local x = leftPad + for i, btn in ipairs(self._headerButtons) do + local w = math_floor(self._colWidthsCurrent[i] or (self.columns[i] and self.columns[i].width or 100)) + if IsValid(btn) then + btn:SetSize(w, self.headerHeight) + btn:SetPos(x, 0) + end + x = x + w + end + + local total = 0 + for i = 1, #self.columns do total = total + (self._colWidthsCurrent[i] or self.columns[i].width) end + + local panelW = self:GetWide() or 0 + if panelW <= 0 then + local par = self:GetParent() + if IsValid(par) and par.GetWide then panelW = par:GetWide() end + end + if panelW <= 0 then panelW = ScrW() end + + local rightPad = self.sidePadding + ((self._lastVBarVis and self.vbarRightPadding) or 0) + local contentW = math_max(total + leftPad + rightPad, panelW) + + for _, row in ipairs(self._rowPanels) do + if IsValid(row) and row._labels then + local x2 = leftPad + local hv = row._hoverAlpha or 0 + local eased = Mantle.func.easeOutCubic(math.Clamp(hv, 0, 1)) + local shift = math_floor(6 * eased) + for i, label in ipairs(row._labels) do + local w = math_floor(self._colWidthsCurrent[i] or (self.columns[i] and self.columns[i].width or 100)) + if IsValid(label) then + local dx = 0 + if i == 1 then + dx = shift + elseif i == #self.columns then + dx = -shift + end + label:SetSize(w, self.rowHeight) + label:SetPos(x2 + dx, 0) + end + x2 = x2 + w + end + row:SetWide(contentW) + end + end + + self.content:SetWide(contentW) + end +end + +function PANEL:AddColumn(name, width, align, sortable) + table.insert(self.columns, { + name = name, + width = width or 100, + align = align or TEXT_ALIGN_LEFT, + sortable = sortable or false + }) +end + +function PANEL:AddItem(...) + local args = {...} + if #args != #self.columns then + print(Mantle.lang.get('mantle', 'table_wrong_args')) + return + end + + table.insert(self.rows, args) + self:RebuildRows() + return #self.rows +end + +local function getValueType(value) + if value == nil then return 'nil' end + value = tostring(value) + return tonumber(value) and 'number' or 'string' +end + +local function compareValues(a, b) + if a == nil and b == nil then return false end + if a == nil then return true end + if b == nil then return false end + + local typeA = getValueType(a) + local typeB = getValueType(b) + + if typeA != typeB then + return typeA < typeB + end + + if typeA == 'number' then + local numA = tonumber(a) or 0 + local numB = tonumber(b) or 0 + return numA > numB + else + local strA = tostring(a) + local strB = tostring(b) + return strA < strB + end +end + +local function cloneRows(tbl) + local out = {} + for i, v in ipairs(tbl) do out[i] = v end + return out +end + +function PANEL:SortByColumn(columnIndex) + local column = self.columns[columnIndex] + if !column or !column.sortable then return end + + if self.sortColumn != columnIndex then + self.sortColumn = columnIndex + + local numCount, total = 0, 0 + for _, row in ipairs(self.rows) do + local v = row[columnIndex] + if v != nil then + total = total + 1 + if tonumber(tostring(v)) then numCount = numCount + 1 end + end + end + + local isNumeric = (total > 0 and numCount >= math.ceil(total / 2)) + self.sortDesc = isNumeric + else + self.sortDesc = !self.sortDesc + end + + local desc = self.sortDesc + + table.sort(self.rows, function(a, b) + local va = a[columnIndex] + local vb = b[columnIndex] + + if va == nil and vb == nil then return false end + if va == nil then return !desc end + if vb == nil then return desc end + + local sa = tostring(va) + local sb = tostring(vb) + + local na = tonumber(sa) + local nb = tonumber(sb) + + if na and nb then + if desc then + return na > nb + else + return na < nb + end + end + + if na and !nb then + return desc + elseif nb and !na then + return !desc + end + + local la = string.lower(sa) + local lb = string.lower(sb) + if desc then + return la > lb + else + return la < lb + end + end) + + self:RebuildRows() +end + +function PANEL:UpdateColumnWidthTargets() + local cols = self.columns + local n = #cols + if n == 0 then return end + + local panelW = self:GetWide() or 0 + if (!panelW) or panelW <= 0 then + local parent = self:GetParent() + if IsValid(parent) and parent.GetWide then panelW = parent:GetWide() end + end + if (!panelW) or panelW <= 0 then panelW = ScrW() end + + local vbar = (IsValid(self.scrollPanel) and self.scrollPanel.GetVBar) and self.scrollPanel:GetVBar() or nil + local vbarVisible = (IsValid(vbar) and vbar:IsVisible()) + local vbarW = (vbarVisible and vbar:GetWide() or 0) + + local leftPad = self.sidePadding + (vbarVisible and self.vbarLeftExtra or 0) + local rightPad = self.sidePadding + (vbarVisible and self.vbarRightPadding or 0) + + local usable = math_max(0, panelW - leftPad - rightPad - vbarW) + + local used = 0 + for i = 1, math.max(0, n - 1) do + self._colWidthsTarget[i] = cols[i].width or 100 + used = used + self._colWidthsTarget[i] + end + + local lastMin = cols[n].width or 100 + local remaining = usable - used + if remaining < lastMin then remaining = lastMin end + self._colWidthsTarget[n] = remaining + + for i = 1, n do + if self._colWidthsCurrent[i] == nil then + self._colWidthsCurrent[i] = self._colWidthsTarget[i] + end + end + + self._lastVBarVis = vbarVisible +end + +function PANEL:CreateHeader() + local prev = {} + for i, btn in ipairs(self._headerButtons) do + if IsValid(btn) then prev[i] = btn._activeAlpha or 0 end + end + + self.header:Clear() + self._headerButtons = {} + + self.header.Paint = function(_, w, h) + RNDX().Rect(0, 0, w, h) + :Radii(16, 16, 0, 0) + :Color(Mantle.color.focus_panel) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + self:UpdateColumnWidthTargets() + + local xPos = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0) + for i, column in ipairs(self.columns) do + local w = math_floor(self._colWidthsCurrent[i] or self._colWidthsTarget[i] or column.width) + local label = vgui.Create('DButton', self.header) + label:SetText('') + label:SetSize(w, self.headerHeight) + label:SetPos(xPos, 0) + label._hover = 0 + label._activeAlpha = prev[i] or ((self.sortColumn == i) and 1 or 0) + + label.Paint = function(s, bw, bh) + local dt = FrameTime() + local target = s:IsHovered() and 1 or 0 + s._hover = Mantle.func.approachExp(s._hover or 0, target, 14, dt) + local activeTarget = (self.sortColumn == i) and 1 or 0 + s._activeAlpha = Mantle.func.approachExp(s._activeAlpha or 0, activeTarget, 12, dt) + + local tr = Lerp(s._activeAlpha, Mantle.color.text.r, Mantle.color.theme.r) + local tg = Lerp(s._activeAlpha, Mantle.color.text.g, Mantle.color.theme.g) + local tb = Lerp(s._activeAlpha, Mantle.color.text.b, Mantle.color.theme.b) + local ta = Lerp(s._activeAlpha, Mantle.color.text.a, Mantle.color.theme.a) + local textColor = Color(math_floor(tr), math_floor(tg), math_floor(tb), math_floor(ta)) + + draw.SimpleText(column.name, self.font, bw/2, bh/2, textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + if column.sortable then + label.DoClick = function() + self:SortByColumn(i) + Mantle.func.sound() + end + end + + table.insert(self._headerButtons, label) + xPos = xPos + w + end +end + +function PANEL:CreateRow(rowIndex, rowData) + local row = vgui.Create('DButton', self.content) + row:Dock(TOP) + row:DockMargin(0, 0, 0, 1) + row:SetTall(self.rowHeight) + row:SetText('') + + row._index = rowIndex + row._hoverAlpha = 0 + row._selectedAlpha = 0 + row._labels = {} + + row.Paint = function(s, w, h) + local dt = FrameTime() + local hoverTarget = s:IsHovered() and 1 or 0 + s._hoverAlpha = Mantle.func.approachExp(s._hoverAlpha, hoverTarget, 18, dt) + local selTarget = (self.selectedRow == s._index) and 1 or 0 + s._selectedAlpha = Mantle.func.approachExp(s._selectedAlpha, selTarget, 22, dt) + + local base = Mantle.color.panel_alpha[1] + local hoverCol = Mantle.color.hover + local selCol = Mantle.color.theme + + local mixHover = s._hoverAlpha * (1 - s._selectedAlpha) + local blendA = s._selectedAlpha * 0.9 + mixHover * 0.35 + + local r = Lerp(blendA, base.r, selCol.r) + local g = Lerp(blendA, base.g, selCol.g) + local b = Lerp(blendA, base.b, selCol.b) + local a = Lerp(blendA, base.a, selCol.a) + + if s._hoverAlpha > 0.01 and s._selectedAlpha < 0.9 then + local hoverR = Lerp(s._hoverAlpha * 0.6, r, hoverCol.r) + local hoverG = Lerp(s._hoverAlpha * 0.6, g, hoverCol.g) + local hoverB = Lerp(s._hoverAlpha * 0.6, b, hoverCol.b) + r,g,b = hoverR, hoverG, hoverB + end + + RNDX().Rect(0, 0, w, math.max(0, h - 1)) + :Color(Color(math.floor(r), math.floor(g), math.floor(b), math.floor(a))) + :Shape(RNDX.SHAPE_IOS) + :Draw() + end + + row.DoClick = function() + self.selectedRow = rowIndex + self._keyboardIndex = rowIndex + self.OnAction(rowData) + Mantle.func.sound() + end + + row.DoRightClick = function() + self.selectedRow = rowIndex + self.OnRightClick(rowData) + local menu = Mantle.ui.derma_menu() + for i, column in ipairs(self.columns) do + menu:AddOption(Mantle.lang.get('mantle', 'table_copy') .. ' ' .. column.name, function() + SetClipboardText(tostring(rowData[i])) + end) + end + menu:AddSpacer() + menu:AddOption(Mantle.lang.get('mantle', 'table_delete_row'), function() + self:RemoveRow(rowIndex) + end, 'icon16/delete.png') + end + + local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0) + local xPos = leftPad + for i, column in ipairs(self.columns) do + local w = math_floor(self._colWidthsCurrent[i] or self._colWidthsTarget[i] or column.width) + local label = vgui.Create('DLabel', row) + label:SetText(tostring(rowData[i])) + label:SetFont(self.rowFont) + label:SetTextColor(Mantle.color.text) + label:SetSize(w, self.rowHeight) + label:SetPos(xPos, 0) + + if column.align == TEXT_ALIGN_LEFT then + label:SetTextInset(self.padding, 0) + label:SetContentAlignment(4) + elseif column.align == TEXT_ALIGN_RIGHT then + label:SetTextInset(0, 0) + label:SetContentAlignment(6) + else + label:SetTextInset(0, 0) + label:SetContentAlignment(5) + end + + table.insert(row._labels, label) + xPos = xPos + w + end + + table.insert(self._rowPanels, row) +end + +function PANEL:RebuildRows() + local savedRowHover = {} + for idx, oldRow in ipairs(self._rowPanels) do + if IsValid(oldRow) then + savedRowHover[idx] = oldRow._hoverAlpha or 0 + end + end + + local prevHeader = {} + for i, btn in ipairs(self._headerButtons) do + if IsValid(btn) then prevHeader[i] = btn._activeAlpha or 0 end + end + + self.content:Clear() + self._rowPanels = {} + self._headerButtons = {} + + self:UpdateColumnWidthTargets() + + self:CreateHeader() + + for rowIndex, rowData in ipairs(self.rows) do + self:CreateRow(rowIndex, rowData) + if savedRowHover[rowIndex] and IsValid(self._rowPanels[#self._rowPanels]) then + self._rowPanels[#self._rowPanels]._hoverAlpha = savedRowHover[rowIndex] + end + end + + local total = 0 + for i = 1, #self.columns do total = total + (self._colWidthsTarget[i] or self.columns[i].width) end + + local panelW = self:GetWide() or 0 + if panelW <= 0 then + local parent = self:GetParent() + if IsValid(parent) and parent.GetWide then panelW = parent:GetWide() end + end + if panelW <= 0 then panelW = ScrW() end + + local leftPad = self.sidePadding + ((self._lastVBarVis and self.vbarLeftExtra) or 0) + local rightPad = self.sidePadding + ((self._lastVBarVis and self.vbarRightPadding) or 0) + local contentW = math_max(total + leftPad + rightPad, panelW) + + self.content:SetSize(contentW, #self.rows * (self.rowHeight + 1)) + self.scrollPanel:InvalidateLayout(true) +end + +function PANEL:SetAction(func) + self.OnAction = func +end +function PANEL:SetRightClickAction(func) + self.OnRightClick = func +end + +function PANEL:Clear() + self.rows = {} + self.selectedRow = nil + self.content:Clear() +end + +function PANEL:GetSelectedRow() + return self.selectedRow and self.rows[self.selectedRow] or nil +end + +function PANEL:GetRowCount() + return #self.rows +end + +function PANEL:RemoveRow(index) + if index and index > 0 and index <= #self.rows then + table.remove(self.rows, index) + if self.selectedRow == index then + self.selectedRow = nil + elseif self.selectedRow and self.selectedRow > index then + self.selectedRow = self.selectedRow - 1 + end + self:RebuildRows() + self.scrollPanel:InvalidateLayout(true) + end +end + +function PANEL:Paint(w, h) + RNDX().Rect(0, 0, w, h) + :Rad(16) + :Color(Mantle.color.panel_alpha[2]) + :Shape(RNDX.SHAPE_IOS) + :Draw() +end + +vgui.Register('MantleTable', PANEL, 'Panel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/tabs.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/tabs.lua new file mode 100644 index 0000000..7dcceb0 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/tabs.lua @@ -0,0 +1,196 @@ +local PANEL = {} + +function PANEL:Init() + self.tabs = {} + self.active_id = 1 + self.tab_height = 38 + self.animation_speed = 12 + self.tab_style = 'modern' -- modern или classic + self.indicator_height = 2 + + self.indicator_x = 0 + self.indicator_w = 0 + self.indicator_target_x = 0 + self.indicator_target_w = 0 + + self.panel_tabs = vgui.Create('Panel', self) + self.panel_tabs.Paint = nil + + self.content = vgui.Create('Panel', self) + self.content.Paint = nil +end + +function PANEL:Think() + if self.tab_style == 'modern' then + self.indicator_x = Mantle.func.approachExp(self.indicator_x, self.indicator_target_x, self.animation_speed, FrameTime()) + self.indicator_w = Mantle.func.approachExp(self.indicator_w, self.indicator_target_w, self.animation_speed, FrameTime()) + if math.abs(self.indicator_x - self.indicator_target_x) < 0.5 then + self.indicator_x = self.indicator_target_x + end + if math.abs(self.indicator_w - self.indicator_target_w) < 0.5 then + self.indicator_w = self.indicator_target_w + end + end +end + +function PANEL:SetTabStyle(style) + self.tab_style = style + self:Rebuild() +end + +function PANEL:SetTabHeight(height) + self.tab_height = height + self:Rebuild() +end + +function PANEL:SetIndicatorHeight(height) + self.indicator_height = height + self:Rebuild() +end + +function PANEL:AddTab(name, pan, icon) + local newId = #self.tabs + 1 + + self.tabs[newId] = { + name = name, + pan = pan, + icon = icon + } + + self.tabs[newId].pan:SetParent(self.content) + self.tabs[newId].pan:Dock(FILL) + self.tabs[newId].pan:SetVisible(newId == 1 and true or false) + + self:Rebuild() +end + +local color_btn_hovered = Color(255, 255, 255, 10) + +function PANEL:Rebuild() + self.panel_tabs:Clear() + + for id, tab in ipairs(self.tabs) do + local btnTab = vgui.Create('Button', self.panel_tabs) + tab._btn = btnTab + if self.tab_style == 'modern' then + surface.SetFont('Fated.18') + local textW = select(1, surface.GetTextSize(tab.name)) + local iconW = tab.icon and 16 or 0 + local iconTextGap = tab.icon and 8 or 0 + local padding = 16 + local btnWidth = padding + iconW + iconTextGap + textW + padding + btnTab:Dock(LEFT) + btnTab:DockMargin(0, 0, 6, 0) + btnTab:SetTall(34) + btnTab:SetWide(btnWidth) + else + btnTab:Dock(TOP) + btnTab:DockMargin(0, 0, 0, 6) + btnTab:SetTall(34) + end + + btnTab:SetText('') + btnTab.DoClick = function() + self.tabs[self.active_id].pan:SetVisible(false) + tab.pan:SetVisible(true) + self.active_id = id + if self.tab_style == 'modern' and tab._btn then + self.indicator_target_x = tab._btn:GetX() + self.indicator_target_w = tab._btn:GetWide() + end + Mantle.func.sound() + end + btnTab.DoRightClick = function() + local dm = Mantle.ui.derma_menu() + for k, tab in pairs(self.tabs) do + dm:AddOption(tab.name, function() + self.tabs[self.active_id].pan:SetVisible(false) + tab.pan:SetVisible(true) + self.active_id = k + if self.tab_style == 'modern' and tab._btn then + self.indicator_target_x = tab._btn:GetX() + self.indicator_target_w = tab._btn:GetWide() + end + end, tab.icon) + end + end + + btnTab.Paint = function(s, w, h) + local isActive = self.active_id == id + local colorText = isActive and Mantle.color.theme or Mantle.color.text + local colorIcon = isActive and Mantle.color.theme or color_white + + if self.tab_style == 'modern' then + if s:IsHovered() then + RNDX.Draw(16, 0, 0, w, h, color_btn_hovered, RNDX.SHAPE_IOS + (isActive and RNDX.NO_BL + RNDX.NO_BR or 0)) + end + + local padding = 16 + local iconW = tab.icon and 16 or 0 + local iconTextGap = tab.icon and 8 or 0 + local textX = padding + (iconW > 0 and (iconW + iconTextGap) or 0) + + if tab.icon then + RNDX.DrawMaterial(0, padding, (h - 16) * 0.5, 16, 16, colorIcon, tab.icon) + end + + draw.SimpleText(tab.name, 'Fated.18', textX, h * 0.5, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + else + if s:IsHovered() then + RNDX.Draw(24, 0, 0, w, h, color_btn_hovered, RNDX.SHAPE_IOS) + end + + draw.SimpleText(tab.name, 'Fated.18', 34, h * 0.5 - 1, colorText, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + if tab.icon then + RNDX.DrawMaterial(0, 9, 9, 16, 16, colorIcon, tab.icon) + else + RNDX.Draw(24, 9, 9, 16, 16, colorIcon, RNDX.SHAPE_IOS) + end + end + end + end + + self.panel_tabs.Paint = function(s, w, h) + if self.tab_style == 'modern' and self.indicator_w > 0 then + RNDX.Draw(0, self.indicator_x, h - self.indicator_height, self.indicator_w, self.indicator_height, Mantle.color.theme) + end + end +end + +function PANEL:PerformLayout(w, h) + if self.tab_style == 'modern' then + self.panel_tabs:Dock(TOP) + self.panel_tabs:DockMargin(0, 0, 0, 4) + self.panel_tabs:SetTall(self.tab_height) + else + self.panel_tabs:Dock(LEFT) + self.panel_tabs:DockMargin(0, 0, 4, 0) + self.panel_tabs:SetWide(190) + end + + self.content:Dock(FILL) + + if self.tab_style == 'modern' then + local activeBtn = nil + if self.tabs[self.active_id] then + activeBtn = self.tabs[self.active_id]._btn + end + + if IsValid(activeBtn) then + local bx, by = activeBtn:GetPos() + local bw, bh = activeBtn:GetSize() + self.indicator_target_x = bx + self.indicator_target_w = bw + if self.indicator_w == 0 and self.indicator_x == 0 then + self.indicator_x = self.indicator_target_x + self.indicator_w = self.indicator_target_w + end + else + self.indicator_target_x = 0 + self.indicator_target_w = 0 + end + end +end + +vgui.Register('MantleTabs', PANEL, 'Panel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/text.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/text.lua new file mode 100644 index 0000000..4e1a38f --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/text.lua @@ -0,0 +1,257 @@ +local PANEL = {} + +local function utf8_iter(s) + return s:gmatch('([%z\1-\127\194-\244][\128-\191]*)') +end + +function PANEL:Init() + self.text = '' + self.font = 'Fated.18' + self.color = Mantle.color.text + self.align = TEXT_ALIGN_LEFT + self.valign = 'top' + self.padding = 6 + + self._lines = {''} + self._line_h = 16 + self._last_w, self._last_h = 0, 0 + + self:SetMouseInputEnabled(false) + self:SetKeyboardInputEnabled(false) +end + +function PANEL:SetText(text) + self.text = text + self:InvalidateLayout() +end + +function PANEL:GetText() + return self.text +end + +function PANEL:SetFont(font) + self.font = font + self:InvalidateLayout() +end + +function PANEL:SetColor(col) + self.color = col + self:InvalidateLayout() +end + +function PANEL:SetAlign(a) + self.align = a + self:InvalidateLayout() +end + +function PANEL:SetVAlign(v) + if v == 'top' or v == 'center' or v == 'bottom' then + self.valign = v + self:InvalidateLayout() + end +end + +function PANEL:SetPadding(p) + self.padding = p + self:InvalidateLayout() +end + +local function GetTextSize(font, txt) + surface.SetFont(font) + local ok, w, h = pcall(surface.GetTextSize, txt) + if not ok then return 0, 16 end + if not h or type(h) != 'number' or h <= 0 then + local ok2, _, h2 = pcall(surface.GetTextSize, 'Ay') + if ok2 and type(h2) == 'number' and h2 > 0 then + h = h2 + else + h = 16 + end + end + return tonumber(w) or 0, h +end + +local function WrapAndEllipsize(text, font, maxw, max_lines) + if maxw <= 0 or max_lines <= 0 then + return {''}, true + end + + local ell = '...' + local ell_w = GetTextSize(font, ell) + local paragraphs = string.Explode('\n', text) + local lines = {} + local truncated = false + + for pi = 1, #paragraphs do + local para = paragraphs[pi] + + if para == '' then + table.insert(lines, '') + if #lines >= max_lines then + truncated = (pi < #paragraphs) + break + end + else + local words = string.Explode(' ', para) + local i = 1 + local cur = '' + + while i <= #words do + local w = words[i] + local test = (cur == '') and w or (cur .. ' ' .. w) + local tw = GetTextSize(font, test) + if tw <= maxw then + cur = test + i = i + 1 + else + if cur != '' then + table.insert(lines, cur) + cur = '' + if #lines >= max_lines then break end + else + local part = '' + + for ch in utf8_iter(w) do + local tpart = part .. ch + local tw2 = GetTextSize(font, tpart) + if tw2 <= maxw then + part = tpart + else + if part == '' then + local ch_w = GetTextSize(font, ch) + if ch_w <= maxw then + table.insert(lines, ch) + else + table.insert(lines, ch) + end + else + table.insert(lines, part) + local ch_w = GetTextSize(font, ch) + if ch_w <= maxw then + part = ch + else + table.insert(lines, ch) + part = '' + end + end + part = part or '' + + if #lines >= max_lines then break end + end + end + if #lines >= max_lines then break end + cur = part + i = i + 1 + end + end + end + + if #lines < max_lines and cur != '' then + table.insert(lines, cur) + end + + if #lines >= max_lines then + if i <= #words or pi < #paragraphs then + truncated = true + end + + if truncated then + local rest = '' + if i <= #words then + for j = i, #words do + rest = rest .. words[j] .. (j < #words and ' ' or '') + end + end + if pi < #paragraphs then + for pj = pi + 1, #paragraphs do + if paragraphs[pj] != '' then + rest = rest .. (rest != '' and ' ' or '') .. paragraphs[pj] + end + end + end + + if #lines == 0 then + lines[1] = '' + end + + local lastIdx = #lines + local prev = lines[lastIdx] or '' + if rest == '' then + rest = prev + else + rest = prev .. (rest != '' and (' ' .. rest) or '') + end + + local res = '' + for ch in utf8_iter(rest) do + local t = res .. ch + local tw = GetTextSize(font, t) + if tw + ell_w <= maxw then + res = t + else + break + end + end + lines[lastIdx] = (res == '' and ell) or (res .. ell) + end + + break + end + end + end + + if #lines == 0 then lines[1] = '' end + return lines, truncated +end + +function PANEL:_rebuild_if_needed() + local w, h = self:GetSize() + if w == self._last_w and h == self._last_h then return end + self._last_w, self._last_h = w, h + + local avail_w_df = math.max(1, w - self.padding * 2) + local _, line_h = GetTextSize(self.font, 'Ay') + self._line_h = line_h or 16 + + local max_lines = math.max(1, math.floor((h - self.padding * 2) / self._line_h)) + local lines, trunc = WrapAndEllipsize(self.text, self.font, avail_w_df, max_lines) + self._lines = lines + self._truncated = trunc +end + +function PANEL:PerformLayout(w, h) + self:_rebuild_if_needed() +end + +function PANEL:Paint(w, h) + self:_rebuild_if_needed() + local lines = self._lines or {''} + local line_h = self._line_h or 16 + local total_h = #lines * line_h + + local start_y = self.padding + if self.valign == 'center' then + start_y = math.floor((h - total_h) / 2) + elseif self.valign == 'bottom' then + start_y = h - self.padding - total_h + end + + surface.SetFont(self.font) + for i = 1, #lines do + local line = lines[i] + local y = start_y + (i - 1) * line_h + + local x = self.padding + if self.align == TEXT_ALIGN_CENTER then + x = w * 0.5 + elseif self.align == TEXT_ALIGN_RIGHT then + x = w - self.padding + end + + draw.SimpleText(line, self.font, x, y, self.color, self.align, TEXT_ALIGN_TOP) + end + + return true +end + +vgui.Register('MantleText', PANEL, 'EditablePanel') diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/textbox.lua b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/textbox.lua new file mode 100644 index 0000000..c45261d --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/core/vgui_elements/textbox.lua @@ -0,0 +1,35 @@ +local color_accept = Color(35, 103, 51) + +function Mantle.ui.text_box(title, desc, func) + Mantle.ui.menu_text_box = vgui.Create('MantleFrame') + Mantle.ui.menu_text_box:SetSize(300, 134) + Mantle.ui.menu_text_box:Center() + Mantle.ui.menu_text_box:MakePopup() + Mantle.ui.menu_text_box:SetTitle(title) + Mantle.func.animate_appearance(Mantle.ui.menu_text_box, Mantle.ui.menu_text_box:GetWide(), Mantle.ui.menu_text_box:GetTall(), 0.3, 0.2, nil, 0.9) + Mantle.ui.menu_text_box:DockPadding(12, 30, 12, 12) + + local entry = vgui.Create('MantleEntry', Mantle.ui.menu_text_box) + entry:Dock(TOP) + entry:SetTitle(desc) + + local function apply_func() + func(entry:GetValue()) + + Mantle.ui.menu_text_box:Remove() + end + + entry.OnEnter = function() + apply_func() + end + + local btn_accept = vgui.Create('MantleBtn', Mantle.ui.menu_text_box) + btn_accept:Dock(BOTTOM) + btn_accept:SetTall(30) + btn_accept:SetTxt(Mantle.lang.get('mantle', 'apply')) + btn_accept:SetColorHover(color_accept) + btn_accept.DoClick = function() + Mantle.func.sound() + apply_func() + end +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/init.lua b/addons/mantle_darkfated_ultracode/lua/mantle/init.lua new file mode 100644 index 0000000..7c8974d --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/init.lua @@ -0,0 +1,82 @@ +--[[ + * Mantle * + GitHub: https://github.com/darkfated/mantle + Author's telegram: @darkfated +]]-- + +local function RunScripts() + Mantle.run_cl('config/colors.lua') + + Mantle.run_cl('core/func.lua') + Mantle.run_cl('core/vgui.lua') + Mantle.run_cl('core/legacy_vgui.lua') + Mantle.run_cl('core/menu.lua') + + Mantle.run_cl('core/vgui_elements/button.lua') + Mantle.run_cl('core/vgui_elements/checkbox.lua') + Mantle.run_cl('core/vgui_elements/color_picker.lua') + Mantle.run_cl('core/vgui_elements/derma_menu.lua') + Mantle.run_cl('core/vgui_elements/entry.lua') + Mantle.run_cl('core/vgui_elements/frame.lua') + Mantle.run_cl('core/vgui_elements/player_selector.lua') + Mantle.run_cl('core/vgui_elements/radialpanel.lua') + Mantle.run_cl('core/vgui_elements/scrollpanel.lua') + Mantle.run_cl('core/vgui_elements/slidebox.lua') + Mantle.run_cl('core/vgui_elements/tabs.lua') + Mantle.run_cl('core/vgui_elements/textbox.lua') + Mantle.run_cl('core/vgui_elements/category.lua') + Mantle.run_cl('core/vgui_elements/combobox.lua') + Mantle.run_cl('core/vgui_elements/table.lua') + Mantle.run_cl('core/vgui_elements/text.lua') + + Mantle.run_cl('modules/shadows.lua') + Mantle.run_cl('modules/material_url.lua') + Mantle.run_sh('modules/notify.lua') + Mantle.run_sh('modules/utf8.lua') +end + +local function RunAddons() + local _, addonsName = file.Find('mantle_addons/*', 'LUA') + + for _, addon in ipairs(addonsName) do + if file.Exists('mantle_addons/' .. addon .. '/init.lua', 'LUA') then + Mantle.run_sh('mantle_addons/' .. addon .. '/init.lua') + end + + if file.Exists('mantle_addons/' .. addon .. '/lang.lua', 'LUA') then + local lang = Mantle.run_sh('mantle_addons/' .. addon .. '/lang.lua') + Mantle.lang.list[addon] = lang + end + end + + Mantle.run_sh('core/lang.lua') +end + +local function InitLib() + if SERVER then + resource.AddWorkshop('2924839375') -- DarkFated font + resource.AddWorkshop('3126986993') -- Mantle + end + + local color_div = Color(168, 109, 236) + + MsgC(color_white, '------------------\n') + MsgC(Color(0, 255, 0), '| Mantle LIBRARY |\n') + MsgC(color_white, '------------------\n') + + Mantle = Mantle or { + lang = { list = {}, default = 'en' }, + } + Mantle.run_cl = SERVER and AddCSLuaFile or include + Mantle.run_sv = SERVER and include or function() end + Mantle.run_sh = function(f) + local client = Mantle.run_cl(f) + local server = Mantle.run_sv(f) + return SERVER and server or client + end + + RunScripts() + RunAddons() +end + +InitLib() diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/modules/material_url.lua b/addons/mantle_darkfated_ultracode/lua/mantle/modules/material_url.lua new file mode 100644 index 0000000..fe90587 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/modules/material_url.lua @@ -0,0 +1,40 @@ +local file, Mat, Fetch, find = file, Material, http.Fetch, string.find +local errorMat = Mat('error') +local WebImageCache = {} + +--[[ + Функция для скачивания материала по ссылке и его кэшированного использования +]]-- +function http.DownloadMaterial(url, path, callback, retry_count) + if WebImageCache[url] then + return callback(WebImageCache[url]) + end + + local dataPath = 'data/' .. path + + if file.Exists(path, 'DATA') then + WebImageCache[url] = Mat(dataPath, 'noclamp mips') + + callback(WebImageCache[url]) + else + Fetch(url, function(img) + if !img or find(img, '', 1, true) then + return callback(errorMat) + end + + file.Write(path, img) + + WebImageCache[url] = Mat(dataPath, 'noclamp mips') + + callback(WebImageCache[url]) + end, function() + if retry_count and retry_count > 0 then + retry_count = retry_count - 1 + + http.DownloadMaterial(url, path, callback, retry_count) + else + callback(errorMat) + end + end) + end +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/modules/notify.lua b/addons/mantle_darkfated_ultracode/lua/mantle/modules/notify.lua new file mode 100644 index 0000000..72b0033 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/modules/notify.lua @@ -0,0 +1,25 @@ +if SERVER then + util.AddNetworkString('Mantle-Notify') + + --[[ + Функция для выведения в чат текста. + Можно выводить определённой цели информацию, либо всем, указав вместо pl - true + ]]-- + function Mantle.notify(pl, header_color, header, text) + net.Start('Mantle-Notify') + net.WriteString(header) + net.WriteColor(header_color) + net.WriteString(text) + if pl == true then net.Broadcast() else net.Send(pl) end + end +else + net.Receive('Mantle-Notify', function() + local headerText = net.ReadString() + local headerColor = net.ReadColor() + local headerColorDop = Color(headerColor.r + 10, headerColor.g + 10, headerColor.b + 10) + local text = net.ReadString() + + chat.AddText(headerColorDop, '[', headerColor, headerText, headerColorDop, '] ', color_white, text) + chat.PlaySound() + end) +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/modules/rndx.lua b/addons/mantle_darkfated_ultracode/lua/mantle/modules/rndx.lua new file mode 100644 index 0000000..e598a8b --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/modules/rndx.lua @@ -0,0 +1,691 @@ +--[[ +Copyright (c) 2025 Srlion (https://github.com/Srlion) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] + +if SERVER then + AddCSLuaFile() + return +end + +local bit_band = bit.band +local surface_SetDrawColor = surface.SetDrawColor +local surface_SetMaterial = surface.SetMaterial +local surface_DrawTexturedRectUV = surface.DrawTexturedRectUV +local surface_DrawTexturedRect = surface.DrawTexturedRect +local render_CopyRenderTargetToTexture = render.CopyRenderTargetToTexture +local math_min = math.min +local math_max = math.max +local DisableClipping = DisableClipping +local type = type + +local SHADERS_VERSION = "1757877956" +local SHADERS_GMA = [========[R01BRAOHS2tdVNwrAMQWx2gAAAAAAFJORFhfMTc1Nzg3Nzk1NgAAdW5rbm93bgABAAAAAQAAAHNoYWRlcnMvZnhjLzE3NTc4Nzc5NTZfcm5keF9yb3VuZGVkX2JsdXJfcHMzMC52Y3MAUAUAAAAAAAAAAAAAAgAAAHNoYWRlcnMvZnhjLzE3NTc4Nzc5NTZfcm5keF9yb3VuZGVkX3BzMzAudmNzADQEAAAAAAAAAAAAAAMAAABzaGFkZXJzL2Z4Yy8xNzU3ODc3OTU2X3JuZHhfc2hhZG93c19ibHVyX3BzMzAudmNzADYFAAAAAAAAAAAAAAQAAABzaGFkZXJzL2Z4Yy8xNzU3ODc3OTU2X3JuZHhfc2hhZG93c19wczMwLnZjcwDeAwAAAAAAAAAAAAAFAAAAc2hhZGVycy9meGMvMTc1Nzg3Nzk1Nl9ybmR4X3ZlcnRleF92czMwLnZjcwAeAQAAAAAAAAAAAAAAAAAABgAAAAEAAAABAAAAAAAAAAAAAAACAAAAHUcBbAAAAAAwAAAA/////1AFAAAAAAAAGAUAQExaTUG0DgAABwUAAF0AAAABAABoqV8kgL/sqj/+eCjfxRdm72ukxxrZJOmY5BiSff6UK8jKnQg0wmy60gGA6OIVrm+AZ/lvb8Ywy3K8LU+BJPZn395onULJrRD4M/GDQNqeVSGshmtApEeReU+ZTtlBcM3KgMP5kNHFcYeMjOP18v1rXRkhTnsRXCivQkjpG0AzOenhnTzSeUk0VRjyYUnN3TMr2QcLKyqCwWb6m/Fs7nXcrvFthAwSs0ciBXYmrkwlQ310qhdU+A7QyOJg9+a4osRtdsSFsU0kDnqfMCg3LJ/xPGbKLgrBp9Gp9WHeJZlAkxwGefkRNGJxCIQHLe/mMKU3/zoj0lpzNB+tDMSouHs1pc4Tao0Vnw7+gilRptrVd106Cc9HdUId8tlzu3EUSh75xRLQ/LkyqbgLeHg6VjD9cWcx8Fdq1e3Icg6ut5v0rg30grbcJQU4teRPS4Wf5+1qeYTID52pLXIKqTBQGZtYOuSjbA8roO5AKZw7hBirqZ8H4WC7dSmHudrAvjtPeVPjOpABK3Q+N+KPu97KER7zTZMx9Uwmtb5yXpTSpKsuRX03kZxlL1bi4l8GF/2zPP1barOH4ZWuC4c+l/N+/naMPMfTau5LXAMg0FTc23AFYG1D0/BRWSIueZ8BeyFkoOL12W2I9Kvoga0GYSKR9rSnQdG9RkIFf0UXv8PYoESenIWvFLY7dFuzqNeJUXT4U0KKswIb5OLisV5vjTS/KZCkvZxgj6YVYOev8K2SUAd7wC2lrE6hJxdRxFSfnnlebSIjW7dIP3JJATeZBVJGQdPY7YTxKYISudydzgEjEeBGo8XP+7zuiF/53LicBsZu2m/gaEQ6RBGWkv5kMZTWRe1TS1xzLlxZCMSHRniAHZBA6+Xu7b5C5+vVYxG1/Uo7AXzUYRkaX076jIFYdhH5jiUl3kDFW80VAbJya5jVQPX6H0osnxcyY9Tqya7iENMj19Nf8NIXXsq31uSew+ev7LIyrqiGgDQc50KDmu7VTELYGEfVZmFjuPoOpNxzd3sGvn+tULFd8pEOTjzZNJIxmcVUGS8OTkRZa/0ntBj80P6HZzT3XJkv5Trc1zmAf0ee+mRuMXLO4o4wkkwvt2/JmeMRdGptSXBh015K/iwDqknZvuNbCwI7ILoeHP0S78lC3o6nQpe/96CeVmEPwXvbqbMly76i4z7ELTbbMHxCG4S0UjKUtB1R41Z4uDEEds624Zy8LnwjnJ6nJqEiEZy68bDShzBg8VoGqnl5/NFMBrTNpHdZ73euE2Fxm4tMBxDBOexUPSP5D2qcg73zMVTuCIE4i4blFWIwDdoPNG3SHQNLgZ+DLkLmgAlf3syt2myk5t2rTrqoYiw6Ow1EDNENSACJK+bu4IqiEFz7FEhJkq2G9tM+RZ4OHIqSikUymqgNIC5k+Se/4sk3gjKnqdW8UjO1f5CQNk8Z1kAAeIdFM67xRTGafWAbjIpA7f2bvMMPDtkHEAGXcC2RLd4ZcWRV79g8txCT8HjMBlzJA1S+2Kwsbws1SX+aIa/rm55ONmwVmaVcPWp6yf4xQ+hvBn2rZry1XVH+cCiXSN+DjgUpc9nL+QcwRixWTt1SHTTmbEkY2sZwfYT889oXKgTEpx8/qhVFQQYiS2FbhkeBXnxSXArAfnR6Pm4RmKhxw3Lvgjf4Eo4aSb2f4CEUlJVDjIeDeumTv/9OzAfoRZXEIDuXWcEZ4VoTdAAA/////wYAAAABAAAAAQAAAAAAAAAAAAAAAgAAAC+rzZYAAAAAMAAAAP////80BAAAAAAAAPwDAEBMWk1BgAoAAOsDAABdAAAAAQAAaJxe2IK/7KknxcSXK86dhEFS5n0YZtr4ZBTKG6WPr92ZGhquZzTIAKwwliLKh/wHyv7F/aVS8kpvJo5JPXNZPgXTFX/r2QzKEbGTOLiSpZb0yRzahJKiusbwU71tIeclNnMc/99W3WWjJetsaZ+WtSVKSPK1gik1voA3BrTI/PRBgTM4UIhTe2kkA8iMqPHiXR2hcqYwuuWgpVPHQXAVTuZnx9Zxn7bIpbv064K2rh42q3/XhlqkGkdjxR91QiiLMG9Chi6pQUshsjfAtQOYMGq/uDdmEXd3u6d7fVl4c4khoVbbs2840Tl3f+HX6kaJop667+ZhIxCIkHfBTkrJVyGuzpHDwvLTlI5u9FFg5v5w3m6nvQDpubo8iNPkx7pjnYOAApaD8p7PB42hx7Z/zDRIokdXY5O20wkNlzug1BHGm3HZuO0jXQsDIlSsiFurNm3N8maWhjLOKVcjm6y0TUPSQwTk/XUHjT/sj0X7Rq1sTXMCPdkV17lw+p6UozRKJJpxjouFdqyLH9BgT+fPSp2sWHjdy0kfhm8Sz94+HMWo5RtnOIfBws69zzbIFHJu70Jt32rZA6N5YM3No0C65Mi+FMX6HIqCu/DXXoGuKzxyBcnxURaE7ICSKx+A5aLOTWg+60yTxguXcqAx/RGYRJzv/6UDfEMoTjfRPz6a8TdPpNg2OxDLbzsu3SzLEwbPJMLSHS+ZuZ3QGew39UBbHHnxsyv3o3ft+zZ4/D8l/IIc0Ra0JFwgPkQQNl7gxpW0LFsfPjW7IobAXwqtczEM5HdClLhNE6YcRzQmtugRzHHrYnSOKpcf3mwr2AxTwpqtEw198bpfhpM1PQxKmSCJtzhuZz9atBHdInc/GhB2PlaDBm71z4I4T0EaDqgfp4WCmoolhi4Z4kJ9sWZ505wJxIOczgalRbgnERpjYFhSVUxmSs4yhEXijcptcncWvN87f0peWcxvWRFtiLdbxi33jFb8qklA7UnSp6cN0jz8Prs7QDJxAIUMN7WWnUSrJsHC1JEr+Z8WVMJGMYfLOVeRSCgu1BMHgvd7r9keQBsbMpUjIKBY9qeOqyZxyEu3HIWurvGd5r5mw8VE6J3kDUTxc4PRETqcyCIj52ys7wexeU3c/MSu6/UG0zFwJpJRzbTAhFWD9CamRx9SA8BrD7TtdErPhcc/L5diqGfBvN3WZv4Bp7rQHr4lfO2KUkxqq/8tVe7z+EpHN4WGYPS2k4Imc7PqUk5mNzk4jJ4YnWENas9Qz5JkNOZCJxSilqhDy4KqjHkiBCNmUJvWLy5XGu6TnwK4XJ9kCuA7EAOiM+H6uB8uxDTWt5CzuQD/////BgAAAAEAAAABAAAAAAAAAAAAAAACAAAA5CvzcQAAAAAwAAAA/////zYFAAAAAAAA/gQAQExaTUEgDgAA7QQAAF0AAAABAABohF/3ANos8ikRxPcBjHHEdepXp59WPT3vqirl6vheC7siJXviLHTHGaBqsjjm8uLG5Ve4w16rPpO+g1UZp520DHb0HpjYXJSk0M5IFR3Z3LJ6CXR6tPtNlqpMD8ZAKDdvjwcIwfPX2C0FiL5+eD32kebgYrV8PQnqCCxXZiN+/fwfAX0dF/AhVpUarBAj7DQRYywlck3WHyM09yjgwHsv5JdVZ+yabdwWo7K9bIQZkzVC4wJbWodKY9XjuDKoe7X6nat7dsjajdvnb8b5dWXoFBIwIuv4w+98OvjAM8uZqF4CbCoEBV/r7nqxx2RYsv+CYtPIPYAu6d7gK4BsVxy6kZRrI54N0cWF63nYa93Ce6GrkCPKg0p1QJMfe4/roFMA2GOp/7wkY2j3b+KwvFJh4vX2vsMdDL0oZ3MOhA5P+7nGrJECft7fEI7H9ykxU3jwbCyKfbBtPK6WSqWKiunXV2cHqBe9tNysHz0zGyIftTRZK8DXWdxswDEgAKhjqD+DIYey23RiC1HQX4oUMtadmoZ7QN9YcyhPnJQOPxMmKmtk7+DW6lBK92Ikyyr/lrZv+CR6c/Dhxr52JvtZLwWYv4bja08Ks6ZhHk9j9laSsMrN/q1XMbMtiAYleup8IXxgJgVYorVQBn/zcaRx0HTm7txKdNgWe4DyzrkqT7uYWTNNwLFmwKhiLd2RCGR4vwZ+nQsSS443H/TgPROTccB4WxTSBuSIRQVotQAUpJGTEmro0vsCEqoDkQxCuuHz7kWdWzXp5HQlwb2qlWYbd27nObHO1uUKJ9FpOkTInUPdWZ7I6Y3kcnGC5X2KabIzOPOh0GirJYmNpybhJrpLBRzQHvxV3AD0w3qP0Od67MrhZnv1wn3LDy8iroHOR58ab1jZ0xCGH9Qwo1EXtTuMUhyCi4riP5SiHFGRXXaOl32lW+rCoUi3QFm3wpoJ6N0kjQwAeUqHneaOjD3uyihFQrG6RC4VeVQLRwhW5kJIx9qXQBguOS4u1/hUlW+HfD3BwpdrvOBaICxBGNkAuju8+ah3vPyvESXbQZaDAhg7dfxnNOB951z/ftzEt489RsAZXz646GLTJGyLD25rLOhFRrn3LsVHgkQyD9YADf+fvwDYg9QHWCmhkgEluRTsiYcO87vMuma3+3++u3NmsSEPdDpYON6/EY4OE6WktRPDS19FflOA/aHh/GnrsQ7bJ7jYmV+d1R+3oXBMq+GIAkD3D/O22HroGKkoYC6tUQf1wMCmZ/mj+ihc6mtoV1KdVDLYWatmlR4U8avkG5RFI4vAs/7z0c34UDoutvoIwWrRG+rYQ1ALHp4+Nlquu3rhltrYk6n2gzSpnEjozJoJ+TGs4bttDCqggliwUCnHsDeRM8+wiGLEoo/ib+otxzTiRue28334DMQw3ec2PfzbLMnB5AYB8cw78oaIzkbRob5H+tsE0QFOwumh3nnyjOq1QuIIwJRCTs/wz+dhUJU7yKiMBfdYqJIa+tomn+Biaexl/d98Onnn+Aoguen1I29+DRkG7fvom2rHpXAOXH41W/cvczU0jwYabtKkdvA43c97oDu2rcegTlxpza4C4v/HquZa3nJ27UlYI89jM73vOSWcOfaRSoeEGXuwxgWGnGMaC1OKGrcp7+HsAUTec3yFir5DQWGN3ImkF17dOoXXAP////8GAAAAAQAAAAEAAAAAAAAAAAAAAAIAAABJTIjdAAAAADAAAAD/////3gMAAAAAAACmAwBATFpNQUAJAACVAwAAXQAAAAEAAGiMXviDP+ypJ8XER2Obf/Gub4RtwST2I5aFElPLRnYyBGKzzWHS3j92PM7OOrjSszB3wZMwdm0ahxEzeRRdNzXWcyklmZpnZnyTRC1yzISeAfbjOOXNofxCuF8x+RimSjb0+CE9pgV8Fgs6Nza/MSog2twkgUxmn0aoky4CECmnsEJJcQ66Ump+4tkbY284nKlxFxhT5k59LWkOwjOaFUysSXLX5R+gwJC82uA54PE1GidvXhqA/AkjGjcz0crb5k/rsqQ77T/wZsFhxana52fesSgZCV6fvqoGjkzqZnmsJVRGQcSPS2LBaJLIc+OOk8ZbDiGqBn5Xsxb9J31v/qjpov8yGxRyHi4yXRCCjE2QeaMeDtDSLxCXdTCYhjFtCJZytirhuAigToCAO1qMzZy4fREQYWlH0l8lEp13GryblNQkYNdwjgxZlwnavBf/O9G5hNH10VgiONbDa++CPCMStyDovKk1rOP6F3++I9wOyI5nnzYDxWd1Zo9j549iEsN8JbdhcD1JQUI/mt0N21t/FFJ5IWnChz3s/CmajA6AhG7xEXPc9SdqDDRegPwDBdktJSHOEpSmZOkeizeev4Emz0y76UP6oREqOSa8w9o2cgcxiPlbWqcQzIYb3D/WbwiYYexKjJM2Wszl2l401eHQLrduaUc5oYBufGT+do+LUUbxPvl1XwMIH6KyrwKFwHv2KsWRtCjNWB75xugj5FJcE1L1g2J2YUXkqFNuZveahmgjJ4KjyETVWv7DBlj6/GD5vJzEeIICH+mrkgKArOgHcEeMbNzGIUhAwY4wwMjxdMrUpwUwwKkmfx6L1eNjiqWrrholmk8qUGFN5IJMIvCAKUHujMSaqnCMO/7jvlWeWy5nsejSnWBNii/+YQJAxMBcUKmeSC54PzInKQxWTPygv1hxoD60xjr7B403/1ym7C0JKZEMrkLpB2dQ/9MrXqWH5jnpQuNd7GZ/wFYNMBQHQlODNaeWwPRJ8qbUlcgkeqWRC5/zhJ1H03Lb9hhGPTew9EHrKcDpUJvRQcJD2S5QMJ8wqbS6fODbJJxWCK6TU30bHf25JKqxv/S6sCAtPh7L/LypsErbO2f8sril+ZYtOWOdYJldzYzK79DNl453VbFjBfqlla+E74sKEC29OoaGAzIb+dFd8Ozl2fi1iB5tzXwwbauu9M0uKGvtZgQu2Zsx53qVwM7rC4TFKYfxEf7cAP////8GAAAAAQAAAAEAAAAAAAAAAAAAAAIAAAB3Q0KZAAAAADAAAAD/////HgEAAAAAAADmAABATFpNQWQBAADVAAAAXQAAAAEAAGiVXdSHP+xjGaphZkpGU+Usm+MtQUH83EbXXMjgea+yS5+C8AjZsriU7FrSa/C3QwfnfNO2E25hgUTRGIDQmsxKx7Q+ggw5O2Hyu6lPnEYPfqt3jvm3cjj6Z1X02PoibeZEF4V28Or5mSkKcqgZk6cbnqeeVgnqfAvD/O3uLu+nT7VAOydRrNBSD1yQVTBZUZtIJLmvDuIE27Eo7GuwHoYCUrVUwgW6q0SbikkxwEeOthaz5bMITbOd2JgjhkHkQV22VJTNinlRW2ADS1E/dJnyAAD/////AAAAAA==]========] +do + local DECODED_SHADERS_GMA = util.Base64Decode(SHADERS_GMA) + if not DECODED_SHADERS_GMA or #DECODED_SHADERS_GMA == 0 then + print("Failed to load shaders!") -- this shouldn't happen + return + end + + file.Write("rndx_shaders_" .. SHADERS_VERSION .. ".gma", DECODED_SHADERS_GMA) + game.MountGMA("data/rndx_shaders_" .. SHADERS_VERSION .. ".gma") +end + +local function GET_SHADER(name) + return SHADERS_VERSION:gsub("%.", "_") .. "_" .. name +end + +local BLUR_RT = GetRenderTargetEx("RNDX" .. SHADERS_VERSION .. SysTime(), + 1024, 1024, + RT_SIZE_LITERAL, + MATERIAL_RT_DEPTH_SEPARATE, + bit.bor(2, 256, 4, 8 --[[4, 8 is clamp_s + clamp-t]]), + 0, + IMAGE_FORMAT_BGRA8888 +) + +local NEW_FLAG; do + local flags_n = -1 + function NEW_FLAG() + flags_n = flags_n + 1 + return 2 ^ flags_n + end +end + +local NO_TL, NO_TR, NO_BL, NO_BR = NEW_FLAG(), NEW_FLAG(), NEW_FLAG(), NEW_FLAG() + +-- Svetov/Jaffies's great idea! +local SHAPE_CIRCLE, SHAPE_FIGMA, SHAPE_IOS = NEW_FLAG(), NEW_FLAG(), NEW_FLAG() + +local BLUR = NEW_FLAG() + +local RNDX = {} + +local shader_mat = [==[ +screenspace_general +{ + $pixshader "" + $vertexshader "" + + $basetexture "" + $texture1 "" + $texture2 "" + $texture3 "" + + // Mandatory, don't touch + $ignorez 1 + $vertexcolor 1 + $vertextransform 1 + " 1 then + local inv = 1 / k + TL, TR, BL, BR = TL * inv, TR * inv, BL * inv, BR * inv + end + + return clamp0(TL), clamp0(TR), clamp0(BL), clamp0(BR) + end +end + +local function SetupDraw() + local TL, TR, BL, BR = normalize_corner_radii() + + local matrix = MATRIXES[MAT] + MATRIX_SetUnpacked( + matrix, + + BL, W, OUTLINE_THICKNESS or -1, END_ANGLE, + BR, H, SHADOW_INTENSITY, ROTATION, + TR, SHAPE, BLUR_INTENSITY or 1.0, 0, + TL, TEXTURE and 1 or 0, START_ANGLE, 0 + ) + MATERIAL_SetMatrix(MAT, "$viewprojmat", matrix) + + if COL_R then + surface_SetDrawColor(COL_R, COL_G, COL_B, COL_A) + end + + surface_SetMaterial(MAT) +end + +local MANUAL_COLOR = NEW_FLAG() +local DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE + +local function draw_rounded(x, y, w, h, col, flags, tl, tr, bl, br, texture, thickness) + if col and col.a == 0 then + return + end + + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + local using_blur = bit_band(flags, BLUR) ~= 0 + if using_blur then + return RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + end + + MAT = ROUNDED_MAT; if texture then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", texture) + TEXTURE = texture + end + + W, H = w, h + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + elseif col then + COL_R, COL_G, COL_B, COL_A = col.r, col.g, col.b, col.a + else + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + end + + SetupDraw() + + -- https://github.com/Jaffies/rboxes/blob/main/rboxes.lua + -- fixes setting $basetexture to ""(none) not working correctly + return surface_DrawTexturedRectUV(x, y, w, h, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.Draw(r, x, y, w, h, col, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r) +end + +function RNDX.DrawOutlined(r, x, y, w, h, col, thickness, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, nil, thickness or 1) +end + +function RNDX.DrawTexture(r, x, y, w, h, col, texture, flags) + return draw_rounded(x, y, w, h, col, flags, r, r, r, r, texture) +end + +function RNDX.DrawMaterial(r, x, y, w, h, col, mat, flags) + local tex = mat:GetTexture("$basetexture") + if tex then + return RNDX.DrawTexture(r, x, y, w, h, col, tex, flags) + end +end + +function RNDX.DrawCircle(x, y, r, col, flags) + return RNDX.Draw(r / 2, x - r / 2, y - r / 2, r, r, col, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleOutlined(x, y, r, col, thickness, flags) + return RNDX.DrawOutlined(r / 2, x - r / 2, y - r / 2, r, r, col, thickness, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleTexture(x, y, r, col, texture, flags) + return RNDX.DrawTexture(r / 2, x - r / 2, y - r / 2, r, r, col, texture, (flags or 0) + SHAPE_CIRCLE) +end + +function RNDX.DrawCircleMaterial(x, y, r, col, mat, flags) + return RNDX.DrawMaterial(r / 2, x - r / 2, y - r / 2, r, r, col, mat, (flags or 0) + SHAPE_CIRCLE) +end + +local USE_SHADOWS_BLUR = false + +local function draw_blur() + if USE_SHADOWS_BLUR then + MAT = SHADOWS_BLUR_MAT + else + MAT = ROUNDED_BLUR_MAT + end + + COL_R, COL_G, COL_B, COL_A = 255, 255, 255, 255 + SetupDraw() + + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 0) + surface_DrawTexturedRect(X, Y, W, H) + + render_CopyRenderTargetToTexture(BLUR_RT) + MATERIAL_SetFloat(MAT, BLUR_VERTICAL, 1) + surface_DrawTexturedRect(X, Y, W, H) +end + +function RNDX.DrawBlur(x, y, w, h, flags, tl, tr, bl, br, thickness) + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + X, Y = x, y + W, H = w, h + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + OUTLINE_THICKNESS = thickness + + draw_blur() +end + +local function setup_shadows() + X = X - SHADOW_SPREAD + Y = Y - SHADOW_SPREAD + W = W + (SHADOW_SPREAD * 2) + H = H + (SHADOW_SPREAD * 2) + + TL = TL + (SHADOW_SPREAD * 2) + TR = TR + (SHADOW_SPREAD * 2) + BL = BL + (SHADOW_SPREAD * 2) + BR = BR + (SHADOW_SPREAD * 2) +end + +local function draw_shadows(r, g, b, a) + if USING_BLUR then + USE_SHADOWS_BLUR = true + draw_blur() + USE_SHADOWS_BLUR = false + end + + MAT = SHADOWS_MAT + + if r == false then + COL_R = nil + else + COL_R, COL_G, COL_B, COL_A = r, g, b, a + end + + SetupDraw() + -- https://github.com/Jaffies/rboxes/blob/main/rboxes.lua + -- fixes having no $basetexture causing uv to be broken + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) +end + +function RNDX.DrawShadowsEx(x, y, w, h, col, flags, tl, tr, bl, br, spread, intensity, thickness) + if col and col.a == 0 then + return + end + + local OLD_CLIPPING_STATE = DisableClipping(true) + + RESET_PARAMS() + + if not flags then + flags = DEFAULT_DRAW_FLAGS + end + + X, Y = x, y + W, H = w, h + SHADOW_SPREAD = spread or 30 + SHADOW_INTENSITY = intensity or SHADOW_SPREAD * 1.2 + + TL, TR, BL, BR = bit_band(flags, NO_TL) == 0 and tl or 0, + bit_band(flags, NO_TR) == 0 and tr or 0, + bit_band(flags, NO_BL) == 0 and bl or 0, + bit_band(flags, NO_BR) == 0 and br or 0 + + SHAPE = SHAPES[bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS)] or SHAPES[DEFAULT_SHAPE] + + OUTLINE_THICKNESS = thickness + + setup_shadows() + + USING_BLUR = bit_band(flags, BLUR) ~= 0 + + if bit_band(flags, MANUAL_COLOR) ~= 0 then + draw_shadows(false, nil, nil, nil) + elseif col then + draw_shadows(col.r, col.g, col.b, col.a) + else + draw_shadows(0, 0, 0, 255) + end + + DisableClipping(OLD_CLIPPING_STATE) +end + +function RNDX.DrawShadows(r, x, y, w, h, col, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity) +end + +function RNDX.DrawShadowsOutlined(r, x, y, w, h, col, thickness, spread, intensity, flags) + return RNDX.DrawShadowsEx(x, y, w, h, col, flags, r, r, r, r, spread, intensity, thickness or 1) +end + +local BASE_FUNCS; BASE_FUNCS = { + Rad = function(self, rad) + TL, TR, BL, BR = rad, rad, rad, rad + return self + end, + Radii = function(self, tl, tr, bl, br) + TL, TR, BL, BR = tl or 0, tr or 0, bl or 0, br or 0 + return self + end, + Texture = function(self, texture) + TEXTURE = texture + return self + end, + Material = function(self, mat) + local tex = mat:GetTexture("$basetexture") + if tex then + TEXTURE = tex + end + return self + end, + Outline = function(self, thickness) + OUTLINE_THICKNESS = thickness + return self + end, + Shape = function(self, shape) + SHAPE = SHAPES[shape] or 2.2 + return self + end, + Color = function(self, col_or_r, g, b, a) + if type(col_or_r) == "number" then + COL_R, COL_G, COL_B, COL_A = col_or_r, g or 255, b or 255, a or 255 + else + COL_R, COL_G, COL_B, COL_A = col_or_r.r, col_or_r.g, col_or_r.b, col_or_r.a + end + return self + end, + Blur = function(self, intensity) + if not intensity then + intensity = 1.0 + end + intensity = math_max(intensity, 0) + USING_BLUR, BLUR_INTENSITY = true, intensity + return self + end, + Rotation = function(self, angle) + ROTATION = math.rad(angle or 0) + return self + end, + StartAngle = function(self, angle) + START_ANGLE = angle or 0 + return self + end, + EndAngle = function(self, angle) + END_ANGLE = angle or 360 + return self + end, + Shadow = function(self, spread, intensity) + SHADOW_ENABLED, SHADOW_SPREAD, SHADOW_INTENSITY = true, spread or 30, intensity or (spread or 30) * 1.2 + return self + end, + Clip = function(self, pnl) + CLIP_PANEL = pnl + return self + end, + Flags = function(self, flags) + flags = flags or 0 + + -- Corner flags + if bit_band(flags, NO_TL) ~= 0 then + TL = 0 + end + if bit_band(flags, NO_TR) ~= 0 then + TR = 0 + end + if bit_band(flags, NO_BL) ~= 0 then + BL = 0 + end + if bit_band(flags, NO_BR) ~= 0 then + BR = 0 + end + + -- Shape flags + local shape_flag = bit_band(flags, SHAPE_CIRCLE + SHAPE_FIGMA + SHAPE_IOS) + if shape_flag ~= 0 then + SHAPE = SHAPES[shape_flag] or SHAPES[DEFAULT_SHAPE] + end + + -- Blur flag + if bit_band(flags, BLUR) ~= 0 then + BASE_FUNCS.Blur(self) + end + + -- Manual color flag + if bit_band(flags, MANUAL_COLOR) ~= 0 then + COL_R = nil + end + + return self + end, + +} + +local RECT = { + Rad = BASE_FUNCS.Rad, + Radii = BASE_FUNCS.Radii, + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Shape = BASE_FUNCS.Shape, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + + Draw = function(self) + if START_ANGLE == END_ANGLE then + return -- nothing to draw + end + + local OLD_CLIPPING_STATE + if SHADOW_ENABLED or CLIP_PANEL then + -- if we are inside a panel, we need to draw outside of it + OLD_CLIPPING_STATE = DisableClipping(true) + end + + if CLIP_PANEL then + local sx, sy = CLIP_PANEL:LocalToScreen(0, 0) + local sw, sh = CLIP_PANEL:GetSize() + render.SetScissorRect(sx, sy, sx + sw, sy + sh, true) + end + + if SHADOW_ENABLED then + setup_shadows() + draw_shadows(COL_R, COL_G, COL_B, COL_A) + elseif USING_BLUR then + draw_blur() + else + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + end + + SetupDraw() + surface_DrawTexturedRectUV(X, Y, W, H, -0.015625, -0.015625, 1.015625, 1.015625) + end + + if CLIP_PANEL then + render.SetScissorRect(0, 0, 0, 0, false) + end + + if SHADOW_ENABLED or CLIP_PANEL then + DisableClipping(OLD_CLIPPING_STATE) + end + end, + + GetMaterial = function(self) + if SHADOW_ENABLED or USING_BLUR then + error("You can't get the material of a shadowed or blurred rectangle!") + end + + if TEXTURE then + MAT = ROUNDED_TEXTURE_MAT + MATERIAL_SetTexture(MAT, "$basetexture", TEXTURE) + end + SetupDraw() + + return MAT + end, +} + +local CIRCLE = { + Texture = BASE_FUNCS.Texture, + Material = BASE_FUNCS.Material, + Outline = BASE_FUNCS.Outline, + Color = BASE_FUNCS.Color, + Blur = BASE_FUNCS.Blur, + Rotation = BASE_FUNCS.Rotation, + StartAngle = BASE_FUNCS.StartAngle, + EndAngle = BASE_FUNCS.EndAngle, + Clip = BASE_FUNCS.Clip, + Shadow = BASE_FUNCS.Shadow, + Flags = BASE_FUNCS.Flags, + + Draw = RECT.Draw, + GetMaterial = RECT.GetMaterial, +} + +local TYPES = { + Rect = function(x, y, w, h) + RESET_PARAMS() + MAT = ROUNDED_MAT + X, Y, W, H = x, y, w, h + return RECT + end, + Circle = function(x, y, r) + RESET_PARAMS() + MAT = ROUNDED_MAT + SHAPE = SHAPES[SHAPE_CIRCLE] + X, Y, W, H = x - r / 2, y - r / 2, r, r + r = r / 2 + TL, TR, BL, BR = r, r, r, r + return CIRCLE + end +} + +setmetatable(RNDX, { + __call = function() + return TYPES + end +}) + +-- Flags +RNDX.NO_TL = NO_TL +RNDX.NO_TR = NO_TR +RNDX.NO_BL = NO_BL +RNDX.NO_BR = NO_BR + +RNDX.SHAPE_CIRCLE = SHAPE_CIRCLE +RNDX.SHAPE_FIGMA = SHAPE_FIGMA +RNDX.SHAPE_IOS = SHAPE_IOS + +RNDX.BLUR = BLUR +RNDX.MANUAL_COLOR = MANUAL_COLOR + +function RNDX.SetFlag(flags, flag, bool) + flag = RNDX[flag] or flag + if tobool(bool) then + return bit.bor(flags, flag) + else + return bit.band(flags, bit.bnot(flag)) + end +end + +function RNDX.SetDefaultShape(shape) + DEFAULT_SHAPE = shape or SHAPE_FIGMA + DEFAULT_DRAW_FLAGS = DEFAULT_SHAPE +end + +return RNDX diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/modules/shadows.lua b/addons/mantle_darkfated_ultracode/lua/mantle/modules/shadows.lua new file mode 100644 index 0000000..3f6baa3 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/modules/shadows.lua @@ -0,0 +1,115 @@ +local math_sin = math.sin +local math_cos = math.cos +local math_rad = math.rad +local math_ceil = math.ceil + +local function CreateBShadows() + BShadows = {} + + local resStr = Mantle.func.sw .. Mantle.func.sh + + BShadows.RenderTarget = GetRenderTarget('BShadows_original_' .. resStr, Mantle.func.sw, Mantle.func.sh) + BShadows.RenderTarget2 = GetRenderTarget('BShadows_shadow_' .. resStr, Mantle.func.sw, Mantle.func.sh) + BShadows.ShadowMaterial = CreateMaterial('BShadows', 'UnlitGeneric', { + ['$translucent'] = 1, + ['$vertexalpha'] = 1, + ['alpha'] = 1 + }) + + BShadows.ShadowMaterialGrayscale = CreateMaterial('BShadows_grayscale', 'UnlitGeneric', { + ['$translucent'] = 1, + ['$vertexalpha'] = 1, + ['$alpha'] = 1, + ['$color'] = '0 0 0', + ['$color2'] = '0 0 0' + }) + + BShadows.BeginShadow = function() + render.PushRenderTarget(BShadows.RenderTarget) + + render.OverrideAlphaWriteEnable(true, true) + render.Clear(0, 0, 0, 0) + render.OverrideAlphaWriteEnable(false, false) + + cam.Start2D() + end + + BShadows.EndShadow = function(intensity, spread, blur, opacity, direction, distance, bool_shadow_only) + opacity = opacity or 255 + direction = direction or 0 + distance = distance or 0 + bool_shadow_only = bool_shadow_only or false + + render.CopyRenderTargetToTexture(BShadows.RenderTarget2) + + if blur > 0 then + render.OverrideAlphaWriteEnable(true, true) + render.BlurRenderTarget(BShadows.RenderTarget2, spread, spread, blur) + render.OverrideAlphaWriteEnable(false, false) + end + + render.PopRenderTarget() + + BShadows.ShadowMaterial:SetTexture('$basetexture', BShadows.RenderTarget) + BShadows.ShadowMaterialGrayscale:SetTexture('$basetexture', BShadows.RenderTarget2) + + local xOffset = math_sin(math_rad(direction)) * distance + local yOffset = math_cos(math_rad(direction)) * distance + + BShadows.ShadowMaterialGrayscale:SetFloat('$alpha', opacity / 255) + render.SetMaterial(BShadows.ShadowMaterialGrayscale) + + for i = 1, math_ceil(intensity) do + render.DrawScreenQuadEx(xOffset, yOffset, Mantle.func.sw, Mantle.func.sh) + end + + if !bool_shadow_only then + BShadows.ShadowMaterial:SetTexture('$basetexture', BShadows.RenderTarget) + render.SetMaterial(BShadows.ShadowMaterial) + render.DrawScreenQuad() + end + + cam.End2D() + end + + BShadows.DrawShadowTexture = function(texture, intensity, spread, blur, opacity, direction, distance, bool_shadow_only) + opacity = opacity or 255 + direction = direction or 0 + distance = distance or 0 + bool_shadow_only = bool_shadow_only or false + + render.CopyTexture(texture, BShadows.RenderTarget2) + + if blur > 0 then + render.PushRenderTarget(BShadows.RenderTarget2) + render.OverrideAlphaWriteEnable(true, true) + render.BlurRenderTarget(BShadows.RenderTarget2, spread, spread, blur) + render.OverrideAlphaWriteEnable(false, false) + render.PopRenderTarget() + end + + BShadows.ShadowMaterialGrayscale:SetTexture('$basetexture', BShadows.RenderTarget2) + + local xOffset = math_sin(math_rad(direction)) * distance + local yOffset = math_cos(math_rad(direction)) * distance + + BShadows.ShadowMaterialGrayscale:SetFloat('$alpha', opacity / 255) + render.SetMaterial(BShadows.ShadowMaterialGrayscale) + + for i = 1, math_ceil(intensity) do + render.DrawScreenQuadEx(xOffset, yOffset, Mantle.func.sw, Mantle.func.sh) + end + + if !bool_shadow_only then + BShadows.ShadowMaterial:SetTexture('$basetexture', texture) + render.SetMaterial(BShadows.ShadowMaterial) + render.DrawScreenQuad() + end + end +end + +CreateBShadows() + +hook.Add('OnScreenSizeChanged', 'Mantle.Shadows', function() + CreateBShadows() +end) diff --git a/addons/mantle_darkfated_ultracode/lua/mantle/modules/utf8.lua b/addons/mantle_darkfated_ultracode/lua/mantle/modules/utf8.lua new file mode 100644 index 0000000..c1caf41 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle/modules/utf8.lua @@ -0,0 +1,23 @@ +if utf8 == nil then + utf8 = { + charpattern = '[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*' + } +end + +local uc_lc = {['А']='а',['Б']='б',['В']='в',['Г']='г',['Д']='д',['Е']='е',['Ё']='ё',['Ж']='ж',['З']='з',['И']='и',['Й']='й',['К']='к',['Л']='л',['М']='м',['Н']='н',['О']='о',['П']='п',['Р']='р',['С']='с',['Т']='т',['У']='у',['Ф']='ф',['Х']='х',['Ц']='ц',['Ч']='ч',['Ш']='ш',['Щ']='щ',['Ъ']='ъ',['Ы']='ы',['Ь']='ь',['Э']='э',['Ю']='ю',['Я']='я'} +local lc_uc = {} + +for uc, lc in pairs(uc_lc) do + lc_uc[lc] = uc +end + +setmetatable(uc_lc, {__index = function(_, char) return char:lower() end}) +setmetatable(lc_uc, {__index = function(_, char) return char:upper() end}) + +function utf8.lower(text) + return text:gsub(utf8.charpattern, uc_lc) +end + +function utf8.upper(text) + return text:gsub(utf8.charpattern, lc_uc) +end diff --git a/addons/mantle_darkfated_ultracode/lua/mantle_addons/mantle/lang.lua b/addons/mantle_darkfated_ultracode/lua/mantle_addons/mantle/lang.lua new file mode 100644 index 0000000..e0a3037 --- /dev/null +++ b/addons/mantle_darkfated_ultracode/lua/mantle_addons/mantle/lang.lua @@ -0,0 +1,55 @@ +local tabl = {} + +tabl['en'] = { + apply = 'Apply', + + table_wrong_args = 'MantleTable Error: Invalid number of arguments', + table_copy = 'Copy', + table_delete_row = 'Delete row', + + player_title = 'Player Selector', + player_offline = 'Disconnected', + player_close = 'Close', + player_ping = 'ms', + + frame_title = 'Title', + frame_alpha = 'Transparency', + frame_move_from_menu = 'Move from menu', + frame_close_window = 'Close window', + + color_title = 'Color Picker', + color_cancel = 'Cancel', + color_select = 'Select', + + btn_default = 'Button', + + entry_default_placeholder = 'Enter text' +} + +tabl['ru'] = { + apply = 'Применить', + + table_wrong_args = 'MantleTable Error: Неверное количество аргументов', + table_copy = 'Копировать', + table_delete_row = 'Удалить строку', + + player_title = 'Выбор игрока', + player_offline = 'Вышел', + player_close = 'Закрыть', + player_ping = 'мс', + + frame_title = 'Заголовок', + frame_alpha = 'Прозрачность', + frame_move_from_menu = 'Передвижение из меню', + frame_close_window = 'Закрыть окно', + + color_title = 'Выбор цвета', + color_cancel = 'Отмена', + color_select = 'Выбрать', + + btn_default = 'Кнопка', + + entry_default_placeholder = 'Введите текст' +} + +return tabl diff --git a/addons/mantle_darkfated_ultracode/materials/mantle/close_btn.png b/addons/mantle_darkfated_ultracode/materials/mantle/close_btn.png new file mode 100644 index 0000000..5b07d92 Binary files /dev/null and b/addons/mantle_darkfated_ultracode/materials/mantle/close_btn.png differ diff --git a/addons/mantle_darkfated_ultracode/materials/mantle/close_btn_new.png b/addons/mantle_darkfated_ultracode/materials/mantle/close_btn_new.png new file mode 100644 index 0000000..2d779fd Binary files /dev/null and b/addons/mantle_darkfated_ultracode/materials/mantle/close_btn_new.png differ diff --git a/addons/mantle_darkfated_ultracode/materials/mantle/slider.png b/addons/mantle_darkfated_ultracode/materials/mantle/slider.png new file mode 100644 index 0000000..e3ce99e Binary files /dev/null and b/addons/mantle_darkfated_ultracode/materials/mantle/slider.png differ diff --git a/addons/mantle_darkfated_ultracode/sound/mantle/btn_click.ogg b/addons/mantle_darkfated_ultracode/sound/mantle/btn_click.ogg new file mode 100644 index 0000000..420443e Binary files /dev/null and b/addons/mantle_darkfated_ultracode/sound/mantle/btn_click.ogg differ diff --git a/addons/mantle_darkfated_ultracode/sound/mantle/ratio_btn.ogg b/addons/mantle_darkfated_ultracode/sound/mantle/ratio_btn.ogg new file mode 100644 index 0000000..4f889e3 Binary files /dev/null and b/addons/mantle_darkfated_ultracode/sound/mantle/ratio_btn.ogg differ diff --git a/addons/map_content/addon.json b/addons/map_content/addon.json new file mode 100644 index 0000000..716f306 --- /dev/null +++ b/addons/map_content/addon.json @@ -0,0 +1,9 @@ +{ + "title": "mapcontent", + "type": "servercontent", + "tags": [ + "build", + "roleplay" + ], + "ignore": [] +} \ No newline at end of file diff --git a/addons/map_content/maps/rp_bangclaw_winter_v2.bsp b/addons/map_content/maps/rp_bangclaw_winter_v2.bsp new file mode 100644 index 0000000..c6d71d0 Binary files /dev/null and b/addons/map_content/maps/rp_bangclaw_winter_v2.bsp differ diff --git a/addons/map_content/materials/danktown/discotile01.vmt b/addons/map_content/materials/danktown/discotile01.vmt new file mode 100644 index 0000000..846cea6 --- /dev/null +++ b/addons/map_content/materials/danktown/discotile01.vmt @@ -0,0 +1,10 @@ +"LightmappedGeneric" +{ + "$basetexture" "danktown/discotile01" + "$color" "[.0125 .0125 .0125]" + "$detail" "danktown/discotile01_noise" + "$detailscale" "0.03125" + "$detailblendmode" "0" + "$detailblendfactor" 100 + "$surfaceprop" "Tile" +} diff --git a/addons/map_content/materials/danktown/discotile01.vtf b/addons/map_content/materials/danktown/discotile01.vtf new file mode 100644 index 0000000..2322c77 Binary files /dev/null and b/addons/map_content/materials/danktown/discotile01.vtf differ diff --git a/addons/map_content/materials/danktown/discotile01_noise.vtf b/addons/map_content/materials/danktown/discotile01_noise.vtf new file mode 100644 index 0000000..1ab2562 Binary files /dev/null and b/addons/map_content/materials/danktown/discotile01_noise.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_cloud1.vmt b/addons/map_content/materials/de_austria/skybox/austria_cloud1.vmt new file mode 100644 index 0000000..b9718d3 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_cloud1.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_cloud1" + $translucent 1 + $vertexcolor 1 + $vertexalpha 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_cloud1.vtf b/addons/map_content/materials/de_austria/skybox/austria_cloud1.vtf new file mode 100644 index 0000000..e53de6d Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_cloud1.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_cloud2.vmt b/addons/map_content/materials/de_austria/skybox/austria_cloud2.vmt new file mode 100644 index 0000000..fdd929f --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_cloud2.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_cloud2" + $translucent 1 + $vertexcolor 1 + $vertexalpha 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_cloud2.vtf b/addons/map_content/materials/de_austria/skybox/austria_cloud2.vtf new file mode 100644 index 0000000..7b45dcc Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_cloud2.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_mountain1.vmt b/addons/map_content/materials/de_austria/skybox/austria_mountain1.vmt new file mode 100644 index 0000000..a628424 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_mountain1.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_mountain1" + $alphatest 1 +} diff --git a/addons/map_content/materials/de_austria/skybox/austria_mountain1.vtf b/addons/map_content/materials/de_austria/skybox/austria_mountain1.vtf new file mode 100644 index 0000000..ccdf9f1 Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_mountain1.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_mountain2.vmt b/addons/map_content/materials/de_austria/skybox/austria_mountain2.vmt new file mode 100644 index 0000000..0edcd77 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_mountain2.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_mountain2" + $alphatest 1 +} diff --git a/addons/map_content/materials/de_austria/skybox/austria_mountain2.vtf b/addons/map_content/materials/de_austria/skybox/austria_mountain2.vtf new file mode 100644 index 0000000..683e9e6 Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_mountain2.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_overlay_snow3.vmt b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow3.vmt new file mode 100644 index 0000000..63f304b --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow3.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_overlay_snow3" + $translucent 1 + $vertexcolor 1 + $vertexalpha 1 + $decal 1 + $decalscale 1 +} diff --git a/addons/map_content/materials/de_austria/skybox/austria_overlay_snow3.vtf b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow3.vtf new file mode 100644 index 0000000..9bce43f Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow3.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_overlay_snow4.vmt b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow4.vmt new file mode 100644 index 0000000..ccd6ce2 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow4.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_overlay_snow4" + $translucent 1 + $vertexcolor 1 + $vertexalpha 1 + $decal 1 + $decalscale 1 +} diff --git a/addons/map_content/materials/de_austria/skybox/austria_overlay_snow4.vtf b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow4.vtf new file mode 100644 index 0000000..1f21801 Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow4.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_overlay_snow5.vmt b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow5.vmt new file mode 100644 index 0000000..7ef6324 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow5.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_overlay_snow5" + $translucent 1 + $vertexcolor 1 + $vertexalpha 1 + $decal 1 + $decalscale 1 +} diff --git a/addons/map_content/materials/de_austria/skybox/austria_overlay_snow5.vtf b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow5.vtf new file mode 100644 index 0000000..4b31974 Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_overlay_snow5.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church1.vmt b/addons/map_content/materials/de_austria/skybox/austria_skybox_church1.vmt new file mode 100644 index 0000000..a238dbe --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_skybox_church1.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + "$basetexture" "de_austria/skybox/austria_skybox_church1" + $selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church1.vtf b/addons/map_content/materials/de_austria/skybox/austria_skybox_church1.vtf new file mode 100644 index 0000000..b25fd2b Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_skybox_church1.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church2.vmt b/addons/map_content/materials/de_austria/skybox/austria_skybox_church2.vmt new file mode 100644 index 0000000..d2e5942 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_skybox_church2.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ + "$basetexture" "de_austria/skybox/austria_skybox_church2" +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church2.vtf b/addons/map_content/materials/de_austria/skybox/austria_skybox_church2.vtf new file mode 100644 index 0000000..5c9ad71 Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_skybox_church2.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church3.vmt b/addons/map_content/materials/de_austria/skybox/austria_skybox_church3.vmt new file mode 100644 index 0000000..ed32282 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_skybox_church3.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ + "$basetexture" "de_austria/skybox/austria_skybox_church3" +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church3.vtf b/addons/map_content/materials/de_austria/skybox/austria_skybox_church3.vtf new file mode 100644 index 0000000..66d415a Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_skybox_church3.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church4.vmt b/addons/map_content/materials/de_austria/skybox/austria_skybox_church4.vmt new file mode 100644 index 0000000..2bcac0b --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_skybox_church4.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + "$basetexture" "de_austria/skybox/austria_skybox_church4" + $selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church4.vtf b/addons/map_content/materials/de_austria/skybox/austria_skybox_church4.vtf new file mode 100644 index 0000000..c03233b Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_skybox_church4.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church5.vmt b/addons/map_content/materials/de_austria/skybox/austria_skybox_church5.vmt new file mode 100644 index 0000000..cffde7c --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_skybox_church5.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ + "$basetexture" "de_austria/skybox/austria_skybox_church5" +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_church5.vtf b/addons/map_content/materials/de_austria/skybox/austria_skybox_church5.vtf new file mode 100644 index 0000000..68717a6 Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_skybox_church5.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_house1.vmt b/addons/map_content/materials/de_austria/skybox/austria_skybox_house1.vmt new file mode 100644 index 0000000..4916d11 --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_skybox_house1.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_skybox_house1" + $selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_house1.vtf b/addons/map_content/materials/de_austria/skybox/austria_skybox_house1.vtf new file mode 100644 index 0000000..4bacaba Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_skybox_house1.vtf differ diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_house2.vmt b/addons/map_content/materials/de_austria/skybox/austria_skybox_house2.vmt new file mode 100644 index 0000000..b70ad3f --- /dev/null +++ b/addons/map_content/materials/de_austria/skybox/austria_skybox_house2.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/skybox/austria_skybox_house2" + $selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/skybox/austria_skybox_house2.vtf b/addons/map_content/materials/de_austria/skybox/austria_skybox_house2.vtf new file mode 100644 index 0000000..96e4ce4 Binary files /dev/null and b/addons/map_content/materials/de_austria/skybox/austria_skybox_house2.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_rock1.vtf b/addons/map_content/materials/de_austria/snow/austria_rock1.vtf new file mode 100644 index 0000000..a76df1e Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_rock1.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_rock1_n.vtf b/addons/map_content/materials/de_austria/snow/austria_rock1_n.vtf new file mode 100644 index 0000000..c7d9678 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_rock1_n.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1.vmt new file mode 100644 index 0000000..263a2fc --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1.vmt @@ -0,0 +1,20 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/snow/austria_snow1" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 8 + $detailblendfactor .7 + $detailblendmode 0 + + $envmap env_cubemap + $normalmapalphaenvmapmask 1 + $envmaptint "[0.5 0.5 0.6]" + + $phong 1 + $phongexponent 5 + $phongMaskContrastBrightness "[0 .3]" + $phongAmount "[.9 .9 1 1.0]" +} diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1.vtf b/addons/map_content/materials/de_austria/snow/austria_snow1.vtf new file mode 100644 index 0000000..456f61e Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow1.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_border1.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_border1.vmt new file mode 100644 index 0000000..1f9551b --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_border1.vmt @@ -0,0 +1,20 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/snow/austria_snow1_border1" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 4 + $detailblendfactor .7 + $detailblendmode 0 + + $envmap env_cubemap + $normalmapalphaenvmapmask 1 + $envmaptint "[0.5 0.5 0.6]" + + $phong 1 + $phongexponent 5 + $phongMaskContrastBrightness "[0 .3]" + $phongAmount "[.9 .9 1 1.0]" +} diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_border1.vtf b/addons/map_content/materials/de_austria/snow/austria_snow1_border1.vtf new file mode 100644 index 0000000..fb667f1 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow1_border1.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_border2.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_border2.vmt new file mode 100644 index 0000000..a901307 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_border2.vmt @@ -0,0 +1,20 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/snow/austria_snow1_border2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 4 + $detailblendfactor .7 + $detailblendmode 0 + + $envmap env_cubemap + $normalmapalphaenvmapmask 1 + $envmaptint "[0.5 0.5 0.6]" + + $phong 1 + $phongexponent 5 + $phongMaskContrastBrightness "[0 .3]" + $phongAmount "[.9 .9 1 1.0]" +} diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_border2.vtf b/addons/map_content/materials/de_austria/snow/austria_snow1_border2.vtf new file mode 100644 index 0000000..b3f0136 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow1_border2.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_n.vtf b/addons/map_content/materials/de_austria/snow/austria_snow1_n.vtf new file mode 100644 index 0000000..b9da2b3 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow1_n.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_to_2.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_to_2.vmt new file mode 100644 index 0000000..1d46983 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_to_2.vmt @@ -0,0 +1,16 @@ +"WorldVertexTransition" +{ + + $basetexture "de_austria/snow/austria_snow2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow2_n" + + + $basetexture2 "de_austria/snow/austria_snow1" + $surfaceprop2 snow + $bumpmap2 "de_austria/snow/austria_snow1_n" + + + $blendmodulatetexture "de_austria/snow/austria_snow_mod1" + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_to_conc.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_to_conc.vmt new file mode 100644 index 0000000..a819757 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_to_conc.vmt @@ -0,0 +1,15 @@ +"WorldVertexTransition" +{ + $basetexture "de_austria/snow/austria_snow1" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" + + + $basetexture2 "de_austria/floor/austria_concrete1" + $surfaceprop2 concrete + $bumpmap2 "de_austria/floor/austria_concrete1_n" + + + $blendmodulatetexture "de_austria/snow/austria_snow_mod2" + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_to_conc2.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_to_conc2.vmt new file mode 100644 index 0000000..9f6d107 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_to_conc2.vmt @@ -0,0 +1,15 @@ +"WorldVertexTransition" +{ + $basetexture "de_austria/snow/austria_snow2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow2_n" + + + $basetexture2 "de_austria/floor/austria_asphalt1" + $surfaceprop2 concrete + $bumpmap2 "de_austria/floor/austria_asphalt1_n" + + + $blendmodulatetexture "de_austria/snow/austria_snow_mod2" + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_to_dirt1.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_to_dirt1.vmt new file mode 100644 index 0000000..a06ce7f --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_to_dirt1.vmt @@ -0,0 +1,14 @@ +"WorldVertexTransition" +{ + $basetexture "nature/dirtfloor011a" + $surfaceprop rock + $bumpmap "de_austria/snow/austria_snow1_n" + + $basetexture2 "de_austria/snow/austria_snow1" + $surfaceprop2 snow + $bumpmap2 "de_austria/snow/austria_snow1_n" + + $blendmodulatetexture "de_austria/snow/austria_snow_mod2" + + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_to_rock.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_to_rock.vmt new file mode 100644 index 0000000..cd36e9f --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_to_rock.vmt @@ -0,0 +1,13 @@ +"WorldVertexTransition" +{ + $basetexture "de_austria/snow/austria_rock1" + $surfaceprop rock + $bumpmap "de_austria/snow/austria_rock1_n" + + $basetexture2 "de_austria/snow/austria_snow1" + $surfaceprop2 snow + $bumpmap2 "de_austria/snow/austria_snow1_n" + + $blendmodulatetexture "de_austria/snow/austria_snow_mod2" + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow1_to_roof1.vmt b/addons/map_content/materials/de_austria/snow/austria_snow1_to_roof1.vmt new file mode 100644 index 0000000..972f406 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow1_to_roof1.vmt @@ -0,0 +1,13 @@ +"WorldVertexTransition" +{ + "$basetexture" "de_cbble/roof_alt/roof_b" + "$surfaceprop" "wood" + + + "$basetexture2" "de_austria/snow/austria_snow1" + "$surfaceprop2" "snow" + "$bumpmap2" "de_austria/snow/austria_snow1_n" + + "$blendmodulatetexture" "de_austria/snow/austria_snow_mod2" + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow2.vmt b/addons/map_content/materials/de_austria/snow/austria_snow2.vmt new file mode 100644 index 0000000..284a2ab --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow2.vmt @@ -0,0 +1,11 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/snow/austria_snow2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow2_n" + + $detailscale 8 + $detailblendfactor .7 + $detailblendmode 0 + $detail "de_austria/snow/austria_snow_detail1" +} diff --git a/addons/map_content/materials/de_austria/snow/austria_snow2.vtf b/addons/map_content/materials/de_austria/snow/austria_snow2.vtf new file mode 100644 index 0000000..3178e26 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow2.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow2_n.vtf b/addons/map_content/materials/de_austria/snow/austria_snow2_n.vtf new file mode 100644 index 0000000..6b829e6 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow2_n.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow2_to_conc.vmt b/addons/map_content/materials/de_austria/snow/austria_snow2_to_conc.vmt new file mode 100644 index 0000000..5ce1f0c --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow2_to_conc.vmt @@ -0,0 +1,15 @@ +"WorldVertexTransition" +{ + $basetexture "de_austria/snow/austria_snow2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow2_n" + + + $basetexture2 "de_austria/floor/austria_concrete1" + $surfaceprop2 concrete + $bumpmap2 "de_austria/floor/austria_concrete1_n" + + + $blendmodulatetexture "de_austria/snow/austria_snow_mod1" + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow2_to_tile.vmt b/addons/map_content/materials/de_austria/snow/austria_snow2_to_tile.vmt new file mode 100644 index 0000000..9577e14 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/austria_snow2_to_tile.vmt @@ -0,0 +1,13 @@ +"WorldVertexTransition" +{ + $basetexture "de_austria/snow/austria_snow2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow2_n" + + $basetexture2 "de_austria/floor/austria_tilefloor1" + $surfaceprop2 tile + $bumpmap2 "de_austria/floor/austria_tilefloor1_n" + + $blendmodulatetexture "de_austria/snow/austria_snow_mod1" + +} \ No newline at end of file diff --git a/addons/map_content/materials/de_austria/snow/austria_snow_detail1.vtf b/addons/map_content/materials/de_austria/snow/austria_snow_detail1.vtf new file mode 100644 index 0000000..918e1e5 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow_detail1.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow_detail2.vtf b/addons/map_content/materials/de_austria/snow/austria_snow_detail2.vtf new file mode 100644 index 0000000..447f255 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow_detail2.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow_mod1.vtf b/addons/map_content/materials/de_austria/snow/austria_snow_mod1.vtf new file mode 100644 index 0000000..82e2817 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow_mod1.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/austria_snow_mod2.vtf b/addons/map_content/materials/de_austria/snow/austria_snow_mod2.vtf new file mode 100644 index 0000000..c40f858 Binary files /dev/null and b/addons/map_content/materials/de_austria/snow/austria_snow_mod2.vtf differ diff --git a/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_border1_cheap.vmt b/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_border1_cheap.vmt new file mode 100644 index 0000000..cd004e2 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_border1_cheap.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/snow/austria_snow1_border1" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" +} diff --git a/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_cheap.vmt b/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_cheap.vmt new file mode 100644 index 0000000..ad0aa0b --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_cheap.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + $basetexture "de_austria/snow/austria_snow1" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" +} diff --git a/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_to_rock_cheap.vmt b/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_to_rock_cheap.vmt new file mode 100644 index 0000000..e6b5350 --- /dev/null +++ b/addons/map_content/materials/de_austria/snow/cheap/austria_snow1_to_rock_cheap.vmt @@ -0,0 +1,12 @@ +"WorldVertexTransition" +{ + $basetexture "de_austria/snow/austria_rock1" + $surfaceprop rock + $bumpmap "de_austria/snow/austria_rock1_n" + + $basetexture2 "de_austria/snow/austria_snow1" + $surfaceprop2 snow + $bumpmap2 "de_austria/snow/austria_snow1_n" + + $blendmodulatetexture "de_austria/snow/austria_snow_mod2" +} \ No newline at end of file diff --git a/addons/map_content/materials/glass/glass01.vmt b/addons/map_content/materials/glass/glass01.vmt new file mode 100644 index 0000000..29c64a0 --- /dev/null +++ b/addons/map_content/materials/glass/glass01.vmt @@ -0,0 +1,11 @@ +LightmappedGeneric +{ +$baseTexture "models\props_windows/window_uban_apt_glass" +$translucent 1 +$envmap env_cubemap +$envmapmask "models\props_windows\window_uban_apt_glass_ref" +$envmaptint "[1 1 1]" +$nocull 1 +$surfaceprop glass +$envmapcontrast ".7" +} \ No newline at end of file diff --git a/addons/map_content/materials/glass/glass01opaque.vmt b/addons/map_content/materials/glass/glass01opaque.vmt new file mode 100644 index 0000000..98547c4 --- /dev/null +++ b/addons/map_content/materials/glass/glass01opaque.vmt @@ -0,0 +1,10 @@ +LightmappedGeneric +{ +$baseTexture "models\props_windows/window_uban_apt_glass" +$envmap env_cubemap +$envmapmask "models\props_windows\window_uban_apt_glass_ref" +$envmaptint "[1 1 1]" +$surfaceprop glass +$envmapcontrast ".7" + +} \ No newline at end of file diff --git a/addons/map_content/materials/glass/glasswindow007a_nobullets.vmt b/addons/map_content/materials/glass/glasswindow007a_nobullets.vmt new file mode 100644 index 0000000..29c9faf --- /dev/null +++ b/addons/map_content/materials/glass/glasswindow007a_nobullets.vmt @@ -0,0 +1,10 @@ +"LightmappedGeneric" +{ + "$basetexture" "Glass/glasswindow007a" + "$surfaceprop" "glass" + "%keywords" "c17trainstation" + "$translucent" 1 + "$envmapmask" "glass/glasswindow007a_mask" + "$envmap" "env_cubemap" + +} diff --git a/addons/map_content/materials/glass/glasswindow007a_opaque.vmt b/addons/map_content/materials/glass/glasswindow007a_opaque.vmt new file mode 100644 index 0000000..f381503 --- /dev/null +++ b/addons/map_content/materials/glass/glasswindow007a_opaque.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + "$basetexture" "Glass/glasswindow007a" + "$surfaceprop" "glass" + "%keywords" "c17trainstation" + "$envmapmask" "glass/glasswindow007a_mask" + "$envmap" "env_cubemap" + +} diff --git a/addons/map_content/materials/glass/infblindsa_unlit.vmt b/addons/map_content/materials/glass/infblindsa_unlit.vmt new file mode 100644 index 0000000..c83de10 --- /dev/null +++ b/addons/map_content/materials/glass/infblindsa_unlit.vmt @@ -0,0 +1,6 @@ +UnlitGeneric +{ + "$basetexture" "glass\infblindsa" + "$surfaceprop" "plastic" + +} diff --git a/addons/map_content/materials/glass/urban_glass_03.vmt b/addons/map_content/materials/glass/urban_glass_03.vmt new file mode 100644 index 0000000..b4553f7 --- /dev/null +++ b/addons/map_content/materials/glass/urban_glass_03.vmt @@ -0,0 +1,15 @@ +LightmappedGeneric +{ +$basetexture "glass\urban_glass_03" +$translucent 1 +$nocull 1 +$envmap env_cubemap +$envmapmask "glass\urban_glass_03_ref" +$envmaptint "[1 1 1]" +$crackmaterial "glass\offwndwb_break" +lightmappedgeneric_HDR_dx9 +{ +$envmaptint "[.35 .35 .35]" +$crackmaterial "glass\offwndwb_break_hdr" +} +} \ No newline at end of file diff --git a/addons/map_content/materials/glass/urban_glass_03.vtf b/addons/map_content/materials/glass/urban_glass_03.vtf new file mode 100644 index 0000000..36a0361 Binary files /dev/null and b/addons/map_content/materials/glass/urban_glass_03.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009a.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall009a.vmt new file mode 100644 index 0000000..1203367 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall009a.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall009a" + "$surfaceprop" "brick" + "%keywords" "c17downtown" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009a.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall009a.vtf new file mode 100644 index 0000000..2973193 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall009a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009b.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall009b.vmt new file mode 100644 index 0000000..a64a6b2 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall009b.vmt @@ -0,0 +1,12 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall009b" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$bumpmap" "hl2_leak/brick/brickwall009b_normal" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009b.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall009b.vtf new file mode 100644 index 0000000..2f771f1 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall009b.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009b_normal.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall009b_normal.vtf new file mode 100644 index 0000000..8c084b9 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall009b_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009c.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall009c.vmt new file mode 100644 index 0000000..7c06f16 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall009c.vmt @@ -0,0 +1,12 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall009c" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$bumpmap" "hl2_leak/brick/brickwall009c_normal" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009c.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall009c.vtf new file mode 100644 index 0000000..f05c7e1 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall009c.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009c_normal.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall009c_normal.vtf new file mode 100644 index 0000000..40c5858 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall009c_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009d.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall009d.vmt new file mode 100644 index 0000000..edb114a --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall009d.vmt @@ -0,0 +1,12 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall009d" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$bumpmap" "hl2_leak/brick/brickwall009d_normal" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009d.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall009d.vtf new file mode 100644 index 0000000..b3e18ae Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall009d.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall009d_normal.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall009d_normal.vtf new file mode 100644 index 0000000..ede5ffc Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall009d_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015a.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall015a.vmt new file mode 100644 index 0000000..8e38d05 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall015a.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall015a" + "$surfaceprop" "brick" + "%keywords" "c17downtown,wasteland" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015a.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall015a.vtf new file mode 100644 index 0000000..681c319 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall015a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015b.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall015b.vmt new file mode 100644 index 0000000..e9ec332 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall015b.vmt @@ -0,0 +1,12 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall015b" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$bumpmap" "hl2_leak/brick/brickwall015b_normal" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015b.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall015b.vtf new file mode 100644 index 0000000..34615e7 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall015b.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015b_normal.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall015b_normal.vtf new file mode 100644 index 0000000..951d722 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall015b_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015c.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall015c.vmt new file mode 100644 index 0000000..a9b9995 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall015c.vmt @@ -0,0 +1,12 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall015c" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$bumpmap" "hl2_leak/brick/brickwall015c_normal" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015c.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall015c.vtf new file mode 100644 index 0000000..cfb342d Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall015c.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015c_normal.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall015c_normal.vtf new file mode 100644 index 0000000..2cf9eea Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall015c_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015d.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall015d.vmt new file mode 100644 index 0000000..9a32d7d --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall015d.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall015d" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$bumpmap" "hl2_leak/brick/brickwall015d_normal" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015d.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall015d.vtf new file mode 100644 index 0000000..157b2bc Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall015d.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall015d_normal.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall015d_normal.vtf new file mode 100644 index 0000000..31d6151 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall015d_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall016a.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall016a.vmt new file mode 100644 index 0000000..ae16312 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall016a.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall016a" + "$surfaceprop" "brick" + "%keywords" "c17downtown" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall016a.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall016a.vtf new file mode 100644 index 0000000..657349d Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall016a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall016b.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall016b.vmt new file mode 100644 index 0000000..f7fb0bf --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall016b.vmt @@ -0,0 +1,12 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall016b" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$bumpmap" "hl2_leak/brick/brickwall016b_normal" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" + "$envmap" "env_cubemap" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall016b.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall016b.vtf new file mode 100644 index 0000000..edc8e77 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall016b.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall016b_normal.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall016b_normal.vtf new file mode 100644 index 0000000..971a893 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall016b_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall016d.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall016d.vmt new file mode 100644 index 0000000..20c3b3e --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall016d.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall016d" + "$surfaceprop" "brick" + "%keywords" "c17downtown" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall016d.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall016d.vtf new file mode 100644 index 0000000..6f31eea Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall016d.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall031a.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall031a.vmt new file mode 100644 index 0000000..c943ff8 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall031a.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall031a" + "$surfaceprop" "brick" + "%keywords" "c17downtown" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall031a.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall031a.vtf new file mode 100644 index 0000000..84597d0 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall031a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall031c.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall031c.vmt new file mode 100644 index 0000000..bce557c --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall031c.vmt @@ -0,0 +1,11 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall031c" + "$surfaceprop" "brick" + "%keywords" "c17downtown" + "$basealphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" + "$envmap" "env_cubemap" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall031c.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall031c.vtf new file mode 100644 index 0000000..4c52c00 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall031c.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall031d.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall031d.vmt new file mode 100644 index 0000000..430553e --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall031d.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Brick/brickwall031d" + "$surfaceprop" "brick" + "%keywords" "c17downtown" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall031d.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall031d.vtf new file mode 100644 index 0000000..596d5aa Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall031d.vtf differ diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall053d.vmt b/addons/map_content/materials/hl2_leak/brick/brickwall053d.vmt new file mode 100644 index 0000000..8284c5d --- /dev/null +++ b/addons/map_content/materials/hl2_leak/brick/brickwall053d.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/Brick/brickwall053d" + "$surfaceprop" "brick" + "%keywords" "c17downtown" +} diff --git a/addons/map_content/materials/hl2_leak/brick/brickwall053d.vtf b/addons/map_content/materials/hl2_leak/brick/brickwall053d.vtf new file mode 100644 index 0000000..df8696f Binary files /dev/null and b/addons/map_content/materials/hl2_leak/brick/brickwall053d.vtf differ diff --git a/addons/map_content/materials/hl2_leak/building_template/building_template006b.vmt b/addons/map_content/materials/hl2_leak/building_template/building_template006b.vmt new file mode 100644 index 0000000..639248b --- /dev/null +++ b/addons/map_content/materials/hl2_leak/building_template/building_template006b.vmt @@ -0,0 +1,11 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Building_Template/Building_Template006c" + "$surfaceprop" "concrete" + "$bumpmap" "hl2_leak/Building_Template/Building_Template006b_normal" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" +} diff --git a/addons/map_content/materials/hl2_leak/building_template/building_template006b_normal.vtf b/addons/map_content/materials/hl2_leak/building_template/building_template006b_normal.vtf new file mode 100644 index 0000000..94b8265 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/building_template/building_template006b_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/building_template/building_template006c.vmt b/addons/map_content/materials/hl2_leak/building_template/building_template006c.vmt new file mode 100644 index 0000000..25c0da9 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/building_template/building_template006c.vmt @@ -0,0 +1,12 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Building_Template/Building_Template006c" + "$surfaceprop" "concrete" + "$bumpmap" "hl2_leak/Building_Template/Building_Template006b_normal" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" 1 + "$envmapcontrast" 1 + "$envmapsaturation" 1 + "$envmaptint" "[ .5 .5 .5]" + "$translucent" 1 +} diff --git a/addons/map_content/materials/hl2_leak/building_template/building_template006c.vtf b/addons/map_content/materials/hl2_leak/building_template/building_template006c.vtf new file mode 100644 index 0000000..92d3d35 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/building_template/building_template006c.vtf differ diff --git a/addons/map_content/materials/hl2_leak/building_template/building_template011b.vmt b/addons/map_content/materials/hl2_leak/building_template/building_template011b.vmt new file mode 100644 index 0000000..e2f5a60 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/building_template/building_template011b.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Building_Template/Building_Template011b" + "$surfaceprop" "concrete" +} diff --git a/addons/map_content/materials/hl2_leak/building_template/building_template011b.vtf b/addons/map_content/materials/hl2_leak/building_template/building_template011b.vtf new file mode 100644 index 0000000..3aaa82e Binary files /dev/null and b/addons/map_content/materials/hl2_leak/building_template/building_template011b.vtf differ diff --git a/addons/map_content/materials/hl2_leak/decals/decalshipsign004e.vmt b/addons/map_content/materials/hl2_leak/decals/decalshipsign004e.vmt new file mode 100644 index 0000000..b62c8a3 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/decals/decalshipsign004e.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + // Original shader: DecalBaseTimesLightmapAlphaBlend + "$translucent" 1 + "$basetexture" "hl2_leak/decals/decalshipsign004e" + "$decal" 1 + "$decalscale" 0.125 + "%keywords" "borealis" +} diff --git a/addons/map_content/materials/hl2_leak/decals/decalshipsign004e.vtf b/addons/map_content/materials/hl2_leak/decals/decalshipsign004e.vtf new file mode 100644 index 0000000..1b24c0f Binary files /dev/null and b/addons/map_content/materials/hl2_leak/decals/decalshipsign004e.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow015b.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow015b.vmt new file mode 100644 index 0000000..30789d0 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow015b.vmt @@ -0,0 +1,8 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmapSelfIllum + "$basetexture" "hl2_leak/Glass/glasswindow015b" + "$selfillum" 1 + "$surfaceprop" "glass" + "%keywords" "c17industrial,wasteland" +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow015b.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow015b.vtf new file mode 100644 index 0000000..1634064 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow015b.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow036a.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow036a.vmt new file mode 100644 index 0000000..3d762d5 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow036a.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/glass/glasswindow036a" + "$surfaceprop" "glass" + "%keywords" "c17industrial" + "$translucent" 1 +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow036a.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow036a.vtf new file mode 100644 index 0000000..bfaf96e Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow036a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066a.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow066a.vtf new file mode 100644 index 0000000..9998d86 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow066a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066a_mask.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow066a_mask.vtf new file mode 100644 index 0000000..96c968e Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow066a_mask.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066b.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow066b.vmt new file mode 100644 index 0000000..289ee48 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow066b.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + // Original shader: WorldAdditiveTransEnvMapWithMaskedTexture + "$basetexture" "hl2_leak/Glass/glasswindow066a" + "$envmap" "env_cubemap" + "$surfaceprop" "glass" + "%keywords" "c17industrial" + "$envmapmask" "hl2_leak/glass/glasswindow066a_mask" +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066f.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow066f.vmt new file mode 100644 index 0000000..9bf73e8 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow066f.vmt @@ -0,0 +1,10 @@ +"LightmappedGeneric" +{ + // Original shader: WorldAdditiveTransEnvMapWithMaskedTexture + "$basetexture" "hl2_leak/Glass/glasswindow066f" + "$envmap" "env_cubemap" + "$surfaceprop" "glass" + "%keywords" "c17industrial" + "$envmapmask" "hl2_leak/glass/glasswindow066f_mask" + "$translucent" 1 +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066f.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow066f.vtf new file mode 100644 index 0000000..37d0adc Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow066f.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066f_mask.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow066f_mask.vtf new file mode 100644 index 0000000..ad0ca30 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow066f_mask.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066f_o.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow066f_o.vmt new file mode 100644 index 0000000..9e46e44 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow066f_o.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + // Original shader: WorldAdditiveTransEnvMapWithMaskedTexture + "$basetexture" "hl2_leak/Glass/glasswindow066f" + "$envmap" "env_cubemap" + "$surfaceprop" "glass" + "%keywords" "c17industrial" + "$envmapmask" "hl2_leak/glass/glasswindow066f_mask" +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066g.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow066g.vmt new file mode 100644 index 0000000..dfe280d --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow066g.vmt @@ -0,0 +1,9 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Glass/glasswindow066g" + "$envmap" "env_cubemap" + "$surfaceprop" "glass" + "%keywords" "c17industrial" + "$envmapmask" "hl2_leak/glass/glasswindow066g_mask" + "$translucent" 1 +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066g.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow066g.vtf new file mode 100644 index 0000000..3bda90f Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow066g.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066g_mask.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow066g_mask.vtf new file mode 100644 index 0000000..3117728 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow066g_mask.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow066g_o.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow066g_o.vmt new file mode 100644 index 0000000..5d6caca --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow066g_o.vmt @@ -0,0 +1,8 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Glass/glasswindow066g" + "$envmap" "env_cubemap" + "$surfaceprop" "glass" + "%keywords" "c17industrial" + "$envmapmask" "hl2_leak/glass/glasswindow066g_mask" +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow067a.vmt b/addons/map_content/materials/hl2_leak/glass/glasswindow067a.vmt new file mode 100644 index 0000000..540be2f --- /dev/null +++ b/addons/map_content/materials/hl2_leak/glass/glasswindow067a.vmt @@ -0,0 +1,10 @@ +"LightmappedGeneric" +{ + // Original shader: WorldAdditiveTransEnvMapWithMaskedTexture + "$basetexture" "hl2_leak/glass/glasswindow067a" + "$envmap" "env_cubemap" + "$surfaceprop" "glass" + "%keywords" "c17downtown,c17trainstation" + "$envmapmask" "hl2_leak/glass/glasswindow067a_mask" + "$translucent" 1 +} diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow067a.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow067a.vtf new file mode 100644 index 0000000..e629995 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow067a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/glass/glasswindow067a_mask.vtf b/addons/map_content/materials/hl2_leak/glass/glasswindow067a_mask.vtf new file mode 100644 index 0000000..188d50e Binary files /dev/null and b/addons/map_content/materials/hl2_leak/glass/glasswindow067a_mask.vtf differ diff --git a/addons/map_content/materials/hl2_leak/metal/metalhull009b.vmt b/addons/map_content/materials/hl2_leak/metal/metalhull009b.vmt new file mode 100644 index 0000000..3da0cd9 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/metal/metalhull009b.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/Metal/metalhull009b" + "$surfaceprop" "metal" + "%keywords" "borealis" +} diff --git a/addons/map_content/materials/hl2_leak/metal/metalhull009b.vtf b/addons/map_content/materials/hl2_leak/metal/metalhull009b.vtf new file mode 100644 index 0000000..1f64cfe Binary files /dev/null and b/addons/map_content/materials/hl2_leak/metal/metalhull009b.vtf differ diff --git a/addons/map_content/materials/hl2_leak/metal/metalhull009c.vmt b/addons/map_content/materials/hl2_leak/metal/metalhull009c.vmt new file mode 100644 index 0000000..994154d --- /dev/null +++ b/addons/map_content/materials/hl2_leak/metal/metalhull009c.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/Metal/metalhull009c" + "$surfaceprop" "metal" + "%keywords" "borealis" +} diff --git a/addons/map_content/materials/hl2_leak/metal/metalhull009c.vtf b/addons/map_content/materials/hl2_leak/metal/metalhull009c.vtf new file mode 100644 index 0000000..87c8866 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/metal/metalhull009c.vtf differ diff --git a/addons/map_content/materials/hl2_leak/props/carpetfloor005a.vmt b/addons/map_content/materials/hl2_leak/props/carpetfloor005a.vmt new file mode 100644 index 0000000..506a335 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/props/carpetfloor005a.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/props/carpetfloor005a" + "$surfaceprop" "carpet" +} diff --git a/addons/map_content/materials/hl2_leak/props/carpetfloor005a.vtf b/addons/map_content/materials/hl2_leak/props/carpetfloor005a.vtf new file mode 100644 index 0000000..d22f5fe Binary files /dev/null and b/addons/map_content/materials/hl2_leak/props/carpetfloor005a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/props/elevatorbutton003a.vmt b/addons/map_content/materials/hl2_leak/props/elevatorbutton003a.vmt new file mode 100644 index 0000000..18c0648 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/props/elevatorbutton003a.vmt @@ -0,0 +1,8 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmapSelfIllum + "$basetexture" "hl2_leak/props/elevatorbutton003a" + "$selfillum" 1 + "$surfaceprop" "metalpanel" + "%keywords" "c17skyscraper" +} diff --git a/addons/map_content/materials/hl2_leak/props/elevatorbutton003a.vtf b/addons/map_content/materials/hl2_leak/props/elevatorbutton003a.vtf new file mode 100644 index 0000000..b9cf471 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/props/elevatorbutton003a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonefloor009a.vmt b/addons/map_content/materials/hl2_leak/stone/stonefloor009a.vmt new file mode 100644 index 0000000..5236c03 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonefloor009a.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Stone/stonefloor009a" + "$surfaceprop" "concrete" + "%keywords" "c17downtown,wasteland" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonefloor009a.vtf b/addons/map_content/materials/hl2_leak/stone/stonefloor009a.vtf new file mode 100644 index 0000000..999117c Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonefloor009a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonefloor009a_normal.vtf b/addons/map_content/materials/hl2_leak/stone/stonefloor009a_normal.vtf new file mode 100644 index 0000000..05e8a94 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonefloor009a_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall030a.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall030a.vmt new file mode 100644 index 0000000..61e1d78 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall030a.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/stone/stonewall030a" + "$surfaceprop" "concrete" + "%keywords" "c17skyscraper" + "$bumpmap" "hl2_leak/stone/stonewall030a_normal" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall030a.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall030a.vtf new file mode 100644 index 0000000..90a10a4 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall030a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall030a_normal.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall030a_normal.vtf new file mode 100644 index 0000000..7c08044 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall030a_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall030b.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall030b.vmt new file mode 100644 index 0000000..1e729e8 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall030b.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/stone/stonewall030b" + "$surfaceprop" "concrete" + "%keywords" "c17skyscraper" + "$bumpmap" "hl2_leak/stone/stonewall030b_normal" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall030b.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall030b.vtf new file mode 100644 index 0000000..8000d8c Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall030b.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall030b_normal.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall030b_normal.vtf new file mode 100644 index 0000000..156ff53 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall030b_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall031a.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall031a.vmt new file mode 100644 index 0000000..71f29e0 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall031a.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/stone/stonewall031a" + "$surfaceprop" "concrete" + "%keywords" "c17skyscraper" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall031a.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall031a.vtf new file mode 100644 index 0000000..15672b4 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall031a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall035a.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall035a.vmt new file mode 100644 index 0000000..6ca5858 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall035a.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/stone/stonewall035a" + "$surfaceprop" "concrete" + "%keywords" "c17downtown,wasteland" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall035a.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall035a.vtf new file mode 100644 index 0000000..8705673 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall035a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall037a.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall037a.vmt new file mode 100644 index 0000000..9943607 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall037a.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/stone/stonewall037a" + "$surfaceprop" "concrete" + "%keywords" "c17downtown,wasteland" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall037a.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall037a.vtf new file mode 100644 index 0000000..bd4d683 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall037a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall037d.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall037d.vmt new file mode 100644 index 0000000..10f5808 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall037d.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/stone/stonewall037d" + "$surfaceprop" "concrete" + "%keywords" "c17downtown,wasteland" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall037d.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall037d.vtf new file mode 100644 index 0000000..8c7124e Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall037d.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall037g.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall037g.vmt new file mode 100644 index 0000000..ec14500 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall037g.vmt @@ -0,0 +1,7 @@ +"LightmappedGeneric" +{ + // Original shader: BaseTimesLightmap + "$basetexture" "hl2_leak/stone/stonewall037g" + "$surfaceprop" "concrete" + "%keywords" "c17downtown,wasteland" +} diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall037g.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall037g.vtf new file mode 100644 index 0000000..7f170b7 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall037g.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall045k.vmt b/addons/map_content/materials/hl2_leak/stone/stonewall045k.vmt new file mode 100644 index 0000000..f36de70 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/stone/stonewall045k.vmt @@ -0,0 +1,8 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/stone/stonewall045k" + "$surfaceprop" "concrete" + "$bumpmap" "hl2_leak/stone/stonewall045k_normal" + "%keywords" "c17downtown" +} + diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall045k.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall045k.vtf new file mode 100644 index 0000000..1527c41 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall045k.vtf differ diff --git a/addons/map_content/materials/hl2_leak/stone/stonewall045k_normal.vtf b/addons/map_content/materials/hl2_leak/stone/stonewall045k_normal.vtf new file mode 100644 index 0000000..fbef665 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/stone/stonewall045k_normal.vtf differ diff --git a/addons/map_content/materials/hl2_leak/wood/woodwall033.vmt b/addons/map_content/materials/hl2_leak/wood/woodwall033.vmt new file mode 100644 index 0000000..91c86cb --- /dev/null +++ b/addons/map_content/materials/hl2_leak/wood/woodwall033.vmt @@ -0,0 +1,8 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Wood/woodwall033a" + "$surfaceprop" "wood" + "%keywords" "c17downtown" + "$envmap" "env_cubemap" + $basealphaenvmapmask 1 +} diff --git a/addons/map_content/materials/hl2_leak/wood/woodwall033a.vtf b/addons/map_content/materials/hl2_leak/wood/woodwall033a.vtf new file mode 100644 index 0000000..efa4e4d Binary files /dev/null and b/addons/map_content/materials/hl2_leak/wood/woodwall033a.vtf differ diff --git a/addons/map_content/materials/hl2_leak/wood/woodwall033d.vmt b/addons/map_content/materials/hl2_leak/wood/woodwall033d.vmt new file mode 100644 index 0000000..4381004 --- /dev/null +++ b/addons/map_content/materials/hl2_leak/wood/woodwall033d.vmt @@ -0,0 +1,6 @@ +"LightmappedGeneric" +{ + "$basetexture" "hl2_leak/Wood/woodwall033d" + "$surfaceprop" "wood" + "%keywords" "c17downtown" +} diff --git a/addons/map_content/materials/hl2_leak/wood/woodwall033d.vtf b/addons/map_content/materials/hl2_leak/wood/woodwall033d.vtf new file mode 100644 index 0000000..27c22a9 Binary files /dev/null and b/addons/map_content/materials/hl2_leak/wood/woodwall033d.vtf differ diff --git a/addons/map_content/materials/kzmod/starttimersign.vmt b/addons/map_content/materials/kzmod/starttimersign.vmt new file mode 100644 index 0000000..4fb2c4a --- /dev/null +++ b/addons/map_content/materials/kzmod/starttimersign.vmt @@ -0,0 +1,9 @@ +Sprite +{ + "$basetexture" "kzmod/starttimersign" + "$spriteorientation" "oriented" + "$spriteorigin" "[ 0.50 0.50 ]" + + "$nocull" 1 + "$translucent" "1" +} diff --git a/addons/map_content/materials/kzmod/starttimersign.vtf b/addons/map_content/materials/kzmod/starttimersign.vtf new file mode 100644 index 0000000..a00511d Binary files /dev/null and b/addons/map_content/materials/kzmod/starttimersign.vtf differ diff --git a/addons/map_content/materials/kzmod/stoptimersign.vmt b/addons/map_content/materials/kzmod/stoptimersign.vmt new file mode 100644 index 0000000..fc949ee --- /dev/null +++ b/addons/map_content/materials/kzmod/stoptimersign.vmt @@ -0,0 +1,8 @@ +Sprite +{ + "$basetexture" "kzmod/stoptimersign" + "$spriteorientation" "oriented" + "$spriteorigin" "[ 0.50 0.50 ]" + + "$nocull" 1 +} diff --git a/addons/map_content/materials/kzmod/stoptimersign.vtf b/addons/map_content/materials/kzmod/stoptimersign.vtf new file mode 100644 index 0000000..1b656b9 Binary files /dev/null and b/addons/map_content/materials/kzmod/stoptimersign.vtf differ diff --git a/addons/map_content/materials/mazur/blend_grass_forestdirt.vmt b/addons/map_content/materials/mazur/blend_grass_forestdirt.vmt new file mode 100644 index 0000000..ce7ba58 --- /dev/null +++ b/addons/map_content/materials/mazur/blend_grass_forestdirt.vmt @@ -0,0 +1,8 @@ +"WorldVertexTransition" +{ + "$basetexture2" "nature/forest_grass_01" + "%detailtype" "alley_grass1" + "$basetexture" "nature/forest_dirt_04" + "$surfaceprop2" "grass" + "$surfaceprop" "dirt" +} diff --git a/addons/map_content/materials/metal/metalwall021e_lit.vmt b/addons/map_content/materials/metal/metalwall021e_lit.vmt new file mode 100644 index 0000000..11eaa6d --- /dev/null +++ b/addons/map_content/materials/metal/metalwall021e_lit.vmt @@ -0,0 +1,14 @@ +// envmaptint_fix +"LightmappedGeneric" +{ + "$basetexture" "Metal/metalwall021e_lit" + "$surfaceprop" "metal" + "$translucent" 1 + "$detail" "detail\metal_detail_01" + "$detailscale" "4.283" + "$detailblendfactor" .8 + "$detailblendmode" "0" + + "%keywords" "c17industrial,wasteland" + "$bumpmap" "Metal/metalwall018e_normal" +} diff --git a/addons/map_content/materials/metal/metalwall021e_lit.vtf b/addons/map_content/materials/metal/metalwall021e_lit.vtf new file mode 100644 index 0000000..5c17131 Binary files /dev/null and b/addons/map_content/materials/metal/metalwall021e_lit.vtf differ diff --git a/addons/map_content/materials/models/gibs/furniture_gibs/furnituretable001a.vmt b/addons/map_content/materials/models/gibs/furniture_gibs/furnituretable001a.vmt new file mode 100644 index 0000000..bde7037 --- /dev/null +++ b/addons/map_content/materials/models/gibs/furniture_gibs/furnituretable001a.vmt @@ -0,0 +1,8 @@ +VertexLitGeneric +{ +$basetexture "Models/props_c17/FurnitureTable001a" +$envmap env_cubemap +$basealphaenvmapmask 1 +$envmaptint .2 .2 .2 + +} diff --git a/addons/map_content/materials/models/gibs/furniture_gibs/furniturewooddrawers001a.vmt b/addons/map_content/materials/models/gibs/furniture_gibs/furniturewooddrawers001a.vmt new file mode 100644 index 0000000..8f71833 --- /dev/null +++ b/addons/map_content/materials/models/gibs/furniture_gibs/furniturewooddrawers001a.vmt @@ -0,0 +1,4 @@ +VertexLitGeneric +{ +$basetexture "models/props_furniture/FurnitureWoodDrawers001a" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox.vmt new file mode 100644 index 0000000..887595a --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox.vmt @@ -0,0 +1,5 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox" + "$bumpmap" "models/models_kit/xmas/giftbox_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox.vtf new file mode 100644 index 0000000..e5ba9b4 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_norm.vtf new file mode 100644 index 0000000..cf018e5 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower.vmt new file mode 100644 index 0000000..37592cd --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower.vmt @@ -0,0 +1,5 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_flower" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_flower_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower.vtf new file mode 100644 index 0000000..58c2cf5 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower_norm.vtf new file mode 100644 index 0000000..1980be0 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_flower_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala.vmt new file mode 100644 index 0000000..3fa7a1e --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_specialA" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_specialA_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala.vtf new file mode 100644 index 0000000..fcac23d Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala_norm.vtf new file mode 100644 index 0000000..b9ca08c Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciala_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb.vmt new file mode 100644 index 0000000..20d92b8 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_specialB" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_specialB_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb.vtf new file mode 100644 index 0000000..a49cfeb Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb_norm.vtf new file mode 100644 index 0000000..8a66dbc Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialb_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc.vmt new file mode 100644 index 0000000..8c595ce --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_specialC" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_specialC_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc.vtf new file mode 100644 index 0000000..b8e48e8 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc_norm.vtf new file mode 100644 index 0000000..c85463f Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_specialc_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald.vmt new file mode 100644 index 0000000..3aff81e --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_specialD" + "$envmap" "env_cubemap" + "$normalmapalphaenvmapmask" "1" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_specialD_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald.vtf new file mode 100644 index 0000000..2cd9684 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald_norm.vtf new file mode 100644 index 0000000..6c0ed63 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_speciald_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag.vmt new file mode 100644 index 0000000..05e675d --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag.vtf new file mode 100644 index 0000000..2f82086 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_norm.vtf new file mode 100644 index 0000000..8b69487 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2.vmt new file mode 100644 index 0000000..d8fe01d --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin2" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_skin2_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2.vtf new file mode 100644 index 0000000..55e8c8d Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2_norm.vtf new file mode 100644 index 0000000..bcf66d1 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin2_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3.vmt new file mode 100644 index 0000000..d9fc634 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin3" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_skin3_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3.vtf new file mode 100644 index 0000000..e55501c Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3_norm.vtf new file mode 100644 index 0000000..a6a8ebf Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin3_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin4.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin4.vmt new file mode 100644 index 0000000..9dd9113 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin4.vmt @@ -0,0 +1,4 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin4" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin4.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin4.vtf new file mode 100644 index 0000000..7ee74e1 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin4.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5.vmt new file mode 100644 index 0000000..c11e039 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin5" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_skin5_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5.vtf new file mode 100644 index 0000000..e753998 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5_norm.vtf new file mode 100644 index 0000000..dc7cef2 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin5_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6.vmt new file mode 100644 index 0000000..af06d48 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin6" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_skin6_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6.vtf new file mode 100644 index 0000000..f0ba3cc Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6_norm.vtf new file mode 100644 index 0000000..8481fd4 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin6_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7.vmt new file mode 100644 index 0000000..6a5a63d --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin7" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_skin7_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7.vtf new file mode 100644 index 0000000..4908d55 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7_norm.vtf new file mode 100644 index 0000000..864f4b2 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin7_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8.vmt new file mode 100644 index 0000000..bc21a66 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin8" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_skin8_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8.vtf new file mode 100644 index 0000000..7f4179d Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8_norm.vtf new file mode 100644 index 0000000..521de43 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin8_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9.vmt new file mode 100644 index 0000000..834177d --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_ribbon_tag_skin9" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_ribbon_tag_skin9_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9.vtf new file mode 100644 index 0000000..39e3cf8 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9_norm.vtf new file mode 100644 index 0000000..9fc5e1e Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_ribbon_tag_skin9_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2.vmt new file mode 100644 index 0000000..509a160 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_skin2" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_skin2_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2.vtf new file mode 100644 index 0000000..efc4abb Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2_norm.vtf new file mode 100644 index 0000000..46cebd3 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin2_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3.vmt new file mode 100644 index 0000000..078a354 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_skin3" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_skin3_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3.vtf new file mode 100644 index 0000000..194b60d Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3_norm.vtf new file mode 100644 index 0000000..40b8bcd Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin3_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4.vmt new file mode 100644 index 0000000..eb9cd58 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_skin4" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_skin4_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4.vtf new file mode 100644 index 0000000..66b2d99 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4_norm.vtf new file mode 100644 index 0000000..ce3a534 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin4_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5.vmt b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5.vmt new file mode 100644 index 0000000..49052c0 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5.vmt @@ -0,0 +1,7 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/giftbox_skin5" + "$normalmapalphaenvmapmask" "1" + "$envmap" "env_cubemap" + "$bumpmap" "models/models_kit/xmas/giftbox_skin5_NORM" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5.vtf new file mode 100644 index 0000000..7192119 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5_norm.vtf b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5_norm.vtf new file mode 100644 index 0000000..ea10df6 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/giftbox_skin5_norm.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_misca.vmt b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca.vmt new file mode 100644 index 0000000..352473d --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca.vmt @@ -0,0 +1,6 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/xmastree_miscA" + "$alphatest" "1" + "$nocull" "1" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_misca.vtf b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca.vtf new file mode 100644 index 0000000..ccd2b31 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_misca_skin2.vmt b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca_skin2.vmt new file mode 100644 index 0000000..e18f422 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca_skin2.vmt @@ -0,0 +1,6 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/xmastree_miscA_skin2" + "$alphatest" "1" + "$nocull" "1" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_misca_skin2.vtf b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca_skin2.vtf new file mode 100644 index 0000000..ea30bb6 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/xmastree_misca_skin2.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb.vmt b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb.vmt new file mode 100644 index 0000000..f936d63 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb.vmt @@ -0,0 +1,6 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/xmastree_miscB" + "$envmap" "env_cubemap" + "$envmapmask" "models/models_kit/xmas/xmastree_miscB_spec" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb.vtf b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb.vtf new file mode 100644 index 0000000..6c4c5c0 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_skin2.vmt b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_skin2.vmt new file mode 100644 index 0000000..0669e05 --- /dev/null +++ b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_skin2.vmt @@ -0,0 +1,6 @@ +"vertexlitGeneric" +{ + "$basetexture" "models/models_kit/xmas/xmastree_miscB_skin2" + "$envmap" "env_cubemap" + "$envmapmask" "models/models_kit/xmas/xmastree_miscB_spec" +} diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_skin2.vtf b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_skin2.vtf new file mode 100644 index 0000000..e0f3345 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_skin2.vtf differ diff --git a/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_spec.vtf b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_spec.vtf new file mode 100644 index 0000000..9d36b08 Binary files /dev/null and b/addons/map_content/materials/models/models_kit/xmas/xmastree_miscb_spec.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow1.vmt new file mode 100644 index 0000000..c996202 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow1.vmt @@ -0,0 +1,18 @@ +"VertexlitGeneric" +{ + $baseTexture "models\props\de_austria\foliage\austria_branches_snow1" + $AlphaTest 1 + $AlphaTestReference .3 + $nocull 1 + $model 1 + $FlashlightNoLambert 1 + %compilepassbullets 1 + $DISABLECSMLOOKUP 1 + + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 10 + $detailblendfactor 1 +} + +//small bushes (bush_snow_64, bush_snow_128) \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow1.vtf new file mode 100644 index 0000000..b860765 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow2.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow2.vmt new file mode 100644 index 0000000..65eef51 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow2.vmt @@ -0,0 +1,18 @@ +VertexlitGeneric +{ + $baseTexture "models\props\de_austria\foliage\austria_branches_snow2" + //$baseTexture "models\props_foliage\Urban_Trees_branches02_mip0" + $Alphatest 1 + $AlphaTestReference ".5" + $nocull 1 + $model 1 + $FlashlightNoLambert 1 + + "$DISABLECSMLOOKUP" "1" + + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 10 + $detailblendfactor 1 +} +//vine (vine_snow_001, vine_snow_002) \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow2.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow2.vtf new file mode 100644 index 0000000..931f056 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_branches_snow2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_bush_snow1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_bush_snow1.vmt new file mode 100644 index 0000000..d50dc3d --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_bush_snow1.vmt @@ -0,0 +1,13 @@ +"VertexLitGeneric" +{ + $basetexture "models\props\de_austria\foliage\austria_bush_snow1" + $alphatest 1 + $nocull 1 + $model 1 + + $DISABLECSMLOOKUP "1" + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 5 + $detailblendfactor 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_bush_snow1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_bush_snow1.vtf new file mode 100644 index 0000000..387c865 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_bush_snow1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_grass_frozen1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_grass_frozen1.vmt new file mode 100644 index 0000000..625b158 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_grass_frozen1.vmt @@ -0,0 +1,15 @@ +"VertexlitGeneric" +{ + $baseTexture "models/props/de_austria/foliage/austria_grass_frozen1" + $AlphaTest 1 + $AlphaTestReference .3 + $nocull 1 + $model 1 + $FlashlightNoLambert 1 + %compilepassbullets 1 + $DISABLECSMLOOKUP 1 + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 1 + $detailblendfactor 1 +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_grass_frozen1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_grass_frozen1.vtf new file mode 100644 index 0000000..5136b27 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_grass_frozen1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_hedge_snow1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_hedge_snow1.vmt new file mode 100644 index 0000000..a81b86b --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_hedge_snow1.vmt @@ -0,0 +1,14 @@ +VertexLitGeneric +{ + $basetexture "models\props\de_austria\foliage\austria_branches_snow1" + $alphatest 1 + $alphatestreference .3 + $nocull 1 + $model 1 + $FlashlightNoLambert 1 + $treeSwayHeight 200 + $treeSwayRadius 100 + + + $DISABLECSMLOOKUP 1 +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark1.vmt new file mode 100644 index 0000000..13f798a --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark1.vmt @@ -0,0 +1,8 @@ +VertexLitGeneric +{ + "$baseTexture" "models\props\de_austria\foliage\austria_ivy_bark1.vtf" + + "$detail" "detail\dt_wood1" + "$detailscale" "[4 4 4]" + "$detailblendfactor" "2" +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark1.vtf new file mode 100644 index 0000000..667d3f2 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark2.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark2.vmt new file mode 100644 index 0000000..16e8c58 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark2.vmt @@ -0,0 +1,9 @@ +VertexLitGeneric +{ + "$baseTexture" "models\props\de_austria\foliage\austria_ivy_bark2.vtf" + + + "$detail" "detail\dt_wood1" + "$detailscale" "[4 4 4]" + "$detailblendfactor" "2" +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark2.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark2.vtf new file mode 100644 index 0000000..886bbf2 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_bark2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_snow1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_snow1.vmt new file mode 100644 index 0000000..c461aa2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_snow1.vmt @@ -0,0 +1,25 @@ +VertexLitGeneric +{ + $baseTexture "models\props\de_austria\foliage\austria_ivy_snow1.vtf" + $alphatest 1 + $bumpmap "models\props\de_train\hr_t\ivy_a\ivy_a_normals.vtf" + $nocull 1 + "$normalmapalphaenvmapmask" 1 + +// "$phong" "1" +// "$phongboost" "2" +// "$phongalbedoboost" "60" +// "$phongfresnelranges" "[.9 .9 1]" +// "$phongexponent" 60 +// "$phongalbedotint" "1" +// "$phongdisablehalflambert" "1" + + +// $detail "de_austria/snow/austria_snow_detail1" +// $detailscale 2 +// $detailblendfactor 1 + + + "$envmap" "env_cubemap" + "$envmaptint" "[.05 .05 .05]" +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_snow1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_snow1.vtf new file mode 100644 index 0000000..3418111 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_ivy_snow1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_pine1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine1.vmt new file mode 100644 index 0000000..af46cac --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine1.vmt @@ -0,0 +1,28 @@ +"VertexlitGeneric" +{ + $basetexture "models\props\de_austria\foliage\austria_pine1" + //"$detail" "detail\noise_detail_01" + //"$detailscale" "[ 6 6 ]" + $detailblendfactor ".5" + //"$bumpmap" "models\props\de_austria\tree_snow_001\pine_a_normals" + $AlphaTest 1 + $AlphaTestReference .666 + $nocull 1 + $FlashlightNoLambert 1 + $model 1 + $treeSway "1" // height at which the effect is on fully (in world units) + $treeSwayHeight "300" // radius at which the effect is on fully (in world units) + $treeSwayStartHeight ".5" // portion of height at which the effect starts to fade in (0-1) + $treeSwayRadius "200" // radius at which the effect is on fully (in world units) + $treeSwayStartRadius "0" // portion of radius at which the effect starts to fade in (0-1) + $treeSwaySpeed ".2" // how quickly the tree sways (as a multiplier of time) + $treeSwayStrength ".4" // how much the tree sways (as a multiplier of position) + $treeSwayScrumbleSpeed ".2" // how quickly the leaves move (as a multiplier of time) + $treeSwayScrumbleStrength ".4" // how much the leaves move( as a multiplier of position) + $treeSwayScrumbleFrequency "2" // spatial frequency of sine wave applied to leaves. Typically high-ish (10-25) + $treeSwayFalloffExp "32" // falloff parameter for wave motion on branches/trunk, higher means core of tree is more stable + $treeSwayScrumbleFalloffExp "32" // falloff parameter for scrumble motion on leaves, higher means the core of the tree is more stable + $treeSwaySpeedHighWindMultiplier "0" // multiplier of movement speed at higher wind + $treeSwaySpeedLerpStart "200.0" // wind speed at which high wind multiplier begins to have effect + $treeSwaySpeedLerpEnd "600.0" // wind speed at which high wind multiplier is fully on +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_pine1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine1.vtf new file mode 100644 index 0000000..c358bee Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_pine_bark1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine_bark1.vmt new file mode 100644 index 0000000..1492e31 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine_bark1.vmt @@ -0,0 +1,8 @@ +"VertexlitGeneric" +{ + $basetexture "models\props\de_austria\foliage\austria_pine_bark1" + $detail "detail\noise_detail_01" + $detailscale "[ 6 6 ]" + $detailblendfactor .5 + $bumpmap "models\props\de_cbble\pine_a\pine_a_bark_normals.vtf" +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_pine_bark1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine_bark1.vtf new file mode 100644 index 0000000..f579c2a Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_pine_bark1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1.vmt b/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1.vmt new file mode 100644 index 0000000..5dd7303 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1.vmt @@ -0,0 +1,29 @@ +VertexlitGeneric +{ + $baseTexture "models\props\de_austria\foliage\austria_tree_snow1" + //$bumpmap "models\props\de_austria\foliage\austria_tree_snow1_n" + + + //"$normalmapalphaenvmapmask" 1 + //"$phong" " 1" + $phongBoost 10 + $phongExponent 30 + $phongfresnelranges "[0.8 0.8 1]" + $phongalbedotint 1 + //"$phongtint" "[.9 1 1]" + "$phongdisablehalflambert" "1" + + $AlphaTest 1 + $AlphaTestReference .3 + $nocull 1 + $model 1 + + $vertexfog 1 + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 10 + $detailblendfactor 1 + + + +} diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1.vtf new file mode 100644 index 0000000..6312fbc Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1_normal.vtf b/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1_normal.vtf new file mode 100644 index 0000000..78c3c34 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/foliage/austria_tree_snow1_normal.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_bench_001.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_bench_001.vmt new file mode 100644 index 0000000..9baadb9 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_bench_001.vmt @@ -0,0 +1,14 @@ +VertexLitGeneric +{ + $basetexture "models\props\de_austria\misc\austria_bench_001" + $bumpmap "models\props\de_inferno\stone_bench_normal" + //$bumpmap "models\props\de_austria\misc\austria_bench_001_n" + $surfaceprop brick + + + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 8 + $detailblendfactor .7 + $detailblendmode 0 +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_bench_001.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_bench_001.vtf new file mode 100644 index 0000000..4c0f0f4 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_bench_001.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_flag1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_flag1.vmt new file mode 100644 index 0000000..6e298db --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_flag1.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/misc/austria_flag1" + $surfaceprop carpet +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_flag1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_flag1.vtf new file mode 100644 index 0000000..080bb34 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_flag1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_flag2.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_flag2.vmt new file mode 100644 index 0000000..6f4d07b --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_flag2.vmt @@ -0,0 +1,5 @@ +"VertexLitGeneric" +{ + $basetexture "models/props/de_austria/misc/austria_flag2" + $surfaceprop carpet +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_flag2.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_flag2.vtf new file mode 100644 index 0000000..22b414d Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_flag2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_flag3.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_flag3.vmt new file mode 100644 index 0000000..4d9aec4 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_flag3.vmt @@ -0,0 +1,6 @@ +"VertexLitGeneric" +{ + $basetexturetransform "center 0.5 0.5 scale -1.0 1.0 rotate 90.0 translate 0.0 0.0" + $basetexture "models/props/de_austria/misc/austria_flag3" + $surfaceprop carpet +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_flag3.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_flag3.vtf new file mode 100644 index 0000000..627cfea Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_flag3.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_fountain_snow1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_fountain_snow1.vtf new file mode 100644 index 0000000..5d722e8 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_fountain_snow1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_gold2.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_gold2.vmt new file mode 100644 index 0000000..dac4a91 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_gold2.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + "$basetexture" "de_austria/misc/austria_gold2" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_iron1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_iron1.vmt new file mode 100644 index 0000000..0cfe1c2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_iron1.vmt @@ -0,0 +1,4 @@ +VertexLitGeneric +{ + $basetexture "models/props/de_austria/misc/austria_iron1" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_iron1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_iron1.vtf new file mode 100644 index 0000000..b185d30 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_iron1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_pole1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_pole1.vmt new file mode 100644 index 0000000..88dd28c --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_pole1.vmt @@ -0,0 +1,6 @@ +"vertexlitgeneric" +{ + "$baseTexture" "wood/vostok_wood" +// "$bumpmap" "models\props\de_austria\misc\austria_lamp_pole1_n" + "$surfaceprop" "wood" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_pole1_n.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_pole1_n.vtf new file mode 100644 index 0000000..43d2d22 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_pole1_n.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post.vmt new file mode 100644 index 0000000..36827e0 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post.vmt @@ -0,0 +1,13 @@ +"VertexLitGeneric" +{ + $basetexture "models/props/de_austria/misc/austria_lamp_post" + $bumpmap "models/props/de_austria/misc/austria_lamp_post_n" + + $phong 1 + $phongfresnelranges "[0.8 1.7 3.1]" + $halflambert 1 + $phongexponent 16 + $phongboost 1 + + $selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post.vtf new file mode 100644 index 0000000..75010dc Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post_n.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post_n.vtf new file mode 100644 index 0000000..43d2d22 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_lamp_post_n.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1.vmt new file mode 100644 index 0000000..4f74bb0 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1.vmt @@ -0,0 +1,19 @@ +VertexLitGeneric +{ + $basetexture "models\props\de_austria\misc\austria_logpile_cloth_snow1" + $bumpmap "models\props\de_austria\misc\austria_logpile_cloth_snow1_n" + $envmap env_cubemap + $normalmapalphaenvmapmask 1 + $nocull 1 + + $phong 1 + $phongboost 2 + $phongfresnelranges "[.9 .9 1]" + $phongexponent 31 + $phongalbedotint 1 + $phongdisablehalflambert 1 + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale "[ 6 4 ]" + $detailblendfactor .7 +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1.vtf new file mode 100644 index 0000000..db7aaf1 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1_n.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1_n.vtf new file mode 100644 index 0000000..f4a739f Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_logpile_cloth_snow1_n.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_mailbox1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_mailbox1.vmt new file mode 100644 index 0000000..0df9d02 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_mailbox1.vmt @@ -0,0 +1,4 @@ +VertexLitGeneric +{ + "$basetexture" "models/props/de_austria/misc/austria_mailbox1" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_mailbox1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_mailbox1.vtf new file mode 100644 index 0000000..f90831c Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_mailbox1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_menu1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_menu1.vmt new file mode 100644 index 0000000..930e1f2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_menu1.vmt @@ -0,0 +1,5 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models/props/de_austria/misc/austria_menu1" + "$surfaceprop" "metal" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_menu1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_menu1.vtf new file mode 100644 index 0000000..b446839 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_menu1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_menu2.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_menu2.vmt new file mode 100644 index 0000000..231069e --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_menu2.vmt @@ -0,0 +1,5 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models/props/de_austria/misc/austria_menu2" + "$surfaceprop" "metal" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_menu2.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_menu2.vtf new file mode 100644 index 0000000..03c076a Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_menu2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_paper1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_paper1.vmt new file mode 100644 index 0000000..8ba85f5 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_paper1.vmt @@ -0,0 +1,8 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/misc/austria_paper1" + $bumpmap "models/props/de_austria/misc/austria_paper1_n" + $surfaceprop paper + $nocull 1 + $model 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_paper1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_paper1.vtf new file mode 100644 index 0000000..79ffd91 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_paper1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_paper1_n.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_paper1_n.vtf new file mode 100644 index 0000000..da8f4c7 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_paper1_n.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_planter1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_planter1.vmt new file mode 100644 index 0000000..da6e37e --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_planter1.vmt @@ -0,0 +1,10 @@ +"vertexlitgeneric" +{ + "$basetexture" "models/props/de_austria/misc/austria_planter1" + "$surfaceprop" "concrete" + "$detail" "de_austria/snow/austria_snow_detail1" + "$detailscale" "2.5" + "$detailblendfactor" ".7" + "$detailblendmode" "0" + +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_planter1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_planter1.vtf new file mode 100644 index 0000000..8d820ca Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_planter1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_planter2.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_planter2.vmt new file mode 100644 index 0000000..d9719d3 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_planter2.vmt @@ -0,0 +1,10 @@ +"vertexlitgeneric" +{ + "$basetexture" "models/props/de_austria/misc/austria_planter2" + "$surfaceprop" "wood" + "$detail" "de_austria/snow/austria_snow_detail1" + "$detailscale" "2.5" + "$detailblendfactor" ".7" + "$detailblendmode" "0" + +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_planter2.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_planter2.vtf new file mode 100644 index 0000000..3b69250 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_planter2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_power_socket1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_power_socket1.vmt new file mode 100644 index 0000000..5960bbe --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_power_socket1.vmt @@ -0,0 +1,9 @@ +vertexlitgeneric +{ + "$basetexture" "models/props/de_austria/misc/austria_power_socket1" + "$basealphaenvmapmask" "1" + "$envmap" "environment maps/metal_generic_002" + "$envmaplightscale" "1" + "$envmaplightscaleminmax" "[0 2]" + "$surfaceprop" "metal" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_power_socket1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_power_socket1.vtf new file mode 100644 index 0000000..bb6225c Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_power_socket1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_rock3.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_rock3.vmt new file mode 100644 index 0000000..146132a --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_rock3.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\props\CS_Militia/rock_riverbed01a" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_snow1_border2.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_snow1_border2.vmt new file mode 100644 index 0000000..3f3a783 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_snow1_border2.vmt @@ -0,0 +1,11 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/snow/austria_snow1_border2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 5 + $detailblendfactor .7 + $detailblendmode 0 +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1.vmt new file mode 100644 index 0000000..b2f6bf2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1.vmt @@ -0,0 +1,11 @@ +"VertexLitGeneric" +{ + $basetexture "models/props/de_austria/misc/austria_street_light1" + $bumpmap "models/props/de_austria/misc/austria_street_light1_n" + + $phong 1 + $phongfresnelranges "[0.8 1.7 3.1]" + $halflambert 1 + $phongexponent 16 + $phongboost 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1.vtf new file mode 100644 index 0000000..2a8f62b Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1_n.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1_n.vtf new file mode 100644 index 0000000..4a6a962 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_street_light1_n.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_tape1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_tape1.vmt new file mode 100644 index 0000000..ec348a7 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_tape1.vmt @@ -0,0 +1,12 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/misc/austria_tape1" + $surfaceprop paper + $alphatest 1 + $nocull 1 + $model 1 + $envmap env_cubemap + $envmapsaturation 1 + $envmapcontrast 1 + $envmaptint "[.4 .4 .4]" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_tape1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_tape1.vtf new file mode 100644 index 0000000..de4c5ad Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_tape1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_trashcan1.vmt b/addons/map_content/materials/models/props/de_austria/misc/austria_trashcan1.vmt new file mode 100644 index 0000000..73e8a3f --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/austria_trashcan1.vmt @@ -0,0 +1,6 @@ +"VertexlitGeneric" +{ + $basetexture "models/props/de_austria/misc/austria_trashcan1" + $envmap env_cubemap + $envmapmask "models/props/de_vostok/TrashCans_ref" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/austria_trashcan1.vtf b/addons/map_content/materials/models/props/de_austria/misc/austria_trashcan1.vtf new file mode 100644 index 0000000..af4cf48 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/misc/austria_trashcan1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/misc/fountain_a.vmt b/addons/map_content/materials/models/props/de_austria/misc/fountain_a.vmt new file mode 100644 index 0000000..f16050d --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/fountain_a.vmt @@ -0,0 +1,8 @@ +"VertexlitGeneric" +{ + $basetexture "models\props\de_austria\misc\austria_fountain_snow1" + $detail "de_austria/snow/austria_snow_detail1" + $detailscale "[ 20 20 ]" + $detailblendfactor .7 + //$bumpmap "models\props\de_cbble\fountain_a\fountain_a_normal" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/light_fixture.vmt b/addons/map_content/materials/models/props/de_austria/misc/light_fixture.vmt new file mode 100644 index 0000000..ad3de15 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/light_fixture.vmt @@ -0,0 +1,5 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\light_fixture" + +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/light_streetlight.vmt b/addons/map_content/materials/models/props/de_austria/misc/light_streetlight.vmt new file mode 100644 index 0000000..80140fc --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/light_streetlight.vmt @@ -0,0 +1,7 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\light_streetlight" + "$envmap" "env_cubemap" + "$envmapmask" "models\props\de_inferno\light_streetlight_ref" + +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/pipemetal001a.vmt b/addons/map_content/materials/models/props/de_austria/misc/pipemetal001a.vmt new file mode 100644 index 0000000..8a96c33 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/pipemetal001a.vmt @@ -0,0 +1,9 @@ +"vertexlitgeneric" +{ + // original shader: basetimeslightmap + $basetexture "models/props_pipes/pipemetal001a" + $surfaceprop "metal" + $envmap "env_cubemap" + $basealphaenvmapmask 1 + $envmaptint "[.5 .5 .5]" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/vostok_wood.vmt b/addons/map_content/materials/models/props/de_austria/misc/vostok_wood.vmt new file mode 100644 index 0000000..fec67d4 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/vostok_wood.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + "$basetexture" "wood/vostok_wood" + "$surfaceprop" "wood" +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/wall_lamp.vmt b/addons/map_content/materials/models/props/de_austria/misc/wall_lamp.vmt new file mode 100644 index 0000000..fc6831c --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/wall_lamp.vmt @@ -0,0 +1,9 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\wall_lamp" + "$alphatest" "1" + "$nocull" "1" + "$envmap" "env_cubemap" + "$envmapmask" "models\props\de_inferno\wall_lamp_ref" + +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/wall_lamp_bulb.vmt b/addons/map_content/materials/models/props/de_austria/misc/wall_lamp_bulb.vmt new file mode 100644 index 0000000..0124067 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/wall_lamp_bulb.vmt @@ -0,0 +1,6 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\wall_lamp_bulb" + "$selfillum" "1" + +} diff --git a/addons/map_content/materials/models/props/de_austria/misc/wall_lamp_glass.vmt b/addons/map_content/materials/models/props/de_austria/misc/wall_lamp_glass.vmt new file mode 100644 index 0000000..1056d71 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/misc/wall_lamp_glass.vmt @@ -0,0 +1,8 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\wall_lamp_glass" + "$translucent" "1" + "$envmap" "env_cubemap" + "$envmapmask" "models\props\de_inferno\wall_lamp_glass_ref" + +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain1.vmt b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain1.vmt new file mode 100644 index 0000000..93e6073 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain1.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/austria_mountain1" + $alphatest 1 +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain1.vtf b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain1.vtf new file mode 100644 index 0000000..ccdf9f1 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain2.vmt b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain2.vmt new file mode 100644 index 0000000..af729ca --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain2.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/austria_mountain2" + $alphatest 1 +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain2.vtf b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain2.vtf new file mode 100644 index 0000000..683e9e6 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/austria_mountain2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree1.vmt b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree1.vmt new file mode 100644 index 0000000..90a8e84 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree1.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/austria_tree1" + $alphatest 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree1.vtf b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree1.vtf new file mode 100644 index 0000000..42e8d10 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree2.vmt b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree2.vmt new file mode 100644 index 0000000..f37606e --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree2.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/austria_tree2" + $alphatest 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree2.vtf b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree2.vtf new file mode 100644 index 0000000..f4c6ee1 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree3.vmt b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree3.vmt new file mode 100644 index 0000000..88f4cbc --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree3.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/austria_tree3" + $alphatest 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree3.vtf b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree3.vtf new file mode 100644 index 0000000..5fa60fa Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree3.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall1.vmt b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall1.vmt new file mode 100644 index 0000000..0409f60 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall1.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/austria_tree_wall1" + $alphatest 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall1.vtf b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall1.vtf new file mode 100644 index 0000000..1f5dd57 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall1.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall2.vmt b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall2.vmt new file mode 100644 index 0000000..838371d --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall2.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/austria_tree_wall2" + $alphatest 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall2.vtf b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall2.vtf new file mode 100644 index 0000000..62a6740 Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/austria_tree_wall2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/boathouse/austria_snow1_border1_cheap.vmt b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/austria_snow1_border1_cheap.vmt new file mode 100644 index 0000000..8a2d93c --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/austria_snow1_border1_cheap.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/snow/austria_snow1_border1" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/boathouse/austria_snow1_cheap.vmt b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/austria_snow1_cheap.vmt new file mode 100644 index 0000000..1e6a3d1 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/austria_snow1_cheap.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/snow/austria_snow1" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/boathouse/boathouse_dock_side.vmt b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/boathouse_dock_side.vmt new file mode 100644 index 0000000..5f2a490 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/boathouse_dock_side.vmt @@ -0,0 +1,4 @@ +vertexlitgeneric +{ +$basetexture "wood/boathouse_dock_side" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/boathouse/infwoodfloor008a.vmt b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/infwoodfloor008a.vmt new file mode 100644 index 0000000..002f4df --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/infwoodfloor008a.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + "$basetexture" "wood/woodfloor008a" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/boathouse/wood_int_07.vmt b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/wood_int_07.vmt new file mode 100644 index 0000000..b284486 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/wood_int_07.vmt @@ -0,0 +1,4 @@ +vertexlitgeneric +{ +$basetexture "wood/wood_int_07" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/boathouse/wood_int_08.vmt b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/wood_int_08.vmt new file mode 100644 index 0000000..22657f2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/boathouse/wood_int_08.vmt @@ -0,0 +1,4 @@ +vertexlitgeneric +{ +$basetexture "wood/wood_int_08" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/church/austria_snow1.vmt b/addons/map_content/materials/models/props/de_austria/skybox/church/austria_snow1.vmt new file mode 100644 index 0000000..1e6a3d1 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/church/austria_snow1.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/snow/austria_snow1" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/church/austria_stonewall3.vmt b/addons/map_content/materials/models/props/de_austria/skybox/church/austria_stonewall3.vmt new file mode 100644 index 0000000..b1f5558 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/church/austria_stonewall3.vmt @@ -0,0 +1,10 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/wall/austria_stonewall3" + $bumpmap "de_austria/wall/austria_stonewall3_n" + $surfaceprop rock + $envmap env_cubemap + $envmaptint "[0.05 0.03 0.03]" + $envmapcontrast 0.5 + $envmapsaturation 0.3 +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/church/wood_int_02.vmt b/addons/map_content/materials/models/props/de_austria/skybox/church/wood_int_02.vmt new file mode 100644 index 0000000..1a47b77 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/church/wood_int_02.vmt @@ -0,0 +1,4 @@ +vertexlitgeneric +{ + $basetexture "wood/wood_int_02" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_clock2.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_clock2.vmt new file mode 100644 index 0000000..129c900 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_clock2.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "models/props/de_austria/skybox/tower_001/austria_clock2" + $alphatest 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_clock2.vtf b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_clock2.vtf new file mode 100644 index 0000000..faf7aef Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_clock2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_gold2.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_gold2.vmt new file mode 100644 index 0000000..dac4a91 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_gold2.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + "$basetexture" "de_austria/misc/austria_gold2" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church1.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church1.vmt new file mode 100644 index 0000000..edf3efa --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church1.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/skybox/austria_skybox_church1" + $selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church2.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church2.vmt new file mode 100644 index 0000000..0457be2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church2.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/skybox/austria_skybox_church2" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church3.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church3.vmt new file mode 100644 index 0000000..7af78ef --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church3.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/skybox/austria_skybox_church3" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church4.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church4.vmt new file mode 100644 index 0000000..1583892 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church4.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/skybox/austria_skybox_church4" + $selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church4b.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church4b.vmt new file mode 100644 index 0000000..a850ed4 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_skybox_church4b.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/skybox/austria_skybox_church4" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_snow1.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_snow1.vmt new file mode 100644 index 0000000..1e6a3d1 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_snow1.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/snow/austria_snow1" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_snow1_border2.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_snow1_border2.vmt new file mode 100644 index 0000000..d5f5bb2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/austria_snow1_border2.vmt @@ -0,0 +1,4 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/snow/austria_snow1_border2" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/milroof001.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/milroof001.vmt new file mode 100644 index 0000000..c5fb33a --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/milroof001.vmt @@ -0,0 +1,6 @@ +vertexlitgeneric +{ + "$basetexture" "wood\milroof001" + "$surfaceprop" "wood_plank" + +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/trim10.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/trim10.vmt new file mode 100644 index 0000000..e6fb0c3 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/trim10.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + "$basetexture" "buildings/trim10" + "$surfaceprop" "brick" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/trim21.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/trim21.vmt new file mode 100644 index 0000000..f139891 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/trim21.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + "$basetexture" "buildings/trim21" + "$surfaceprop" "wood" +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/wood_int_07.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/wood_int_07.vmt new file mode 100644 index 0000000..84b8af3 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/wood_int_07.vmt @@ -0,0 +1,5 @@ +vertexlitgeneric +{ + $basetexture "wood/wood_int_07" + $surfaceprop wood +} diff --git a/addons/map_content/materials/models/props/de_austria/skybox/tower_001/woodm.vmt b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/woodm.vmt new file mode 100644 index 0000000..3deabef --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/skybox/tower_001/woodm.vmt @@ -0,0 +1,5 @@ +"vertexlitgeneric" +{ + "$basetexture" "cs_havana/woodm" + "$surfaceprop" "wood_solid" +} diff --git a/addons/map_content/materials/models/props/de_austria/snow/austria_rock2.vmt b/addons/map_content/materials/models/props/de_austria/snow/austria_rock2.vmt new file mode 100644 index 0000000..d1bd4c1 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/snow/austria_rock2.vmt @@ -0,0 +1,10 @@ +vertexlitgeneric +{ + $basetexture "models/props/de_austria/snow/austria_rock2" + $surfaceprop snow + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 2.5 + $detailblendfactor .7 + $detailblendmode 0 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/snow/austria_rock2.vtf b/addons/map_content/materials/models/props/de_austria/snow/austria_rock2.vtf new file mode 100644 index 0000000..c2a079f Binary files /dev/null and b/addons/map_content/materials/models/props/de_austria/snow/austria_rock2.vtf differ diff --git a/addons/map_content/materials/models/props/de_austria/snow/austria_snow1_border2.vmt b/addons/map_content/materials/models/props/de_austria/snow/austria_snow1_border2.vmt new file mode 100644 index 0000000..fce8b71 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/snow/austria_snow1_border2.vmt @@ -0,0 +1,20 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/snow/austria_snow1_border2" + $surfaceprop snow + $bumpmap "de_austria/snow/austria_snow1_n" + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 5 + $detailblendfactor .7 + $detailblendmode 0 + + //$envmap env_cubemap + //$normalmapalphaenvmapmask 1 + //$envmaptint "[0.5 0.5 0.6]" + + //$phong 1 + //$phongexponent 5 + //$phongMaskContrastBrightness "[0 .3]" + //$phongAmount "[.9 .9 1 1.0]" +} diff --git a/addons/map_content/materials/models/props/de_austria/snow/austria_trim1.vmt b/addons/map_content/materials/models/props/de_austria/snow/austria_trim1.vmt new file mode 100644 index 0000000..0bad0bd --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/snow/austria_trim1.vmt @@ -0,0 +1,11 @@ +"vertexlitgeneric" +{ + $basetexture "de_austria/wall/austria_trim1" + $surfaceprop concrete + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 2.5 + $detailblendfactor .7 + $detailblendmode 0 + +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/snow/austria_trim2.vmt b/addons/map_content/materials/models/props/de_austria/snow/austria_trim2.vmt new file mode 100644 index 0000000..f17de46 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/snow/austria_trim2.vmt @@ -0,0 +1,10 @@ +vertexlitgeneric +{ + $basetexture "de_austria/wall/austria_trim2" + $surfaceprop concrete + + $detail "de_austria/snow/austria_snow_detail1" + $detailscale 5 + $detailblendfactor .7 + $detailblendmode 0 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_austria/snow/offtrima.vmt b/addons/map_content/materials/models/props/de_austria/snow/offtrima.vmt new file mode 100644 index 0000000..7d529f6 --- /dev/null +++ b/addons/map_content/materials/models/props/de_austria/snow/offtrima.vmt @@ -0,0 +1,5 @@ +vertexlitgeneric +{ + $basetexture "concrete\offtrima" + $surfaceprop concrete +} diff --git a/addons/map_content/materials/models/props/de_burger/de_burger_sign_bank.vmt b/addons/map_content/materials/models/props/de_burger/de_burger_sign_bank.vmt new file mode 100644 index 0000000..f2b3167 --- /dev/null +++ b/addons/map_content/materials/models/props/de_burger/de_burger_sign_bank.vmt @@ -0,0 +1,5 @@ +// ### This basic VMT was written based on the D:\dev\tools\python\templateStrings.pyc script ### +"VertexLitGeneric" +{ + $baseTexture "models/props/de_burger/de_burger_sign_bank.vtf" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_burger/de_burger_sign_bank.vtf b/addons/map_content/materials/models/props/de_burger/de_burger_sign_bank.vtf new file mode 100644 index 0000000..38b5dc5 Binary files /dev/null and b/addons/map_content/materials/models/props/de_burger/de_burger_sign_bank.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/arch_stone03_new_color.vmt b/addons/map_content/materials/models/props/de_inferno/arch_stone03_new_color.vmt new file mode 100644 index 0000000..9b452e2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/arch_stone03_new_color.vmt @@ -0,0 +1,10 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\arch_stone03_new_color" + + "$surfaceprop" "brick" + "$detail" "detail\noise_detail_01" + "$detailscale" "[ 6 6 ]" + "$detailblendfactor" ".4" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/arch_stone03_new_color.vtf b/addons/map_content/materials/models/props/de_inferno/arch_stone03_new_color.vtf new file mode 100644 index 0000000..83d204d Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/arch_stone03_new_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/claypot03.vmt b/addons/map_content/materials/models/props/de_inferno/claypot03.vmt new file mode 100644 index 0000000..c1461d0 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/claypot03.vmt @@ -0,0 +1,5 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\claypot03" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/claypot03.vtf b/addons/map_content/materials/models/props/de_inferno/claypot03.vtf new file mode 100644 index 0000000..1eca416 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/claypot03.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/claypot_plants01.vmt b/addons/map_content/materials/models/props/de_inferno/claypot_plants01.vmt new file mode 100644 index 0000000..98b2b69 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/claypot_plants01.vmt @@ -0,0 +1,9 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\claypot_plants01" + "$alphatest" "1" + "$nocull" "1" + "$model" "1" + + "$DISABLECSMLOOKUP" "1" +} diff --git a/addons/map_content/materials/models/props/de_inferno/claypot_plants01.vtf b/addons/map_content/materials/models/props/de_inferno/claypot_plants01.vtf new file mode 100644 index 0000000..adf147a Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/claypot_plants01.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/book_shelf_a/book_shelf_a.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/book_shelf_a/book_shelf_a.vtf new file mode 100644 index 0000000..8a0ab5e Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/book_shelf_a/book_shelf_a.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/curtains_pulled.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/curtains_pulled.vmt new file mode 100644 index 0000000..d9b2e8e --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/curtains_pulled.vmt @@ -0,0 +1,7 @@ +"VertexLitGeneric" +{ + $baseTexture "models\props\de_inferno\hr_i\curtains_pulled\curtains_pulled.vtf" + + "$detail" "detail\dt_fabric2" + "$detailscale" "[24 24]" +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/curtains_pulled.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/curtains_pulled.vtf new file mode 100644 index 0000000..43f2531 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/curtains_pulled.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/metal.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/metal.vmt new file mode 100644 index 0000000..0ab0b96 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/curtains_pulled/metal.vmt @@ -0,0 +1,10 @@ +"VertexLitGeneric" +{ + "$basetexture" "metal/hr_metal/inferno/metal_a" + + "$surfaceprop" "metal" + + "$envmap" "env_cubemap" + "$envmaptint" "[.35 .35 .35]" + +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark.vmt new file mode 100644 index 0000000..26a7ed6 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\props\de_inferno\hr_i\cypress_a\bark_a.vtf" +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark_a.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark_a.vmt new file mode 100644 index 0000000..26a7ed6 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark_a.vmt @@ -0,0 +1,4 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\props\de_inferno\hr_i\cypress_a\bark_a.vtf" +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark_a.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark_a.vtf new file mode 100644 index 0000000..617cbb0 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/bark_a.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a.vmt new file mode 100644 index 0000000..4ae242a --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a.vmt @@ -0,0 +1,36 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\hr_i\cypress_a\cypress_a" +$AlphaTest 1 +$AlphaTestReference ".35" +$nocull 1 +$model 1 + "$envmap" "env_cubemap" + "$envmapfresnel" "1" + "$envmaptint" "[.3 .3 .3]" + $envmapmask "models\props\de_inferno\hr_i\cypress_a\cypress_a_env" +$FlashlightNoLambert 1 +// "//$nolod" 1 +// "//$envmap" "env_cubemap" +// "//$envmapmask" "models\props\de_nuke\car_nuke_ref" + //"$model" "1" + //"$surfaceprop" "chainlink" + //$vertexfog 1 + + $treeSway "1" // height at which the effect is on fully (in world units) + $treeSwayHeight "300" // radius at which the effect is on fully (in world units) + $treeSwayStartHeight ".02" // portion of height at which the effect starts to fade in (0-1) + $treeSwayRadius "500" // radius at which the effect is on fully (in world units) + $treeSwayStartRadius "0.0" // portion of radius at which the effect starts to fade in (0-1) + $treeSwaySpeed ".4" // how quickly the tree sways (as a multiplier of time) + $treeSwayStrength "3" // how much the tree sways (as a multiplier of position) + $treeSwayScrumbleSpeed "1.2" // how quickly the leaves move (as a multiplier of time) + $treeSwayScrumbleStrength "1" // how much the leaves move( as a multiplier of position) + $treeSwayScrumbleFrequency "2" // spatial frequency of sine wave applied to leaves. Typically high-ish (10-25) + $treeSwayFalloffExp "2" // falloff parameter for wave motion on branches/trunk, higher means core of tree is more stable + $treeSwayScrumbleFalloffExp "3" // falloff parameter for scrumble motion on leaves, higher means the core of the tree is more stable + $treeSwaySpeedHighWindMultiplier "0" // multiplier of movement speed at higher wind + $treeSwaySpeedLerpStart "200.0" // wind speed at which high wind multiplier begins to have effect + $treeSwaySpeedLerpEnd "600.0" // wind speed at which high wind multiplier is fully on + "$DISABLECSMLOOKUP" "1" +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a.vtf new file mode 100644 index 0000000..f08f902 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a_env.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a_env.vtf new file mode 100644 index 0000000..e3d8a6e Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/cypress_a/cypress_a_env.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_broom/inferno_broom_color.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_broom/inferno_broom_color.vmt new file mode 100644 index 0000000..baced6c --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_broom/inferno_broom_color.vmt @@ -0,0 +1,19 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\hr_i\inferno_broom\inferno_broom_color" +// "$envmapmask" "models\props\de_inferno\hr_i\inferno_phone_pole\inferno_phone_pole_mask" +// "$envmap" "environment maps\metal_generic_004" +// "$envmap" "env_cubemap" +// "$envmap" "models\props\de_nuke\hr_nuke\metal_railing_001\metal_railing_cube" + + + + +// "$envmaplightscale" "1" +// "$envmaplightscaleminmax" "[0 2]" + + "$envmapcontrast" "0.30" + + + "$surfaceprop" "wood" +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_broom/inferno_broom_color.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_broom/inferno_broom_color.vtf new file mode 100644 index 0000000..175c01f Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_broom/inferno_broom_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_color.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_color.vmt new file mode 100644 index 0000000..cca4061 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_color.vmt @@ -0,0 +1,21 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\hr_i\inferno_chair\inferno_chair_color" + "$envmapmask" "models\props\de_inferno\hr_i\inferno_chair\inferno_chair_mask" +// "$envmap" "environment maps\metal_generic_004" +// "$envmap" "env_cubemap" + "$envmap" "env_cubemap" + "$envmapcontrast" "0.9" + "$envmapsaturation" "0" + + "$blendtintbybasealpha" "1" + + + "$envmaplightscale" "1" + "$envmaplightscaleminmax" "[0 1]" + + + "$surfaceprop" "wood" + + "$color2" "{218 99 82}" +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_color.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_color.vtf new file mode 100644 index 0000000..c1b7d04 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_mask.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_mask.vtf new file mode 100644 index 0000000..ff00dd3 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_chair/inferno_chair_mask.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_color.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_color.vmt new file mode 100644 index 0000000..774cb31 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_color.vmt @@ -0,0 +1,22 @@ +VertexLitGeneric +{ + +$baseTexture "models\props\de_inferno\hr_i\inferno_intercom\inferno_intercom_color" + +$bumpmap "models\props\de_inferno\hr_i\inferno_intercom\inferno_intercom_normal" + "$normalmapalphaenvmapmask" 1 + "$phong" "1" + "$phongBoost" "1" + "$phongExponent" "130" + "$phongfresnelranges" "[0.8 0.8 1]" + +"$envmap" "env_cubemap" +"$envmaplightscale" "1" +"$envmaplightscaleminmax" "[0 1]" + +"$envmapcontrast" "0.50" + + + + +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_color.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_color.vtf new file mode 100644 index 0000000..186f7e3 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_normal.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_normal.vtf new file mode 100644 index 0000000..d6e54ec Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_intercom/inferno_intercom_normal.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_color.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_color.vmt new file mode 100644 index 0000000..da2d12c --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_color.vmt @@ -0,0 +1,21 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\hr_i\inferno_paintings\inferno_painting_color" + + + + "$envmapmask" "models\props\de_inferno\hr_i\inferno_paintings\inferno_painting_mask" +// "$envmap" "environment maps\metal_generic_004" +// "$envmap" "env_cubemap" + "$envmap" "env_cubemap" + "$envmapcontrast" "0.9" + "$envmapsaturation" "0" + + + + "$envmaplightscale" "1" + "$envmaplightscaleminmax" "[0 1]" + + + "$surfaceprop" "metal" +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_color.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_color.vtf new file mode 100644 index 0000000..e2575e6 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_mask.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_mask.vtf new file mode 100644 index 0000000..efe8023 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/inferno_paintings/inferno_painting_mask.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_color.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_color.vmt new file mode 100644 index 0000000..bbd8402 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_color.vmt @@ -0,0 +1,25 @@ +"VertexlitGeneric" +{ + "$basetexture" "models\props\de_inferno\hr_i\large_gate_a\large_gate_02_color" + + "$envmapmask" "models\props\de_inferno\hr_i\large_gate_a\large_gate_02_mask" +// "$envmap" "environment maps\metal_generic_003" +// "$envmap" "env_cubemap" + "$envmap" "env_cubemap" + "$envmapcontrast" "0.9" + "$envmapsaturation" "0" + + $AlphaTest 1 + $AlphaTestReference ".5" +// "$allowalphatocoverage" "1" + + "$detail" "models\props\de_nuke\hr_nuke\metal_crate_001\metal_crate_001_detail" + "$detailscale" "[3 3]" + "$detailblendfactor" "1" + "$envmaplightscale" "1" + "$envmaplightscaleminmax" "[0 1.5]" + + + + +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_color.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_color.vtf new file mode 100644 index 0000000..81e2ea1 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_mask.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_mask.vtf new file mode 100644 index 0000000..4781e27 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/large_gate_a/large_gate_02_mask.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/table_a/table_a.vmt b/addons/map_content/materials/models/props/de_inferno/hr_i/table_a/table_a.vmt new file mode 100644 index 0000000..3f0fb72 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/hr_i/table_a/table_a.vmt @@ -0,0 +1,16 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\props\de_inferno\hr_i\book_shelf_a\book_shelf_a.vtf" + + "$basetexturetransform" "center .5 .5 scale 2 2 rotate 90 translate 0 0" + "$surfaceprop" "wood" + + "$detail" "models\props\de_inferno\hr_i\table_a\table_a_ao.vtf" + "$detailscale" "[1 1]" + "$detailblendfactor" "1" + "$detailblendmode" 0 + "$envmap" "env_cubemap" + "$envmapfresnel" "1" + "$envmaptint" "[.3 .3 .3]" + "$basealphaenvmapmask" "1" +} diff --git a/addons/map_content/materials/models/props/de_inferno/hr_i/table_a/table_a_ao.vtf b/addons/map_content/materials/models/props/de_inferno/hr_i/table_a/table_a_ao.vtf new file mode 100644 index 0000000..4e28b7a Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/hr_i/table_a/table_a_ao.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/infarchc_new_color.vmt b/addons/map_content/materials/models/props/de_inferno/infarchc_new_color.vmt new file mode 100644 index 0000000..62b6fc2 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/infarchc_new_color.vmt @@ -0,0 +1,10 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\infarchc_new_color" + + "$surfaceprop" "brick" + "$detail" "detail\noise_detail_01" + "$detailscale" "[ 6 6 ]" + "$detailblendfactor" "0.3" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/infarchc_new_color.vtf b/addons/map_content/materials/models/props/de_inferno/infarchc_new_color.vtf new file mode 100644 index 0000000..96e9797 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/infarchc_new_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_color.vmt b/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_color.vmt new file mode 100644 index 0000000..6f36201 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_color.vmt @@ -0,0 +1,23 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\inferno_church_entrance_color" + "$bumpmap" "models\props\de_inferno\inferno_church_entrance_normal" + "$phongexponent" 20 + "$phongalbedotint" "1" + "$phongtint" "[.1 .15 .1]" + "$phongdisablehalflambert" "1" + "$phong" "1" + "$phongboost" "25" + "$phongfresnelranges" "[.8 0.5 1]" + "$normalmapalphaenvmapmask" "1" + "$detail" "detail\noise_detail_01" + "$detailscale" "[ 6 6 ]" + "$detailblendfactor" "0.5" + + "$envmap" "env_cubemap" + + "$envmapfresnel" "1" + //"$baseAlphaEnvMapMaskMinMaxExp" "[1 1 3]" + "$envmaptint" "[.3 .3 .3]" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_color.vtf b/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_color.vtf new file mode 100644 index 0000000..9fc9141 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_normal.vtf b/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_normal.vtf new file mode 100644 index 0000000..8350c5d Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/inferno_church_entrance_normal.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_color.vmt b/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_color.vmt new file mode 100644 index 0000000..be5a28a --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_color.vmt @@ -0,0 +1,13 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\inferno_fence_01_color" + "$envmap" "env_cubemap" + "$envmapmask" "models\props\de_inferno\inferno_fence_01_ref" + "$envmapcontrast" "1.01" + + "$surfaceprop" "brick" + "$detail" "detail\noise_detail_01" + "$detailscale" "[ 6 6 ]" + "$detailblendfactor" "0.3" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_color.vtf b/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_color.vtf new file mode 100644 index 0000000..eca024e Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_ref.vtf b/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_ref.vtf new file mode 100644 index 0000000..af2b429 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/inferno_fence_01_ref.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/light_fixture.vtf b/addons/map_content/materials/models/props/de_inferno/light_fixture.vtf new file mode 100644 index 0000000..9fa1802 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/light_fixture.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/light_streetlight.vtf b/addons/map_content/materials/models/props/de_inferno/light_streetlight.vtf new file mode 100644 index 0000000..08b5c9a Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/light_streetlight.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/light_streetlight_ref.vtf b/addons/map_content/materials/models/props/de_inferno/light_streetlight_ref.vtf new file mode 100644 index 0000000..4b3fc2c Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/light_streetlight_ref.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/logpile_new_color.vmt b/addons/map_content/materials/models/props/de_inferno/logpile_new_color.vmt new file mode 100644 index 0000000..0cf4de9 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/logpile_new_color.vmt @@ -0,0 +1,23 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\logpile_new_color" +// "$bumpmap" "models\props\de_inferno\logpile_new_normal" +// "$phongexponent" 20 +// "$phongalbedotint" "1" +// "$phongtint" "[.1 .15 .1]" +// "$phongdisablehalflambert" "1" +// "$phong" "1" +// "$phongboost" "25" +// "$phongfresnelranges" "[.8 0.5 1]" +// "$normalmapalphaenvmapmask" "1" +// "$detail" "detail\noise_detail_01" +// "$detailscale" "[ 6 6 ]" +// "$detailblendfactor" "0.5" + +// "$envmap" "env_cubemap" + +// "$envmapfresnel" "1" +// //"$baseAlphaEnvMapMaskMinMaxExp" "[1 1 3]" +// "$envmaptint" "[.3 .3 .3]" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/logpile_new_color.vtf b/addons/map_content/materials/models/props/de_inferno/logpile_new_color.vtf new file mode 100644 index 0000000..acc8ff8 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/logpile_new_color.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/picture1.vmt b/addons/map_content/materials/models/props/de_inferno/picture1.vmt new file mode 100644 index 0000000..c274bdf --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/picture1.vmt @@ -0,0 +1,5 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\picture1" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/picture1.vtf b/addons/map_content/materials/models/props/de_inferno/picture1.vtf new file mode 100644 index 0000000..c119749 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/picture1.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/picture1_backing.vmt b/addons/map_content/materials/models/props/de_inferno/picture1_backing.vmt new file mode 100644 index 0000000..df24c4d --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/picture1_backing.vmt @@ -0,0 +1,5 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\picture1_backing" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/picture1_backing.vtf b/addons/map_content/materials/models/props/de_inferno/picture1_backing.vtf new file mode 100644 index 0000000..db57f89 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/picture1_backing.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/picture3.vmt b/addons/map_content/materials/models/props/de_inferno/picture3.vmt new file mode 100644 index 0000000..dff3ebe --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/picture3.vmt @@ -0,0 +1,5 @@ +VertexLitGeneric +{ + "$basetexture" "models\props\de_inferno\picture3" + +} diff --git a/addons/map_content/materials/models/props/de_inferno/picture3.vtf b/addons/map_content/materials/models/props/de_inferno/picture3.vtf new file mode 100644 index 0000000..c195044 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/picture3.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/plant_foliage.vmt b/addons/map_content/materials/models/props/de_inferno/plant_foliage.vmt new file mode 100644 index 0000000..a015363 --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/plant_foliage.vmt @@ -0,0 +1,8 @@ +"VertexlitGeneric" +{ + "$basetexture" "models/props/de_inferno/plant_foliage" + "$alphatest" 1 + "$nocull" 1 + + "$DISABLECSMLOOKUP" "1" +} diff --git a/addons/map_content/materials/models/props/de_inferno/plant_foliage.vtf b/addons/map_content/materials/models/props/de_inferno/plant_foliage.vtf new file mode 100644 index 0000000..5080220 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/plant_foliage.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/plant_potdirt.vmt b/addons/map_content/materials/models/props/de_inferno/plant_potdirt.vmt new file mode 100644 index 0000000..571ecdf --- /dev/null +++ b/addons/map_content/materials/models/props/de_inferno/plant_potdirt.vmt @@ -0,0 +1,4 @@ +"VertexlitGeneric" +{ + "$basetexture" "models/props/de_inferno/plant_potdirt" +} diff --git a/addons/map_content/materials/models/props/de_inferno/plant_potdirt.vtf b/addons/map_content/materials/models/props/de_inferno/plant_potdirt.vtf new file mode 100644 index 0000000..cb463f3 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/plant_potdirt.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/stone_bench_normal.vtf b/addons/map_content/materials/models/props/de_inferno/stone_bench_normal.vtf new file mode 100644 index 0000000..5d80a14 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/stone_bench_normal.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/wall_lamp.vtf b/addons/map_content/materials/models/props/de_inferno/wall_lamp.vtf new file mode 100644 index 0000000..b314ded Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/wall_lamp.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/wall_lamp_bulb.vtf b/addons/map_content/materials/models/props/de_inferno/wall_lamp_bulb.vtf new file mode 100644 index 0000000..3f1deca Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/wall_lamp_bulb.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/wall_lamp_glass.vtf b/addons/map_content/materials/models/props/de_inferno/wall_lamp_glass.vtf new file mode 100644 index 0000000..b7d6ed4 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/wall_lamp_glass.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/wall_lamp_glass_ref.vtf b/addons/map_content/materials/models/props/de_inferno/wall_lamp_glass_ref.vtf new file mode 100644 index 0000000..944f5df Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/wall_lamp_glass_ref.vtf differ diff --git a/addons/map_content/materials/models/props/de_inferno/wall_lamp_ref.vtf b/addons/map_content/materials/models/props/de_inferno/wall_lamp_ref.vtf new file mode 100644 index 0000000..7907558 Binary files /dev/null and b/addons/map_content/materials/models/props/de_inferno/wall_lamp_ref.vtf differ diff --git a/addons/map_content/materials/models/props/startkztimer.vmt b/addons/map_content/materials/models/props/startkztimer.vmt new file mode 100644 index 0000000..821f50f --- /dev/null +++ b/addons/map_content/materials/models/props/startkztimer.vmt @@ -0,0 +1,8 @@ +Sprite +{ + "$basetexture" "models/props/startkztimer" + "$spriteorigin" "[ 0.50 0.50 ]" + + "$nocull" 1 + "$translucent" "1" +} diff --git a/addons/map_content/materials/models/props/startkztimer.vtf b/addons/map_content/materials/models/props/startkztimer.vtf new file mode 100644 index 0000000..0098bd1 Binary files /dev/null and b/addons/map_content/materials/models/props/startkztimer.vtf differ diff --git a/addons/map_content/materials/models/props/stopkztimer.vmt b/addons/map_content/materials/models/props/stopkztimer.vmt new file mode 100644 index 0000000..6d42b6d --- /dev/null +++ b/addons/map_content/materials/models/props/stopkztimer.vmt @@ -0,0 +1,7 @@ +Sprite +{ + "$basetexture" "models/props/stopkztimer" + "$spriteorigin" "[ 0.50 0.50 ]" + + "$nocull" 1 +} diff --git a/addons/map_content/materials/models/props/stopkztimer.vtf b/addons/map_content/materials/models/props/stopkztimer.vtf new file mode 100644 index 0000000..a8732f9 Binary files /dev/null and b/addons/map_content/materials/models/props/stopkztimer.vtf differ diff --git a/addons/map_content/materials/models/props/switch.vmt b/addons/map_content/materials/models/props/switch.vmt new file mode 100644 index 0000000..78034db --- /dev/null +++ b/addons/map_content/materials/models/props/switch.vmt @@ -0,0 +1,16 @@ +VertexLitGeneric +{ +$basetexture "models/props/switch" +$surfaceprop metal +$bumpmap "models\props\Metal_box_normal" +$phong 1 +$phongexponenttexture "models\props\Metal_box_exponent" + +$phongboost 1 + +$phongfresnelranges "[5 1 2]" + + + +$selfillum 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/switch.vtf b/addons/map_content/materials/models/props/switch.vtf new file mode 100644 index 0000000..ec857fd Binary files /dev/null and b/addons/map_content/materials/models/props/switch.vtf differ diff --git a/addons/map_content/materials/models/props/switch001.vmt b/addons/map_content/materials/models/props/switch001.vmt new file mode 100644 index 0000000..0cef6a8 --- /dev/null +++ b/addons/map_content/materials/models/props/switch001.vmt @@ -0,0 +1,20 @@ +"VertexLitGeneric" +{ + "$baseTexture" "models\props/switch001" + + + "$bumpmap" "models\props\switch001_normal" + "$phong" "1" + "$phongexponenttexture" "models\props\switch001_exponent" +// "$phongexponent" "25" + + "$phongboost" "10" + "$lightwarptexture" "models\props\switch001_lightwarp" + "$phongfresnelranges" "[5 1 2]" +// "$halflambert" "1" + +// "$envmap" "env_cubemap" +// "$normalmapalphaenvmapmask" 1 + + "$selfillum" 1 +} \ No newline at end of file diff --git a/addons/map_content/materials/models/props/switch001.vtf b/addons/map_content/materials/models/props/switch001.vtf new file mode 100644 index 0000000..63ccfbd Binary files /dev/null and b/addons/map_content/materials/models/props/switch001.vtf differ diff --git a/addons/map_content/materials/models/props/switch001_exponent.vmt b/addons/map_content/materials/models/props/switch001_exponent.vmt new file mode 100644 index 0000000..df2ed2f --- /dev/null +++ b/addons/map_content/materials/models/props/switch001_exponent.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ + "$basetexture" "models/props/switch001_exponent" +} diff --git a/addons/map_content/materials/models/props/switch001_exponent.vtf b/addons/map_content/materials/models/props/switch001_exponent.vtf new file mode 100644 index 0000000..b9fb6f3 Binary files /dev/null and b/addons/map_content/materials/models/props/switch001_exponent.vtf differ diff --git a/addons/map_content/materials/models/props/switch001_lightwarp.vmt b/addons/map_content/materials/models/props/switch001_lightwarp.vmt new file mode 100644 index 0000000..2bf99aa --- /dev/null +++ b/addons/map_content/materials/models/props/switch001_lightwarp.vmt @@ -0,0 +1,4 @@ +"LightmappedGeneric" +{ + "$basetexture" "models/props/switch001_lightwarp" +} diff --git a/addons/map_content/materials/models/props/switch001_lightwarp.vtf b/addons/map_content/materials/models/props/switch001_lightwarp.vtf new file mode 100644 index 0000000..d368cc2 Binary files /dev/null and b/addons/map_content/materials/models/props/switch001_lightwarp.vtf differ diff --git a/addons/map_content/materials/models/props/switch001_normal.vmt b/addons/map_content/materials/models/props/switch001_normal.vmt new file mode 100644 index 0000000..03202d2 --- /dev/null +++ b/addons/map_content/materials/models/props/switch001_normal.vmt @@ -0,0 +1,5 @@ +"LightmappedGeneric" +{ + "$basetexture" "models/props/switch001_normal" + "$translucent" 1 +} diff --git a/addons/map_content/materials/models/props/switch001_normal.vtf b/addons/map_content/materials/models/props/switch001_normal.vtf new file mode 100644 index 0000000..67d30a4 Binary files /dev/null and b/addons/map_content/materials/models/props/switch001_normal.vtf differ diff --git a/addons/map_content/materials/models/props_breakable/glass_192x128_b_normal.vmt b/addons/map_content/materials/models/props_breakable/glass_192x128_b_normal.vmt new file mode 100644 index 0000000..847ee00 --- /dev/null +++ b/addons/map_content/materials/models/props_breakable/glass_192x128_b_normal.vmt @@ -0,0 +1,22 @@ +Refract +{ +$model 1 +$refractamount ".07" +$bluramount ".3" +$REFRACTTINT "{235 247 247}" + +$scale "[1 1]" +$dudvmap "models\props_breakable\glass_fracture_B_normal" +$normalmap "models\props_breakable\glass_fracture_B_normal" +$surfaceprop glass +$translucent 1 + +$envmap env_cubemap +$envmapcontrast 1 +$envmapsaturation "[1 1 1]" +$envmaptint "[.71 .79 .85]" +" MQS.Config.QuestEntDrawDist ^ 2 then return end + + if self:GetEnablePhys() then + self:DrawModel() + else + local sysTime = SysTime() + local rotAng = Angle(Ang) + self.rotationOffset = sysTime % 360 * 130 + rotAng:RotateAroundAxis(planeNormal, self.rotationOffset) + + if not IsValid(self.RenderModel) then + self:InitCsInitRenderModelModel() + end + + self.RenderModel:SetPos(Pos) + self.RenderModel:SetAngles(rotAng) + end + + if self:GetTPly() ~= LocalPlayer() then return end + if not self:GetShowPointer() then return end + Ang:RotateAroundAxis(Ang:Forward(), 90) + local relativeEye = eyepos - Pos + local relativeEyeOnPlane = relativeEye - planeNormal * relativeEye:Dot(planeNormal) + local textAng = relativeEyeOnPlane:AngleEx(planeNormal) + textAng:RotateAroundAxis(textAng:Up(), 90) + textAng:RotateAroundAxis(textAng:Forward(), 90) + cam.Start3D2D(Pos - Ang:Right() * (50 + math.sin(CurTime() * 2) * 5), textAng, 0.2) + MSD.DrawTexturedRect(-23, -23, 48, 64, MSD.Icons48.arrow_down_color, color_white) + cam.End3D2D() +end \ No newline at end of file diff --git a/addons/mc_quests/lua/entities/mqs_ent/init.lua b/addons/mc_quests/lua/entities/mqs_ent/init.lua new file mode 100644 index 0000000..c48d361 --- /dev/null +++ b/addons/mc_quests/lua/entities/mqs_ent/init.lua @@ -0,0 +1,84 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +function ENT:Initialize() + if not self.model then + self.model = "models/props_junk/garbage_newspaper001a.mdl" + end + + self:SetModel(self.model) + self:SetCModel(self.model) + self:SetTPly(self.task_ply) + self:SetShowPointer(self.pointer) + self:SetEnablePhys(self.enablephys) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetSolid(SOLID_VPHYSICS) + self:Activate() +end + +function ENT:PickUP(ply) + MQS.SetSelfNWdata(ply, "quest_colected", MQS.GetSelfNWdata(ply, "quest_colected") + 1) + + if MQS.GetSelfNWdata(ply, "quest_colected") >= MQS.GetSelfNWdata(ply, "quest_ent") then + MQS.UpdateObjective(ply) + end + + self.save_remove = true + SafeRemoveEntity(self) + +end + +function ENT:Use(ply) + if ply ~= self.task_ply then return end + + if self:GetUseHold() then + if not self.StartPicking then + self.StartPicking = CurTime() + self.pickply = ply + end + return + end + self:PickUP(ply) +end + +function ENT:Think() + self:NextThink(CurTime()) + + if self.StartPicking and self:GetUseHold() and IsValid(self.pickply) and self.pickply:KeyDown(IN_USE) then + local progress = (self.StartPicking + self:GetUseHold() - CurTime()) / self:GetUseHold() + progress = 1 - progress + if progress >= 1 then + self:PickUP(self.pickply) + self.StartPicking = nil + self:SetPickProgress(0) + self.pickply = nil + return true + end + self:SetPickProgress(progress) + else + self.StartPicking = nil + self:SetPickProgress(0) + self.pickply = nil + end + return true +end + +function ENT:OnRemove() + if MQS.ActiveTask[self.task_id] then + table.RemoveByValue(MQS.ActiveTask[self.task_id].ents, self:EntIndex()) + end + + if not self.save_remove and IsValid(self.task_ply) then + MQS.SetSelfNWdata(self.task_ply, "quest_ent", MQS.GetSelfNWdata(self.task_ply, "quest_ent") - 1) + + if MQS.GetSelfNWdata(self.task_ply, "quest_colected") >= MQS.GetSelfNWdata(self.task_ply, "quest_ent") then + MQS.UpdateObjective(self.task_ply) + end + end + + if self.task_ply then + MQS.ActiveDataShare(self.task_ply) + end +end \ No newline at end of file diff --git a/addons/mc_quests/lua/entities/mqs_ent/shared.lua b/addons/mc_quests/lua/entities/mqs_ent/shared.lua new file mode 100644 index 0000000..4f77cda --- /dev/null +++ b/addons/mc_quests/lua/entities/mqs_ent/shared.lua @@ -0,0 +1,14 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Quest Item" +ENT.Author = "Mactavish" + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "CModel") + self:NetworkVar("Entity", 0, "TPly") + self:NetworkVar("Bool", 0, "Distractible") + self:NetworkVar("Bool", 1, "EnablePhys") + self:NetworkVar("Bool", 2, "ShowPointer") + self:NetworkVar("Int", 0, "UseHold") + self:NetworkVar("Float", 0, "PickProgress") +end \ No newline at end of file diff --git a/addons/mc_quests/lua/entities/mqs_npc/cl_init.lua b/addons/mc_quests/lua/entities/mqs_npc/cl_init.lua new file mode 100644 index 0000000..6702f6c --- /dev/null +++ b/addons/mc_quests/lua/entities/mqs_npc/cl_init.lua @@ -0,0 +1,31 @@ +include("shared.lua") + +function ENT:Initialize() + self.MQSNPC = true + self.names = 0 +end + +function ENT:Draw() + self:DrawModel() + + if self:GetPos():DistToSqr(LocalPlayer():GetPos()) > MQS.Config.QuestEntDrawDist ^ 2 then return end + + local Pos = self:EyePos() or self:GetPos() + Pos = Pos + Vector(0, 0, 10) + local Ang = self:GetAngles() + local eyepos = EyePos() + local planeNormal = Ang:Up() + Ang:RotateAroundAxis(Ang:Forward(), 90) + + local relativeEye = eyepos - Pos + local relativeEyeOnPlane = relativeEye - planeNormal * relativeEye:Dot(planeNormal) + local textAng = relativeEyeOnPlane:AngleEx(planeNormal) + + textAng:RotateAroundAxis(textAng:Up(), 90) + textAng:RotateAroundAxis(textAng:Forward(), 90) + + cam.Start3D2D(Pos - Ang:Right() * (8 + math.sin(CurTime()) * 0.9), textAng, 0.1) + draw.RoundedBox(8, -self.names / 2 - 10, 0, self.names + 20, 35, MSD.Theme["d"]) + self.names = draw.SimpleTextOutlined(self:GetNamer(), "MSDFont.32", 0, 0, color_white, TEXT_ALIGN_CENTER, 0, 1, color_black) + cam.End3D2D() +end \ No newline at end of file diff --git a/addons/mc_quests/lua/entities/mqs_npc/init.lua b/addons/mc_quests/lua/entities/mqs_npc/init.lua new file mode 100644 index 0000000..337fa81 --- /dev/null +++ b/addons/mc_quests/lua/entities/mqs_npc/init.lua @@ -0,0 +1,25 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") +util.AddNetworkString("MQS.OpenNPCMenu") + +function ENT:Initialize() + self.MQSNPC = true + self:SetHullType(HULL_HUMAN) + self:SetHullSizeNormal() + self:SetNPCState(NPC_STATE_SCRIPT) +end + +function ENT:AcceptInput(istr, ply) + if IsValid(ply) and (not ply.UseTimer or ply.UseTimer < CurTime()) then + ply.UseTimer = CurTime() + 2 + net.Start("MQS.OpenNPCMenu") + net.WriteEntity(self) + net.Send(ply) + end +end + +function ENT:Think() + self:NextThink( CurTime() ) + return true +end \ No newline at end of file diff --git a/addons/mc_quests/lua/entities/mqs_npc/shared.lua b/addons/mc_quests/lua/entities/mqs_npc/shared.lua new file mode 100644 index 0000000..6e8bc64 --- /dev/null +++ b/addons/mc_quests/lua/entities/mqs_npc/shared.lua @@ -0,0 +1,16 @@ +--leak by matveicher +--vk group - https://vk.com/gmodffdev +--steam - https://steamcommunity.com/profiles/76561198968457747/ +--ds server - https://discord.gg/V329W7Ce8g +--ds - matveicher#5801 +ENT.Type = "ai" +ENT.Base = "base_anim" +ENT.PrintName = "MQS Quest NPC" +ENT.Author = "Mactavish" +ENT.Spawnable = false +ENT.AdminSpawnable = true + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "Namer") + self:NetworkVar("Int", 1, "UID") +end \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/core/cl_hooks.lua b/addons/mc_quests/lua/mqs/core/cl_hooks.lua new file mode 100644 index 0000000..539dd8f --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/cl_hooks.lua @@ -0,0 +1,405 @@ +local ply = LocalPlayer() +local AlphaMask = Color(0, 0, 0, 0) +MCS.Config.Main.NPCColor = Color(0, 155, 255, 255) +local icon_color = MCS.Config.Main.NPCColor +local n_loop = 0 +local scrw, scrh = ScrW(), ScrH() +local scrw_10 = scrw / 10 +local time_precc = nil + +local color_black = Color(0, 0, 0, 255) + +local cam_effect, cam_newdata, cam_inprogress = 2, false, nil + +MQS.UIEffect = {} +MQS.CCList = {} + +MQS.UIEffect["Cinematic camera"] = function(data) + data.cam_speed = data.cam_speed / 10 + data.fov_speed = data.fov_speed / 10 + + table.insert(MQS.CCList, data) + + cam_newdata = true +end + +MQS.UIEffect["Quest End"] = function(data) + if MQS.Music and ply:UserID() == data.uid then + MQS.Music:Stop() + MQS.Music = nil + MQS.Music_name = nil + MQS.Music_author = nil + end + if timer.Exists("MQS.PlayerTracker" .. data.id) then + timer.Remove("MQS.PlayerTracker" .. data.id) + end +end + +MQS.UIEffect["Music"] = function(data) + if MQS.Music then + MQS.Music:Stop() + MQS.Music = nil + MQS.Music_name = nil + MQS.Music_author = nil + end + + local url = false + local soundpath = data.path + if soundpath == "" then return end + + if string.StartWith(soundpath, "http") then + url = true + end + + if not string.StartWith(soundpath, "sound/") and not url then + soundpath = "sound/" .. soundpath + end + + + local s_vol = 1 + + local vol_str = string.match(soundpath, "_vol(%d*%.?%d+)") + if vol_str then + s_vol = tonumber(vol_str) or 1 + end + + if url then + sound.PlayURL(soundpath, "noplay", function(station) + if (IsValid(station)) then + MQS.Music = station + local s_author, s_name = soundpath:match(".+/(.-)%-(.+)%.") + MQS.Music_name = s_name + MQS.Music_author = s_author + MQS.Music:Play() + MQS.Music:SetVolume(s_vol) + else + LocalPlayer():ChatPrint("[MQS] Invalid sound URL", soundpath) + end + end) + else + sound.PlayFile(soundpath, "noplay", function(station, errCode, errStr) + if (IsValid(station)) then + MQS.Music = station + local s_author, s_name = soundpath:match(".+/(.-)%-(.+)%.") + MQS.Music_name = s_name + MQS.Music_author = s_author + MQS.Music:Play() + MQS.Music:SetVolume(s_vol) + else + print("[MQS] Error playing sound", soundpath, errCode, errStr) + end + end) + end +end + +MQS.UIEffect["UnTrack"] = function(data) + if timer.Exists("MQS.PlayerTracker" .. data.id) then + timer.Remove("MQS.PlayerTracker" .. data.id) + end +end + +MQS.UIEffect["Track"] = function(data) + local rply = Player(data.uid) + local icon = MSD.PinPoints[data.icon or 0] + local text = data.text + local teams = data.teams + + MQS.DoNotify(MSD.GetPhrase("warning"), text, 4) + + timer.Create("MQS.PlayerTracker" .. data.id, 7, 0, function() + if not IsValid(rply) or not teams[team.GetName(ply:Team())] then + timer.Remove("MQS.PlayerTracker" .. data.id) + return + end + MQS.UdpateTracking(rply:GetPos(), icon) + end) +end + +local function UpdateCam(cid) + cam_newdata = false + + if not MQS.CCList[cid] then + MQS.CCList = {} + cam_newdata, cam_inprogress = false, nil + timer.Simple(1, function() + MQS.CCameraData = nil + MQS.CCam = nil + end) + return + end + + local data = MQS.CCList[cid] + + local function camProcess() + if data.effect then cam_effect = 1 else cam_effect = 2 end + MQS.CCameraData = data + MQS.CCameraData.starttime = CurTime() + + local cd, bn = MQS.TableCompress({ name = "Cinematic camera", pos = data.cam_start.pos, time = data.endtime }) + + net.Start("MQS.UIEffect") + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() + end + + if data.delay then + timer.Simple(tonumber(data.delay) or 1, camProcess) + timer.Simple(0.9, function() + MQS.CCameraData = nil + MQS.CCam = nil + end) + else + camProcess() + end +end + +function MQS.HudNotification() end + +function MQS.HudTaskNotify() end + +function MQS.HudHint() end + +function MQS.TrackPlayer() end + +function MQS.HUDPaint() + MQS.HudNotification() + MQS.HudTaskNotify() + MQS.HudHint() + MQS.TrackPlayer() + MQS.EntInfo() + local x, y, of1, of2 = 25 + (scrw * MQS.Config.UI.HudOffsetX), 25 + (scrh * MQS.Config.UI.HudOffsetY), false, false + + if MQS.Config.UI.HudAlignX then + x = scrw - 25 - (scrw * MQS.Config.UI.HudOffsetX) + of1 = true + end + + if MQS.Config.UI.HudAlignY then + y = scrh - 25 - (scrw * MQS.Config.UI.HudOffsetY) + of2 = true + end + + MQS.DrawQuestInfo(x, y, of1, of2) + MQS.PendingQuest(x, y, of1, of2) +end + +function MQS.Draw3DZone(pos, rad, clr, detail, thicc) + render.SetStencilEnable(true) + render.SetStencilWriteMask(0xFF) + render.SetStencilTestMask(0xFF) + render.SetStencilReferenceValue(0) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_KEEP) + render.SetStencilFailOperation(STENCIL_KEEP) + render.SetStencilZFailOperation(STENCIL_KEEP) + render.SetColorMaterial() + render.ClearStencil() + --All + render.SetStencilReferenceValue(7) + render.SetStencilZFailOperation(STENCILOPERATION_REPLACE) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) + render.DrawSphere(pos, -rad, detail, detail, AlphaMask) + --Under + render.SetStencilReferenceValue(7) + render.SetStencilZFailOperation(STENCILOPERATION_DECR) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) + render.DrawSphere(pos, rad, detail, detail, AlphaMask) + --Inner + render.SetStencilReferenceValue(7) + render.SetStencilZFailOperation(STENCILOPERATION_INCR) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) + render.DrawSphere(pos, -math.max(rad - thicc, 0), detail, detail, AlphaMask) + render.SetStencilZFailOperation(STENCILOPERATION_DECR) + -- Overall + render.SetStencilReferenceValue(7) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) + render.DrawSphere(pos, math.max(rad - thicc, 0), detail, detail, AlphaMask) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + + cam.IgnoreZ(true) + render.SetStencilReferenceValue(7) + render.DrawSphere(pos, rad + thicc, detail, detail, clr) + render.DrawSphere(pos, -rad, detail, detail, clr) + cam.IgnoreZ(false) + render.SetStencilEnable(false) +end + +hook.Add("HUDPaint", "MQS.HUDPaint", MQS.HUDPaint) + +hook.Add("DrawOverlay", "MQS.DrawOverlay", function() + if MQS.CCam then + if MQS.Config.CamFix then + cam.Start2D() + render.RenderView({ + origin = MQS.CCam.pos, + angles = MQS.CCam.ang, + fov = MQS.CCam.fov, + drawviewmodel = false, + drawhud = false, + x = 0, + y = 0, + w = ScrW(), + h = ScrH() + }) + cam.End2D() + end + draw.RoundedBox(0, 0, 0, scrw, scrh / 10, color_black) + draw.RoundedBox(0, 0, scrh - scrh / 10, scrw, scrh / 10, color_black) + if not MQS.PreCC then + draw.SimpleTextOutlined(MQS.CCameraData.text, "MSDFont.Big", scrw / 2, scrh - scrh / 10 + 10, icon_color, + TEXT_ALIGN_CENTER, 0, 1, color_black) + end + end + + if MQS.PreCC then + if not time_precc then + time_precc = CurTime() + 1 + end + + if cam_effect == 1 then + for i = 0, scrw_10 - 1 do + draw.RoundedBox(0, scrw_10 * i, 0, MQS.PreCC * (scrw_10 + 20), scrh, color_black) + end + else + draw.RoundedBox(0, 0, 0, scrw, scrh, MSD.ColorAlpha(color_black, MQS.PreCC * 260)) + end + + if time_precc > CurTime() then + MQS.PreCC = Lerp(FrameTime() * 5, MQS.PreCC, 1) + else + MQS.PreCC = Lerp(FrameTime() * 5, MQS.PreCC, 0) + + if MQS.PreCC < 0.01 then + MQS.PreCC = nil + time_precc = nil + end + end + end +end) + +hook.Add("Think", "MQS.ProcessClient", function() + if n_loop < CurTime() and MQS.HasQuest() then + local q = MQS.HasQuest() + if MQS.Quests[q.quest].stop_anytime or (MQS.GetNWdata(ply, "loops") and not MQS.Quests[q.quest].reward_on_time and MQS.GetNWdata(ply, "loops") > 0) then + if input.IsKeyDown(MQS.Config.StopKey) then + if not MQS.KeyHOLD then + MQS.KeyHOLD = CurTime() + 2 + elseif MQS.KeyHOLD < CurTime() then + MQS.KeyHOLD = nil + n_loop = CurTime() + 5 + RunConsoleCommand("mqs_stop") + end + else + MQS.KeyHOLD = nil + end + end + end + + if not MQS.HasQuest() and not MQS.Config.IntoQuestAutogive and MQS.CanPlayIntro(ply) then + if input.IsKeyDown(MQS.Config.StopKey) then + if not MQS.KeyHOLD then + MQS.KeyHOLD = CurTime() + 2 + elseif MQS.KeyHOLD < CurTime() then + MQS.KeyHOLD = nil + n_loop = CurTime() + 5 + RunConsoleCommand("mqs_start", MQS.Config.IntoQuest) + end + else + MQS.KeyHOLD = nil + end + end + + if cam_newdata and not cam_inprogress then + cam_newdata = false + cam_inprogress = 1 + UpdateCam(1) + end + + if MQS.CCameraData then + local cc = MQS.CCameraData + local CT = CurTime() + + if cc.starttime + 1 > CT then + if not MQS.PreCC then + MQS.PreCC = 0 + end + + return + end + + cc.cam_start.pos = Lerp(FrameTime() * cc.cam_speed, cc.cam_start.pos, cc.cam_end.pos) + cc.cam_start.ang = Lerp(FrameTime() * cc.cam_speed, cc.cam_start.ang, cc.cam_end.ang) + cc.cam_start.fov = Lerp(FrameTime() * cc.fov_speed, cc.cam_start.fov, cc.cam_end.fov) + + MQS.CCam = { + pos = cc.cam_start.pos, + ang = cc.cam_start.ang, + fov = cc.cam_start.fov, + } + + if cc.endtime and cc.endtime + cc.starttime < CT then + cc.endtime = nil + + if not MQS.PreCC then + MQS.PreCC = 0 + end + + cam_inprogress = cam_inprogress + 1 + UpdateCam(cam_inprogress) + end + end +end) + +hook.Add("HUDShouldDraw", "MQS.HUDShouldDraINTRO", function(name) + if MQS.CCam then return false end +end) + +hook.Add("CalcView", "MQS.GetCamData", function(_, pos, angles, fov) + if MQS.CCam and not MQS.Config.CamFix then + local view = { + origin = MQS.CCam.pos, + angles = MQS.CCam.ang, + fov = MQS.CCam.fov, + drawviewer = true + } + + return view + end +end) + +hook.Add("PostDrawTranslucentRenderables", "MQS.PostDrawTranslucentRenderables", function() + local q = MQS.HasQuest() + + if not q then return end + + + local obj = MQS.GetNWdata(ply, "quest_objective") + obj = MQS.Quests[q.quest].objects[obj] + if not obj.mark_area or not obj.point then return end + if obj.point:DistToSqr(LocalPlayer():GetPos()) > (MQS.Config.QuestEntDrawDist * 5) ^ 2 then return end + local dist = obj.dist or obj.stay_inarea or 350 + MQS.Draw3DZone(obj.point, dist, icon_color, 50, 5) +end) + +net.Receive("MQS.UIEffect", function() + local ef_name = net.ReadString() + local ef_data = net.ReadTable() + + if MQS.UIEffect[ef_name] then + MQS.UIEffect[ef_name](ef_data) + end +end) + +net.Receive("MQS.TaskNotify", function() + local text = net.ReadString() + local type = net.ReadInt(16) + MQS.DoTaskNotify(text, type) +end) + +net.Receive("MQS.Notify", function() + local text1 = net.ReadString() + local text2 = net.ReadString() + local type = net.ReadInt(16) + MQS.DoNotify(text1, text2, type) +end) diff --git a/addons/mc_quests/lua/mqs/core/cl_hud.lua b/addons/mc_quests/lua/mqs/core/cl_hud.lua new file mode 100644 index 0000000..045a1e3 --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/cl_hud.lua @@ -0,0 +1,616 @@ +local ply = LocalPlayer() +local ScrW, ScrH = ScrW, ScrH +local info_icon = Material("mqs/icons/info.png", "smooth") +local center_gradient = Material("gui/center_gradient.vtf", "smooth") +local veh_marker = Material("mqs/map_markers/v1.png", "smooth") + +MCS.Config.Main.NPCColor = Color(0, 155, 255, 255) + +local w1, w2, w3, wm, ch, alpha, dist_m = 0, 0, 0, 0, 0, 0, 0 +local icon_color = MCS.Config.Main.NPCColor +local color_red = Color(231, 0, 0) + +local objective_draw = {} +local objective_text = {} + +local vehicle_req_task = { + ["Move to point"] = true, + ["Leave area"] = true, + ["Wait time"] = true, +} + +local notification_types = { + [1] = { MCS.Config.Main.NPCColor, "mqs/notify/1.mp3" }, + [2] = { Color(231, 0, 0), "mqs/fail/1.mp3" }, + [3] = { Color(50, 250, 0), "mqs/notify/3.mp3" }, + [4] = { Color(0, 129, 250), "mqs/fail/2.mp3" }, +} + +local tasknotify_types = { + [1] = { MCS.Config.Main.NPCColor, "mqs/start/2.mp3" }, + [2] = { Color(231, 0, 0), "mqs/fail/1.mp3" }, + [3] = { Color(50, 250, 0), "mqs/done/4.mp3" }, + [4] = { Color(255, 255, 255), "mqs/done/4.mp3" }, +} + +objective_draw["Wait time"] = function(q, obj) + if not obj.stay_inarea then return end + local x, y = ScrW(), ScrH() + local pl_pos = ply:GetPos() + local dist = pl_pos:DistToSqr(obj.point) + local t_dist = obj.stay_inarea / 3 + t_dist = t_dist * t_dist + + if dist < t_dist then + alpha = Lerp(FrameTime() * 7, alpha, 0) + else + alpha = Lerp(FrameTime() * 7, alpha, 1) + end + + surface.SetDrawColor(0, 0, 0, alpha * 170) + surface.SetMaterial(center_gradient) + surface.DrawTexturedRectRotated(x / 2, y / 8, 250, alpha * (x + 200), -90) + local _, h = draw.SimpleTextOutlined(MSD.GetPhrase("warning"), "MSDFont.Biger", x / 2, y / 8 - 50, + MSD.ColorAlpha(color_red, alpha * 255), TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, alpha * 100)) + draw.SimpleTextOutlined(MSD.GetPhrase("q_must_stay_area"), "MSDFont.36", x / 2, y / 8 + h - 50, + MSD.ColorAlpha(color_white, alpha * 255), TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, alpha * 100)) +end + +objective_draw["Collect quest ents"] = function(q, obj, x, y, al1, al2) + local pl_pos = ply:GetPos() + + if obj.show_ents then + local ent_l = MQS.ActiveTask[q.id].ents + if not ent_l then return end + local pos + local dt + + for i, v in pairs(ent_l) do + local e = Entity(v) + if not IsValid(e) then continue end + local dist = pl_pos:DistToSqr(e:GetPos()) + + if not dt or dt > dist then + dt = dist + pos = e:GetPos() + end + end + + if pos then + local dist = pl_pos:Distance(pos) * 0.75 / 25.4 + + if MQS.Config.UI.HUDBG == 2 then + draw.RoundedBox(8, al1 and x - ch - 10 or x - 10, al2 and y - 105 or y + 80, ch + 20, 25, MSD.Theme["m"]) + end + + ch = draw.SimpleTextOutlined("- " .. MSD.GetPhrase("dist_to_close") .. ": " .. math.floor(dist) .. " m", + "MSDFont.22", x, al2 and y - 105 or y + 80, color_white, al1 and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, + 1, color_black) + end + end + + local pos = obj.point + local dist = obj.dist or 500 + if dist ^ 2 > pl_pos:DistToSqr(pos) then return end + dist = pl_pos:Distance(pos) * 0.75 / 25.4 + local screenpos = pos:ToScreen() + x, y = screenpos.x, screenpos.y - 50 + local icon = obj.marker and MSD.PinPoints[obj.marker] + MSD.DrawTexturedRect(x - 24, y - 22, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(color_black, 100 + alpha * 125)) + MSD.DrawTexturedRect(x - 24, y - 24, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(icon_color, 100 + alpha * 125)) + draw.SimpleTextOutlined(math.floor(dist) .. " m", "MSDFont.25", x, y + 35, MSD.ColorAlpha(color_white, 200), + TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, 200)) +end + +objective_draw["Kill NPC"] = function(q, obj, x, y, al1, al2) + if not obj.show_ents then return end + + local pl_pos = ply:GetPos() + local ent_l = MQS.ActiveTask[q.id].misc_ents + if not ent_l then return end + local pos + local dt + + for _, v in pairs(ent_l) do + local e = Entity(v) + if not IsValid(e) then continue end + if not e:GetNWBool("MQSTarget") then continue end + + local epos = e:GetPos() + local dist = pl_pos:DistToSqr(epos) + + if not dt or dt > dist then + dt = dist + pos = epos + end + + if dist > (obj.dist and obj.dist ^ 2 or 1000) then + epos.z = epos.z + 50 + local screenpos = epos:ToScreen() + local sx, sy = screenpos.x, screenpos.y + local icon = obj.marker and MSD.PinPoints[obj.marker] + MSD.DrawTexturedRect(sx - 12, sy - 10, 24, 24, icon or MSD.PinPoints[0], MSD.ColorAlpha(color_black, 100)) + MSD.DrawTexturedRect(sx - 12, sy - 12, 24, 24, icon or MSD.PinPoints[0], MSD.ColorAlpha(color_red, 100)) + if e:Alive() then + draw.RoundedBox(0, sx - 25, sy + 24, 50, 8, MSD.ColorAlpha(color_black, 100)) + draw.RoundedBox(0, sx - 25, sy + 24, 50 * math.Clamp(e:Health() / e:GetMaxHealth(), 0, 1), 8, + MSD.ColorAlpha(color_red, 100)) + draw.SimpleText(e:Health() .. "хп", "ui.12", sx, sy + 26, color_white, TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + end + end + end + + if pos then + local dist = pl_pos:Distance(pos) * 0.75 / 25.4 + + if MQS.Config.UI.HUDBG == 2 then + draw.RoundedBox(8, al1 and x - ch - 10 or x - 10, al2 and y - 105 or y + 80, ch + 20, 25, MSD.Theme["m"]) + end + + ch = draw.SimpleTextOutlined("- " .. MSD.GetPhrase("dist_to_close") .. ": " .. math.floor(dist) .. " m", + "MSDFont.22", x, al2 and y - 105 or y + 80, color_white, al1 and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, + color_black) + end +end + +objective_draw["Move to point"] = function(q, obj) + local pos = obj.point + local screenpos = pos:ToScreen() + local x, y = screenpos.x, screenpos.y - 50 + + if x < ScrW() / 4 or x > ScrW() - ScrW() / 4 then + alpha = Lerp(FrameTime() * 7, alpha, 0) + + if x < 30 then + x = 30 + end + else + dist_m = ply:GetPos():Distance(pos) * 0.75 / 25.4 + alpha = Lerp(FrameTime() * 7, alpha, 1) + end + + if y < 50 then + y = 50 + end + + if x > ScrW() - 48 then + x = ScrW() - 48 + end + + if y > ScrH() - 48 then + y = ScrH() - 48 + end + + local icon = obj.marker and MSD.PinPoints[obj.marker] + MSD.DrawTexturedRect(x - 24, y - 22, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(color_black, 100 + alpha * 125)) + MSD.DrawTexturedRect(x - 24, y - 24, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(icon_color, 100 + alpha * 125)) + draw.SimpleTextOutlined(math.floor(dist_m) .. " m", "MSDFont.25", x, y + 35, MSD.ColorAlpha(color_white, alpha * 200), + TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, alpha * 200)) +end + +objective_draw["Talk to NPC"] = function(q, obj) + local npc + + for _, ent in ipairs(ents.FindByClass("mcs_npc")) do + if ent:GetUID() == obj.npc then + npc = ent + break + end + end + + if not npc then return end + local pos = npc:GetPos() + if ply:GetPos():DistToSqr(pos) < 20000 then return end + local screenpos = pos:ToScreen() + local x, y = screenpos.x, screenpos.y - 20 + + if x < ScrW() / 4 or x > ScrW() - ScrW() / 4 then + alpha = Lerp(FrameTime() * 7, alpha, 0) + + if x < 30 then + x = 30 + end + else + dist_m = ply:GetPos():Distance(pos) * 0.75 / 25.4 + alpha = Lerp(FrameTime() * 7, alpha, 1) + end + + if y < 50 then + y = 50 + end + + if x > ScrW() - 48 then + x = ScrW() - 48 + end + + if y > ScrH() - 48 then + y = ScrH() - 48 + end + + local icon = obj.marker and MSD.PinPoints[obj.marker] + MSD.DrawTexturedRect(x - 24, y - 22, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(color_black, 100 + alpha * 125)) + MSD.DrawTexturedRect(x - 24, y - 24, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(icon_color, 100 + alpha * 125)) + draw.SimpleTextOutlined(math.floor(dist_m) .. " m", "MSDFont.25", x, y + 35, MSD.ColorAlpha(color_white, alpha * 200), + TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, alpha * 200)) +end + +objective_draw["Enter vehicle"] = function(vehicle) + local pos = vehicle:GetPos() + local screenpos = pos:ToScreen() + local x, y = screenpos.x, screenpos.y - 50 + + if x < ScrW() / 4 or x > ScrW() - ScrW() / 4 then + alpha = Lerp(FrameTime() * 7, alpha, 0) + + if x < 30 then + x = 30 + end + else + alpha = Lerp(FrameTime() * 7, alpha, 1) + dist_m = ply:GetPos():Distance(pos) * 0.75 / 25.4 + end + + if y < 50 then + y = 50 + end + + if x > ScrW() - 48 then + x = ScrW() - 48 + end + + if y > ScrH() - 48 then + y = ScrH() - 48 + end + + MSD.DrawTexturedRect(x - 24, y - 22, 48, 48, veh_marker, MSD.ColorAlpha(color_black, 100 + alpha * 125)) + MSD.DrawTexturedRect(x - 24, y - 24, 48, 48, veh_marker, MSD.ColorAlpha(icon_color, 100 + alpha * 125)) + draw.SimpleTextOutlined(math.floor(dist_m) .. " m", "MSDFont.25", x, y + 35, MSD.ColorAlpha(color_white, alpha * 200), + TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, alpha * 200)) +end + +objective_text["Collect quest ents"] = function() + return MQS.GetSelfNWdata(ply, "quest_colected") .. + "/" .. MQS.GetSelfNWdata(ply, "quest_ent") +end + +objective_text["Wait time"] = function(q, obj) + return obj.show_timer and + os.date("%M:%S", tonumber(MQS.GetSelfNWdata(ply, "quest_wait") - CurTime())) or "" +end + +objective_text["Kill random target"] = function(q, obj) + return obj.target_count - MQS.GetSelfNWdata(ply, "targets") .. + "/" .. obj.target_count +end + +function MQS.DoNotify(text1, text2, type) + local startt = CurTime() + local x, y = ScrW(), ScrH() + local color = notification_types[type][1] + local anim = 0 + surface.PlaySound(notification_types[type][2]) + + MQS.HudNotification = function() + if MQS.CCam then return end + surface.SetDrawColor(255, 255, 255, anim * 170) + surface.DrawOutlinedRect(x / 2 - anim * (x - 2) / 2, 50, anim * (x - 2), 180, 2) + local _, h = draw.SimpleTextOutlined(text1, "MSDFont.Biger", x / 2, y / 8 - 50, MSD.ColorAlpha(color, anim * 255), + TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, anim * 255)) + text2 = MSD.TextWrap(text2, "MSDFont.28", y) + draw.DrawText(text2, "MSDFont.36", x / 2 + 1, y / 8 + h - 49, MSD.ColorAlpha(color_black, anim * 255), + TEXT_ALIGN_CENTER, 0) + draw.DrawText(text2, "MSDFont.36", x / 2, y / 8 + h - 50, MSD.ColorAlpha(color_white, anim * 255), + TEXT_ALIGN_CENTER, 0) + + if startt + 8 < CurTime() then + anim = Lerp(FrameTime() * 3, anim, 0) + else + anim = Lerp(FrameTime() * 5, anim, 1) + end + + if startt + 10 < CurTime() then + MQS.HudNotification = function() end + end + end +end + +function MQS.DoTaskNotify(text, type) + local startt = CurTime() + local x, y = ScrW(), ScrH() + local color = tasknotify_types[type][1] + local anim = 0 + surface.PlaySound(tasknotify_types[type][2]) + + MQS.HudTaskNotify = function() + if MQS.CCam then return end + surface.SetDrawColor(0, 0, 0, anim * 100) + surface.SetMaterial(center_gradient) + surface.DrawTexturedRectRotated(x / 2, y - anim * (y / 8), 250, x + 200, -90) + draw.SimpleTextOutlined(text, "MSDFont.Big", x / 2, y - anim * (y / 8 + 10), MSD.ColorAlpha(color, anim * 255), + TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, anim * 255)) + + if startt + 5 < CurTime() then + anim = Lerp(FrameTime() * 3, anim, 0) + else + anim = Lerp(FrameTime() * 5, anim, 1) + end + + if startt + 10 < CurTime() then + MQS.HudTaskNotify = function() end + end + end +end + +function MQS.DoHint(text, type) + MQS.DoTaskNotify(text, type) + local startt = CurTime() + local anim = 0 + local npc_table = ents.FindByClass("mqs_npc") + + if MCS then + for _, ent in pairs(ents.FindByClass("mcs_npc")) do + if IsValid(ent) and MCS.Spawns[ent:GetUID()] and MCS.Spawns[ent:GetUID()].questNPC then + table.insert(npc_table, ent) + end + end + end + + MQS.HudHint = function() + for _, ent in pairs(npc_table) do + if not IsValid(ent) then continue end + + if not ent.uialpha then + ent.uialpha = 0.3 + end + + if not ent.uidist then + ent.uidist = 0 + end + + local pos = ent:GetPos() + local screenpos = pos:ToScreen() + local sx, sy = screenpos.x, screenpos.y - 50 + local x, y = ScrW() / 2, ScrH() / 2 + x = Lerp(anim, x, sx) + y = Lerp(anim, y, sy) + + if startt + 28 > CurTime() then + if x < ScrW() / 3 or x > ScrW() - ScrW() / 3 then + ent.uialpha = Lerp(FrameTime() * 7, ent.uialpha, 0.2) + + if x < 30 then + x = 30 + end + else + ent.uialpha = Lerp(FrameTime() * 7, ent.uialpha, 1) + ent.uidist = ply:GetPos():Distance(pos) * 0.75 / 25.4 + end + + if anim > 0.9 then + draw.SimpleTextOutlined(math.floor(ent.uidist) .. " m", "MSDFont.25", x, y + 35, + MSD.ColorAlpha(color_white, ent.uialpha * 255 - 55), TEXT_ALIGN_CENTER, 0, 1, + MSD.ColorAlpha(color_black, ent.uialpha * 255 - 55)) + end + else + ent.uialpha = Lerp(FrameTime() * 7, ent.uialpha, 0) + end + + if y < 50 then + y = 50 + end + + if x > ScrW() - 48 then + x = ScrW() - 48 + end + + if y > ScrH() - 48 then + y = ScrH() - 48 + end + + MSD.DrawTexturedRect(x - 24, y - 22, 48, 48, MSD.Icons48.account, + MSD.ColorAlpha(color_black, ent.uialpha * 255)) + MSD.DrawTexturedRect(x - 24, y - 24, 48, 48, MSD.Icons48.account, + MSD.ColorAlpha(color_white, ent.uialpha * 255)) + end + + if startt + 1.5 <= CurTime() then + anim = Lerp(FrameTime() * 2, anim, 1) + end + + if startt + 30 < CurTime() then + MQS.HudHint = function() end + end + end +end + +function MQS.UdpateTracking(pos, icon) + local startt = CurTime() + local anim = 0 + local dist = 0 + local alp = 0 + + MQS.TrackPlayer = function() + local screenpos = pos:ToScreen() + local x, y = screenpos.x, screenpos.y - 50 + + if x < ScrW() / 4 or x > ScrW() - ScrW() / 4 then + alp = Lerp(FrameTime() * 7, alp, 0) + + if x < 30 then + x = 30 + end + else + dist = ply:GetPos():Distance(pos) * 0.75 / 25.4 + alp = Lerp(FrameTime() * 7, alp, 1) + end + + local a = alp * 200 + + if y < 50 then + y = 50 + end + + if x > ScrW() - 48 then + x = ScrW() - 48 + end + + if y > ScrH() - 48 then + y = ScrH() - 48 + end + + MSD.DrawTexturedRect(x - 24, y - 22, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(color_black, 55 + a * anim)) + MSD.DrawTexturedRect(x - 24, y - 24, 48, 48, icon or MSD.PinPoints[0], MSD.ColorAlpha(color_red, 55 + a * anim)) + draw.SimpleTextOutlined(math.floor(dist) .. " m", "MSDFont.25", x, y + 35, MSD.ColorAlpha(color_white, a * anim), + TEXT_ALIGN_CENTER, 0, 1, MSD.ColorAlpha(color_black, a * anim)) + + if startt + 6 < CurTime() then + anim = Lerp(FrameTime() * 3, anim, 0) + else + anim = Lerp(FrameTime() * 3, anim, 1) + end + + if startt + 7 < CurTime() then + MQS.TrackPlayer = function() end + end + end +end + +function MQS.PendingQuest(x, y, right, bottom) + local q = MQS.HasQuest() + if q or not (not MQS.Config.IntoQuestAutogive and MQS.CanPlayIntro(ply)) then return end + local qw = MQS.Quests[MQS.Config.IntoQuest] + + if MQS.Config.UI.HUDBG == 1 then + MSD.DrawTexturedRect(right and x - wm - 75 or x - 25, bottom and y - 55 or y - 10, wm + 100, 65, + right and MSD.Materials.gradient_right or MSD.Materials.gradient, MSD.Theme["m"]) + MSD.DrawTexturedRect(right and x - wm - 75 or x - 25, bottom and y - 107 or y + 55, wm + 100, 52, + right and MSD.Materials.gradient_right or MSD.Materials.gradient, MSD.Theme["d"]) + end + + if MQS.Config.UI.HUDBG == 2 then + draw.RoundedBox(8, right and x - wm - 80 or x - 10, bottom and y - 55 or y - 10, wm + 90, 65, MSD.Theme["d"]) + draw.RoundedBox(8, right and x - w3 - 10 or x - 10, bottom and y - 80 or y + 55, w3 + 20, 25, MSD.Theme["m"]) + end + + MSD.DrawTexturedRect(right and x - 48 or x, bottom and y - 48 or y, 48, 48, + qw.custom_icon and MSD.ImgLib.GetMaterial(qw.custom_icon) or info_icon, color_white) + + local tw = draw.SimpleTextOutlined(qw.desc, "MSDFont.28", right and x - 55 or x + 55, bottom and y - 38 or y + 10, + color_white, right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, color_black) + local tw2 = draw.SimpleTextOutlined( + MSD.GetPhrase("into_quest_start", string.upper(input.GetKeyName(MQS.Config.StopKey))), "MSDFont.25", + right and x - 70 - tw or x + 70 + tw, bottom and y - 36 or y + 12, icon_color, + right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, color_black) + if MQS.KeyHOLD then + draw.RoundedBox(2, right and x - 70 - tw or x + 70 + tw, bottom and y - 36 or y + 42, + tw2 * ((MQS.KeyHOLD - CurTime()) / 2), 5, icon_color) + end +end + +local music_prog_c = MCS.Config.Main.NPCColor + +function MQS.DrawQuestInfo(x, y, right, bottom) + local q = MQS.HasQuest() + local string_add = "" + local time = "" + + local bx = right and x - wm - 80 or x - 20 + local by = bottom and y - 55 or y - 20 - 65 + local bw = wm + 90 + local bh = 65 + + if MQS.Music and MQS.Music:IsValid() and (MQS.Music:GetLength() - MQS.Music:GetTime() > 0) and MQS.Music_name and MQS.Music_author then + surface.SetMaterial(Material("vgui/gradient-l")) + surface.SetDrawColor(MSD.Theme["d"]) + surface.DrawTexturedRect(bx, by, bw, bh) + + draw.SimpleTextOutlined("Сейчас играет: " .. MQS.Music_name, "MSDFont.28", right and x - wm - 80 or x - 10, + bottom and y - 55 or y - 20 - 65, color_white, right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, + color_black) + draw.SimpleTextOutlined("Исполнитель: " .. MQS.Music_author, "ui.20", right and x - wm - 80 or x - 10, + bottom and y - 55 or y - 50, color_white, right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, + color_black) + draw.RoundedBox(8, right and x - wm - 80 or x - 20, bottom and y - 55 or y - 20 - 65, + (wm + 90) * math.Clamp(MQS.Music:GetTime() / MQS.Music:GetLength(), 0, 1), 5, music_prog_c) + end + + if not q or not IsValid(ply) then return end + local obj = MQS.GetNWdata(ply, "quest_objective") + if not obj then return end + obj = MQS.Quests[q.quest].objects[obj] + + if MQS.Config.UI.HUDBG == 1 then + MSD.DrawTexturedRect(right and x - wm - 75 or x - 25, bottom and y - 55 or y - 10, wm + 100, 65, + right and MSD.Materials.gradient_right or MSD.Materials.gradient, MSD.Theme["m"]) + MSD.DrawTexturedRect(right and x - wm - 75 or x - 25, bottom and y - 107 or y + 55, wm + 100, 52, + right and MSD.Materials.gradient_right or MSD.Materials.gradient, MSD.Theme["d"]) + end + + if MQS.Config.UI.HUDBG == 2 then + draw.RoundedBox(8, right and x - wm - 80 or x - 10, bottom and y - 55 or y - 10, wm + 90, 65, MSD.Theme["d"]) + draw.RoundedBox(8, right and x - w3 - 10 or x - 10, bottom and y - 80 or y + 55, w3 + 20, 25, MSD.Theme["m"]) + end + + MSD.DrawTexturedRect(right and x - 48 or x, bottom and y - 48 or y, 48, 48, + MQS.Quests[q.quest].custom_icon and MSD.ImgLib.GetMaterial(MQS.Quests[q.quest].custom_icon) or info_icon, + color_white) + + if MQS.GetNWdata(ply, "do_time") then + time = ". " .. + MSD.GetPhrase("q_time_left") .. " - " .. os.date("%M:%S", MQS.GetNWdata(ply, "do_time") - CurTime()) + end + + w1 = draw.SimpleTextOutlined(MSD.GetPhrase("q_in_progress") .. time, "MSDFont.28", right and x - 55 or x + 55, + bottom and y - 38 or y + 10, color_white, right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, color_black) + + if MQS.Quests[q.quest].stop_anytime or (MQS.GetNWdata(ply, "loops") and not MQS.Quests[q.quest].reward_on_time and MQS.GetNWdata(ply, "loops") > 0) then + w2 = draw.SimpleTextOutlined( + MSD.GetPhrase("q_hold_key_stop", string.upper(input.GetKeyName(MQS.Config.StopKey))), "MSDFont.25", + right and x - 70 - w1 or x + 70 + w1, bottom and y - 36 or y + 12, icon_color, + right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, color_black) + + if MQS.KeyHOLD then + draw.RoundedBox(2, right and x - 70 - w1 or x + 70 + w1, bottom and y - 36 or y + 42, + w2 * ((MQS.KeyHOLD - CurTime()) / 2), 5, icon_color) + end + end + + if objective_text[obj.type] then + string_add = " " .. objective_text[obj.type](q, obj) + end + + local vehicle = MQS.ActiveTask[q.id].vehicle and Entity(MQS.ActiveTask[q.id].vehicle) + + if IsValid(vehicle) and MQS.GetActiveVehicle(ply) ~= vehicle and vehicle_req_task[obj.type] and not obj.ignore_veh then + w3 = draw.SimpleTextOutlined("- " .. MSD.GetPhrase("q_enter_veh"), "MSDFont.22", x, bottom and y - 80 or y + 55, + color_white, right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, color_black) + objective_draw["Enter vehicle"](vehicle) + else + w3 = draw.SimpleTextOutlined("- " .. obj.desc .. string_add, "MSDFont.22", x, bottom and y - 80 or y + 55, + color_white, right and TEXT_ALIGN_RIGHT or TEXT_ALIGN_LEFT, 0, 1, color_black) + + if objective_draw[obj.type] then + objective_draw[obj.type](q, obj, x, y, right, bottom) + end + end + + wm = math.max(w1 + w2, w3) +end + +function MQS.EntInfo() + local ent = LocalPlayer():GetEyeTrace().Entity + + if not IsValid(ent) or ent:GetClass() ~= "mqs_ent" or not ent:GetUseHold() then return end + + local ent_p = ent:GetPickProgress() + + if ent_p and ent_p > 0 then + local x, y = ScrW() / 2, ScrH() / 2 + 100 + local wd = draw.SimpleTextOutlined(MSD.GetPhrase("hold_use", string.upper(input.LookupBinding("+use"))), + "MSDFont.25", x, y, color_white, TEXT_ALIGN_CENTER, 0, 1, color_black) + local pr = wd * ent_p + draw.RoundedBox(2, (x - wd / 2) - 1, y + 24, wd + 2, 7, color_black) + draw.RoundedBox(2, x - wd / 2, y + 25, pr, 5, icon_color) + end +end diff --git a/addons/mc_quests/lua/mqs/core/cl_panels.lua b/addons/mc_quests/lua/mqs/core/cl_panels.lua new file mode 100644 index 0000000..d04e63e --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/cl_panels.lua @@ -0,0 +1,1310 @@ +local Ln = MSD.GetPhrase + +MSD.ObjeciveList = {} + +MSD.ObjeciveList["Move to point"] = { + icon = Material("mqs/map_markers/m10.png", "smooth"), + tbl = { + point = Vector(0, 0, 0), + marker = 0, + }, + builUI = function(id, object, panel, popupm) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_description"), Ln("q_obj_des") .. ":", object.desc, function(self, value) + object.desc = value + end, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("def_units", "350"), Ln("q_dist_point"), object.dist, function(self, value) + object.dist = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.VectorSelectorList(panel, Ln("movepoint"), object.point, nil, nil, nil, true, function(vec) + object.point = vec + end) + + MSD.Button(panel, "static", nil, 1, 50, Ln("map_marker"), function() + local sub_list, _, plm = popupm(Ln("map_marker"), 2, 2.5, 50) + + if not object.marker then + object.marker = 0 + end + + for ic_id, ic in pairs(MSD.PinPoints) do + MSD.IconButton(sub_list, ic, "static", nil, 48, object.marker == ic_id and MSD.Config.MainColor.p or nil, nil, function() + object.marker = ic_id + plm.Close() + end) + end + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("q_ignore_veh"), object.ignore_veh or false, function(self, value) + object.ignore_veh = value + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("mark_area"), object.mark_area or false, function(self, value) + object.mark_area = value + end) + end +} + +MSD.ObjeciveList["Leave area"] = { + icon = Material("mqs/map_markers/m14.png", "smooth"), + tbl = { + point = Vector(0, 0, 0), + dist = 1000, + }, + builUI = function(id, object, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_description"), Ln("q_obj_des") .. ":", object.desc, function(self, value) + object.desc = value + end, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("def_units", "1000"), Ln("q_dist_from_point") .. ":", object.dist, function(self, value) + object.dist = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.VectorSelectorList(panel, Ln("leave_pnt"), object.point, nil, nil, nil, true, function(vec) + object.point = vec + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("q_ignore_veh"), object.ignore_veh or false, function(self, value) + object.ignore_veh = value + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("mark_area"), object.mark_area or false, function(self, value) + object.mark_area = value + end) + end +} + +MSD.ObjeciveList["Kill NPC"] = { + icon = Material("mqs/map_markers/c2.png", "smooth"), + tbl = { + open_target = false, + show_ents = false, + marker = 0, + dist = 300, + }, + builUI = function(id, object, panel, popupm) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_description"), Ln("q_obj_des") .. ":", object.desc, function(self, value) + object.desc = value + end, true) + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("q_open_target"), object.open_target or false, function(self, value) + object.open_target = value + end) + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("q_ent_pos_show"), object.show_ents or false, function(self, value) + object.show_ents = value + end) + MSD.TextEntry(panel, "static", nil, 2, 50, Ln("def_units", "300"), Ln("q_npc_mind") .. ":", object.dist, function(self, value) + object.dist = tonumber(value) or nil + end, true, nil, nil, true) + MSD.Button(panel, "static", nil, 2, 50, Ln("map_marker"), function() + local sub_list, _, plm = popupm(Ln("map_marker"), 2, 2.5, 50) + + if not object.marker then + object.marker = 0 + end + + for ic_id, ic in pairs(MSD.PinPoints) do + MSD.IconButton(sub_list, ic, "static", nil, 48, object.marker == ic_id and MSD.Config.MainColor.p or nil, nil, function() + object.marker = ic_id + plm.Close() + end) + end + end) + end +} + +MSD.ObjeciveList["Kill random target"] = { + icon = Material("mqs/map_markers/c3.png", "smooth"), + tbl = { + target_type = 1, + target_class = "npc_zombie", + target_count = 1, + }, + builUI = function(id, object, panel, popupm) + local update + function update() + panel:Clear() + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_description"), Ln("q_obj_des") .. ":", object.desc, function(self, value) + object.desc = value + end, true) + + MSD.Button(panel, "static", nil, 1, 50, Ln("target") .. ": " .. (object.target_type == 1 and Ln("Kill NPC") or Ln("kill_player")), function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + self.Menu = MSD.MenuOpen(false, self) + self.Menu:AddOption(Ln("Kill NPC"), function() + self:SetText(Ln("target") .. ": " .. Ln("Kill NPC")) + object.target_type = 1 + object.target_class = "npc_zombie" + update() + end) + self.Menu:AddOption(Ln("kill_player"), function() + self:SetText(Ln("target") .. ": " .. Ln("kill_player")) + object.target_type = 2 + object.target_class = nil + update() + end) + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + + if object.target_type == 1 then + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_class"), Ln("e_npc_class") .. ":", object.target_class, function(self, value) + object.target_class = value + end, true) + else + local ecls = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("e_blank_dis"), Ln("s_team_whitelist") .. ":", object.target_class, function(self, value) + object.target_class = value + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("search"), function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + self.Menu = MSD.MenuOpen(false, self) + self.Menu:AddOption(Ln("none"), function() + object.target_class = nil + ecls:SetText("") + end) + + for tid, tm in SortedPairsByMemberValue(team.GetAllTeams(), "Name", true) do + if not tm.Joinable then continue end + self.Menu:AddOption(tm.Name, function() + object.target_class = tm.Name + ecls:SetText(tm.Name) + end) + end + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + end + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_number"), Ln("kill_amount") .. ":", object.target_count, function(self, value) + object.target_count = tonumber(value) or 1 + + if object.target_count < 1 then + object.target_count = 1 + end + end, true, nil, nil, true) + end + update() + end +} + +MSD.ObjeciveList["Collect quest ents"] = { + icon = Material("mqs/map_markers/c4.png", "smooth"), + tbl = { + show_ents = true, + point = Vector(0, 0, 0), + dist = 500, + marker = 0, + }, + builUI = function(id, object, panel, popupm) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_description"), Ln("q_obj_des") .. ":", object.desc, function(self, value) + object.desc = value + end, true) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("q_ent_pos_show"), object.show_ents, function(self, value) + object.show_ents = value + end) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("def_units", "500"), Ln("q_s_area_size") .. ":", object.dist, function(self, value) + object.dist = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.VectorSelectorList(panel, Ln("q_s_area_pos"), object.point, nil, nil, nil, true, function(vec) + object.point = vec + end) + + MSD.Button(panel, "static", nil, 1, 50, Ln("map_marker"), function() + local sub_list, _, plm = popupm(Ln("map_marker"), 2, 2.5, 50) + + if not object.marker then + object.marker = 0 + end + + for ic_id, ic in pairs(MSD.PinPoints) do + MSD.IconButton(sub_list, ic, "static", nil, 48, object.marker == ic_id and MSD.Config.MainColor.p or nil, nil, function() + object.marker = ic_id + plm.Close() + end) + end + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("mark_area"), object.mark_area or false, function(self, value) + object.mark_area = value + end) + end +} + +MSD.ObjeciveList["Wait time"] = { + icon = Material("mqs/map_markers/c5.png", "smooth"), + tbl = { + time = 10, + show_timer = true, + stay_inarea = false, + point = Vector(0, 0, 0), + }, + builUI = function(id, object, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_description"), Ln("q_obj_des") .. ":", object.desc, function(self, value) + object.desc = value + end, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("def_seconds", "10"), Ln("time_wait") .. ":", object.time, function(self, value) + object.time = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("q_timer_show"), object.show_timer, function(self, var) + object.show_timer = var + end) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_blank_dis"), Ln("q_area_stay"), object.stay_inarea, function(self, value) + object.stay_inarea = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.VectorSelectorList(panel, Ln("q_area_pos"), object.point, nil, nil, nil, true, function(vec) + object.point = vec + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("q_ignore_veh"), object.ignore_veh or false, function(self, value) + object.ignore_veh = value + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("mark_area"), object.mark_area or false, function(self, value) + object.mark_area = value + end) + end +} + +MSD.ObjeciveList["Talk to NPC"] = { + icon = Material("msd/icons/account-multiple.png", "smooth"), + check = function() return MCS and true or false end, + tbl = { + npc = "", + dialog = 1, + marker = 0, + }, + builUI = function(id, object, panel, popupm) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_description"), Ln("q_obj_des") .. ":", object.desc, function(self, value) + object.desc = value + end, true) + + local bnt = MSD.Button(panel, "static", nil, 1, 50, Ln("npc_select"), function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + local sn_tbl = {} + + for _, ent in ipairs(ents.FindByClass("mcs_npc")) do + local uid = ent:GetUID() + sn_tbl[uid] = true + + self.Menu:AddOption("[" .. uid .. "] " .. ent:GetNamer(), function() + self:SetText("[" .. uid .. "] " .. ent:GetNamer()) + object.npc = uid + end) + end + + for k, c in pairs(MCS.Spawns) do + if sn_tbl[k] then continue end + + self.Menu:AddOption(k .. " (" .. Ln("not_spawned") .. ") ", function() + self:SetText("[" .. k .. "]") + object.npc = k + sn_tbl[k] = true + end) + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + + if object.npc ~= "" then + bnt:SetText("[" .. object.npc .. "]") + end + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("enter_id"), Ln("npc_did_open"), object.dialog, function(self, value) + object.dialog = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.Button(panel, "static", nil, 1, 50, Ln("map_marker"), function() + local sub_list, _, plm = popupm(Ln("map_marker"), 2, 2.5, 50) + + if not object.marker then + object.marker = 0 + end + + for ic_id, ic in pairs(MSD.PinPoints) do + MSD.IconButton(sub_list, ic, "static", nil, 48, object.marker == ic_id and MSD.Config.MainColor.p or nil, nil, function() + object.marker = ic_id + plm.Close() + end) + end + end) + end +} + +MSD.ObjeciveList["Randomize"] = { + icon = MSD.Icons48.reload, + tbl = { + objects = {}, + }, + builUI = function(id, object, panel, pp, quest) + for oid, obj in pairs(quest.objects) do + if id == oid then object.objects[id] = nil continue end + MSD.BoolSlider(panel, "static", nil, 2, 50, "[" .. oid .. "] " .. obj.type, object.objects[oid], function(self, var) + object.objects[oid] = var + end) + end + end +} + +MSD.ObjeciveList["End of quest"] = { + icon = MSD.Icons48.reload_alert, + tbl = { + reward_multiply = 0, + } +} + +MSD.ObjeciveList["Skip to"] = { + icon = MSD.Icons48.skip_to, + tbl = { + oid = 1, + }, + builUI = function(id, object, panel, pp, quest) + local lobj = quest.objects[object.oid] and quest.objects[object.oid].type or "Invalid" + local combo = MSD.ComboBox(panel, "static", nil, 1, 50, Ln("q_needquest_menu") .. ":", "[" .. object.oid .. "] " .. lobj ) + + for oid, obj in pairs(quest.objects) do + if id == oid then object.oid = 1 continue end + combo:AddChoice("[" .. oid .. "] " .. obj.type, oid) + end + + combo.OnSelect = function(self, index, text, data) + if text == Ln("none") then + object.oid = 1 + return + end + object.oid = data + end + end +} + +MSD.EventList = {} + +MSD.EventList["Give weapon"] = { + icon = Material("mqs/icons/pistol.png", "smooth"), + data = "weapon_pistol", + ui_h = 4, + builUI = function(event, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_wep_class"), Ln("weapon_name") .. ":", event[2], function(self, value) + event[2] = value + end, true) + end +} + +MSD.EventList["Give ammo"] = { + icon = Material("mqs/icons/ammo.png", "smooth"), + data = { + [1] = "Pistol", + [2] = 10, + }, + ui_h = 1.1, + builUI = function(event, panel) + local btn = MSD.ButtonIcon(panel, "static", nil, 1, 50, Ln("select_ammo") .. ": " .. language.GetPhrase("#" .. event[2][1] .. "_ammo"), Material("mqs/icons/ammo.png", "smooth"), function() end) + btn.hovered = true + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_number"), Ln("amount_ammo") .. ":", event[2][2], function(self, value) + event[2][2] = tonumber(value) or 0 + end, true, nil, nil, true) + + for _, ammo in ipairs(game.GetAmmoTypes()) do + MSD.Button(panel, "static", nil, 3, 50, language.GetPhrase("#" .. ammo .. "_ammo"), function(self) + event[2][1] = ammo + btn:SetText(Ln("select_ammo") .. ": " .. language.GetPhrase("#" .. ammo .. "_ammo")) + end) + end + end +} + +MSD.EventList["Strip All Weapons"] = { + icon = Material("mqs/icons/pistol_remove.png", "smooth"), + data = false, + ui_h = 6, + builUI = function(event, panel) + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("restore_wep"), event[2], function(self, var) + event[2] = var + end) + end +} + +MSD.EventList["Restore All Weapons"] = { + icon = Material("msd/icons/account-convert.png", "smooth"), + data = nil, + builUI = false +} + +MSD.EventList["Strip Weapon"] = { + icon = Material("mqs/icons/pistol_remove.png", "smooth"), + data = "", + ui_h = 4, + builUI = function(event, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_wep_class"), Ln("weapon_name") .. ":", event[2], function(self, value) + event[2] = value + end, true) + end +} + +MSD.EventList["Spawn quest entity"] = { + icon = Material("mqs/icons/box_open_star.png", "smooth"), + check = "Collect quest ents", + data = { + [1] = "models/props_junk/cardboard_box001a.mdl", + [2] = Vector(0, 0, 0), + [3] = true, + [4] = false, + [5] = Angle(0, 0, 0) + }, + builUI = function(event, panel) + local data = event[2] + + local mdl = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("e_model"), Ln("model") .. ":", data[1], function(self, value) + data[1] = value + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetModel() + mdl:SetText(md) + data[1] = md + end) + + MSD.VectorSelectorList(panel, Ln("spawn_point"), data[2], true, data[5], Ln("spawn_ang"), true, function(vec, ang) + data[2] = vec + data[5] = ang + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("ent_show_pointer"), data[3], function(self, var) + data[3] = var + end) + + MSD.DTextSlider(panel, "static", nil, 1, 50, Ln("ent_stnd_style"), Ln("ent_arcade_style"), data[4], function(self, value) + data[4] = value + end) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_blank_dis"), Ln("hold_use", "E") .. "(" .. Ln("in_sec") .. "):", data[8] or "", function(self, value) + value = tonumber(value) + if value then value = math.Clamp(value, 1, 60) end + data[8] = value or nil + end, true, nil, nil, true) + + local matp = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("mat_default"), Ln("e_material") .. ":", data[6], function(self, value) + data[6] = value or nil + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetMaterial() + matp:SetText(md) + data[6] = md + end) + + MSD.ColorSelector(panel, "static", nil, 1, 50, Ln("custom_color"), data[7] or color_white, function(self, color) + data[7] = color + end, true) + end +} + +MSD.EventList["Spawn entity"] = { + icon = Material("mqs/icons/box_open.png", "smooth"), + data = { + [1] = "", + [2] = Vector(0, 0, 0), + [3] = Angle(0, 0, 0), + [4] = nil, + [5] = false + }, + builUI = function(event, panel) + local data = event[2] + + local ecls = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("e_ent_class"), Ln("e_class") .. ":", data[1], function(self, value) + data[1] = value + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetClass() + ecls:SetText(md) + data[1] = md + end) + + local mdl = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("e_blank_default"), Ln("e_model") .. ":", data[4], function(self, value) + if value == "" then + data[4] = nil + end + + data[4] = value + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetModel() + mdl:SetText(md) + data[4] = md + end) + + MSD.VectorSelectorList(panel, Ln("spawn_point"), data[2], true, data[3], Ln("spawn_ang"), true, function(vec, ang) + data[2] = vec + data[3] = ang + end) + + local matp = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("mat_default"), Ln("e_material") .. ":", data[6], function(self, value) + data[6] = value or nil + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetMaterial() + matp:SetText(md) + data[6] = md + end) + + MSD.ColorSelector(panel, "static", nil, 1, 50, Ln("custom_color"), data[7] or color_white, function(self, color) + data[7] = color + end, true) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("disable_phys"), data[5], function(self, var) + data[5] = var + end) + end +} + +MSD.EventList["Spawn npc"] = { + icon = Material("mqs/icons/user_plus.png", "smooth"), + data = { + [1] = "npc_citizen", + [2] = Vector(0, 0, 0), + [3] = Angle(0, 0, 0), + [4] = false, + [5] = nil, + [6] = nil, + [7] = true, + [8] = nil, + [9] = nil, + }, + builUI = function(event, panel, t_id, object) + local data = event[2] + + local ecls = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("e_npc_class"), Ln("e_class") .. ":", data[1], function(self, value) + data[1] = value + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetClass() + ecls:SetText(md) + data[1] = md + end) + + local mdl = MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("e_blank_default"), Ln("e_model") .. ":", data[5], function(self, value) + if value == "" then + data[5] = nil + end + + data[5] = value + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetModel() + mdl:SetText(md) + data[5] = md + end) + + MSD.VectorSelectorList(panel, Ln("spawn_point"), data[2], true, data[3], Ln("spawn_ang"), true, function(vec, ang) + data[2] = vec + data[3] = ang + end) + + if object and object.type == "Kill NPC" then + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("npc_q_target"), data[4], function(self, var) + data[4] = var + end) + end + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_blank_default"), Ln("e_wep_class") .. ":", data[6], function(self, value) + if value == "" then + data[6] = nil + end + + data[6] = value + end, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_blank_default"), Ln("Custom HP") .. ":", data[9], function(self, value) + data[9] = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("npc_hostile"), data[7], function(self, var) + data[7] = var + end) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("NextBot NPC"), data[8], function(self, var) + data[8] = var + end) + end +} + +MSD.EventList["Manage do time"] = { + icon = Material("mqs/map_markers/c5.png", "smooth"), + data = { + [1] = 1, + [2] = 0, + }, + ui_h = 4, + builUI = function(event, panel) + local data = event[2] + + if isbool(data[1]) then data[1] = 1 end + local datatbl = { + [1] = Ln("q_dotime_reset"), + [2] = Ln("q_dotime_add"), + [3] = Ln("q_dotime_set"), + } + + local combo = MSD.ComboBox(panel, "static", nil, 1, 50, "", datatbl[data[1]] ) + + for k, v in pairs(datatbl) do + combo:AddChoice(v, k) + end + + combo.OnSelect = function(self, index, text, d) + data[1] = d + end + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_number"), Ln("e_number") .. "(" .. Ln("in_sec") .. "):", data[2], function(self, value) + data[2] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MSD.EventList["Spawn vehicle"] = { + icon = Material("mqs/map_markers/v1.png", "smooth"), + data = { + [1] = "", + [2] = "default", + [3] = Vector(0, 0, 0), + [4] = Angle(0, 0, 0), + [5] = 0, + }, + builUI = function(event, panel) + local data = event[2] + + MSD.TextEntry(panel, "static", nil, 1.5, 50, Ln("e_veh_class"), Ln("e_class") .. ":", data[1], function(self, value) + data[1] = value + end, true) + + MSD.Button(panel, "static", nil, 3, 50, Ln("type") .. ": " .. data[2], function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + + self.Menu:AddOption("default", function() + data[2] = "default" + self:SetText(Ln("type") .. ": " .. data[2]) + end) + + self.Menu:AddOption("simfphys", function() + data[2] = "simfphys" + self:SetText(Ln("type") .. ": " .. data[2]) + end) + + self.Menu:AddOption("LFS / SW vehicles", function() + data[2] = "lfs" + self:SetText(Ln("type") .. ": " .. data[2]) + end) + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + + MSD.VectorSelectorList(panel, Ln("spawn_point"), data[3], true, data[4], Ln("spawn_ang"), true, function(vec, ang) + data[3] = vec + data[4] = ang + end) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_number"), Ln("Skin ID") .. ":", data[5] or 0, function(self, value) + data[5] = tonumber(value) or 0 + end, true, nil, nil, true) + + MSD.ColorSelector(panel, "static", nil, 1, 50, Ln("custom_color"), data[7] or color_white, function(self, color) + data[7] = color + end) + + end +} + +MSD.EventList["Remove vehicle"] = { + icon = Material("mqs/map_markers/v7.png", "smooth"), + data = nil, + ui_h = 5, + builUI = function(event, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_blank_dis"), Ln("delay") .. "(" .. Ln("in_sec") .. "):", event[2] or "", function(self, value) + event[2] = tonumber(value) or nil + end, true, nil, nil, true) + end +} + +MSD.EventList["Remove all entites"] = { + icon = MSD.Icons48.layers_remove, + data = nil, +} + +MSD.EventList["Set HP"] = { + icon = Material("mqs/icons/heart.png", "smooth"), + data = { + [1] = true, + [2] = 0, + }, + ui_h = 4, + builUI = function(event, panel) + local data = event[2] + + MSD.DTextSlider(panel, "static", nil, 1, 50, Ln("set_hp_full"), Ln("custom_val"), data[1], function(self, value) + data[1] = value + end) + + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_value"), data[2], function(self, value) + data[2] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MSD.EventList["Set Armor"] = { + icon = Material("mqs/map_markers/a1.png", "smooth"), + data = { + [1] = 0, + }, + ui_h = 6, + builUI = function(event, panel) + local data = event[2] + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_value"), data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MSD.EventList["Teleport player"] = { + icon = Material("mqs/map_markers/m17.png", "smooth"), + data = { + [1] = Vector(0,0,0), + [2] = Angle(0,0,0), + }, + builUI = function(event, panel) + local data = event[2] + MSD.VectorSelectorList(panel, Ln("spawn_point"), data[2], true, data[3], Ln("spawn_ang"), true, function(vec, ang) + data[1] = vec + data[2] = ang + end) + end +} + +MSD.EventList["Set spawn point"] = { + icon = Material("mqs/map_markers/m19.png", "smooth"), + data = { + [1] = Vector(0,0,0), + [2] = Angle(0,0,0), + }, + ui_h = 3, + builUI = function(event, panel) + local data = event[2] + local update + function update() + panel:Clear() + MSD.DTextSlider(panel, "static", nil, 1, 50, Ln("add_new_spawn"), Ln("remove_all_spawn"), data[1] and true or false, function(self, value) + if value then + data[1] = Vector(0,0,0) + else + data[1] = nil + end + update() + end) + + if data[1] then + MSD.VectorSelectorList(panel, Ln("spawn_point"), data[2], true, data[3], Ln("spawn_ang"), true, function(vec, ang) + data[1] = vec + data[2] = ang + end) + end + end + update() + end +} + +MSD.EventList["Cinematic camera"] = { + icon = Material("mqs/map_markers/s1.png", "smooth"), + data = { + delay = nil, + endtime = 7, + text = "", + cam_start = { + pos = Vector(0, 0, 0), + ang = Angle(0, 0, 0), + fov = 90, + }, + cam_end = { + pos = Vector(0, 0, 0), + ang = Angle(0, 0, 0), + fov = 90, + }, + cam_speed = 5, + fov_speed = 5, + effect = true, + }, + builUI = function(event, panel) + local data = event[2] + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_blank_dis"), Ln("delay"), data.delay, function(self, value) + data.delay = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_text"), Ln("dis_text") .. ":", data.text, function(self, value) + data.text = value + end, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), Ln("duration") .. "(" .. Ln("in_sec") .. "):", data.endtime, function(self, value) + data.endtime = math.max(tonumber(value) or 3, 3) + end, true, nil, nil, true) + + MSD.BoolSlider(panel, "static", nil, 1, 50, Ln("cam_effect"), data.effect or false, function(self, value) + data.effect = value + end) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), Ln("cam_speed") .. "(" .. Ln("in_sec") .. "):", data.cam_speed, function(self, value) + data.cam_speed = tonumber(value) or 5 + end, true, nil, nil, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), Ln("fov_speed") .. "(" .. Ln("in_sec") .. "):", data.fov_speed, function(self, value) + data.fov_speed = tonumber(value) or 5 + end, true, nil, nil, true) + + MSD.Header(panel, Ln("cam_start")) + local vec1 = MSD.VectorDisplay(panel, "static", nil, 2, 50, Ln("cam_pos"), data.cam_start.pos, function(vec) + data.cam_start.pos = vec + end) + local amg1 = MSD.AngleDisplay(panel, "static", nil, 2, 50, Ln("cam_ang"), data.cam_start.ang, function(ang) + data.cam_start.ang = ang + end) + + MSD.TextEntry(panel, "static", nil, 2, 50, Ln("e_value"), Ln("cam_fov") .. ":", data.cam_start.fov, function(self, value) + data.cam_start.fov = tonumber(value) or 90 + end, true, nil, nil, true) + + MSD.Button(panel, "static", nil, 2, 50, Ln("set_pos_self"), function() + local vec = LocalPlayer():EyePos() + vec1.vector = vec + data.cam_start.pos = vec + local ang = Angle(LocalPlayer():GetAngles().p, LocalPlayer():GetAngles().y, 0) + amg1.angle = ang + data.cam_start.ang = ang + end) + + MSD.Header(panel, Ln("cam_end")) + local vec2 = MSD.VectorDisplay(panel, "static", nil, 2, 50, Ln("cam_pos"), data.cam_end.pos, function(vec) + data.cam_end.pos = vec + end) + local amg2 = MSD.AngleDisplay(panel, "static", nil, 2, 50, Ln("cam_ang"), data.cam_end.ang, function(ang) + data.cam_end.ang = ang + end) + + MSD.TextEntry(panel, "static", nil, 2, 50, Ln("e_value"), Ln("cam_fov") .. ":", data.cam_end.fov, function(self, value) + data.cam_end.fov = tonumber(value) or 90 + end, true, nil, nil, true) + + MSD.Button(panel, "static", nil, 2, 50, Ln("set_pos_self"), function() + local vec = LocalPlayer():EyePos() + vec2.vector = vec + data.cam_end.pos = vec + local ang = Angle(LocalPlayer():GetAngles().p, LocalPlayer():GetAngles().y, 0) + amg2.angle = ang + data.cam_end.ang = ang + end) + end +} + +MSD.EventList["[MCS] Spawn npc"] = { + icon = Material("msd/icons/account.png", "smooth"), + check = function() return not MCS and true or false end, + ui_h = 3, + data = { + [1] = "", + [2] = Vector(0, 0, 0), + [3] = Angle(0, 0, 0), + }, + builUI = function(event, panel, t_id, object) + local data = event[2] + + local bnt = MSD.Button(panel, "static", nil, 1, 50, Ln("npc_select"), function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + + for k, v in pairs(MCS.Spawns) do + self.Menu:AddOption(k, function() + self:SetText(k) + data[1] = k + end) + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + + if data[1] ~= "" then + bnt:SetText(data[1]) + end + + MSD.VectorSelectorList(panel, Ln("spawn_point"), data[2], true, data[3], Ln("spawn_ang"), true, function(vec, ang) + data[2] = vec + data[3] = ang + end) + end +} + +MSD.EventList["Music player"] = { + icon = Material("mqs/icons/music_circle.png", "smooth"), + data = { + path = "", + }, + ui_h = 4, + builUI = function(event, panel) + local data = event[2] + MSD.TextEntry(panel, "static", nil, 1, 50, "", "Music: (path or URL)", data.path, function(self, value) + data.path = value + end, true) + end +} + +MSD.EventList["Run Console Command"] = { + icon = Material("msd/icons/console.png", "smooth"), + data = { + [1] = "", + [2] = "", + }, + ui_h = 3, + builUI = function(event, panel) + local data = event[2] + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_cmd"), data[1], function(self, value) + data[1] = value + end, true) + MSD.InfoText(panel, Ln("hint_cmd")) + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_args"), data[2], function(self, value) + data[2] = value + end, true) + end +} + +MSD.EventList["Track player"] = { + icon = Material("mqs/map_markers/m5.png", "smooth"), + data = { + [1] = "", + [2] = 0, + [3] = {} + }, + builUI = function(event, panel) + local data = event[2] + local update + -- MSD.Button(panel, "static", nil, 1, 50, Ln("map_marker"), function() + -- local sub_list, _, _ = popupm(Ln("map_marker"), 2, 2.5, 50) + -- for tid, tm in SortedPairsByMemberValue(team.GetAllTeams(), "Name", true) do + -- if not tm.Joinable then continue end + + -- MSD.TextEntry(sub_list, "static", nil, 2, 50, "", tm.Name, data[3][tm.Name], function(self, val) + -- data[3][tm.Name] = val ~= "" and val or nil + -- end, true, nil, nil, true) + -- end + -- end) + function update() + panel:Clear() + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("dis_text"), data[1], function(self, value) + data[1] = value + end, true) + + MSD.InfoHeader(panel, Ln("map_marker")) + if not data[2] then data[2] = 0 end + for ic_id, ic in pairs(MSD.PinPoints) do + MSD.IconButton(panel, ic, "static", nil, 32, data[2] == ic_id and MSD.Config.MainColor.p or nil, nil, function() + data[2] = ic_id + update() + end) + end + + MSD.InfoHeader(panel, Ln("s_team_whitelist")) + for tid, tm in SortedPairsByMemberValue(team.GetAllTeams(), "Name", true) do + if not tm.Joinable then continue end + + MSD.BoolSlider(panel, "static", nil, 2, 50, tm.Name, data[3][tm.Name], function(self, var) + data[3][tm.Name] = var + end) + end + end + update() + end +} + +MSD.EventList["End track player"] = { + icon = Material("mqs/map_markers/m9.png", "smooth"), + data = nil, +} + +MQS.RewardsList = {} + +MQS.RewardsList["Give Weapon"] = { + data = { + [1] = "", + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), Ln("e_wep_class") .. ":", data[1], function(self, value) + data[1] = value + end, true) + end +} + +MQS.RewardsList["DarkRP money"] = { + data = { + [1] = 1000, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), Ln("q_money_give") .. ":", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["PointShop2 Standard Points"] = { + data = { + [1] = 100, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_number") .. ":", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["PointShop2 Premium Points"] = { + data = { + [1] = 10, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_number") .. ":", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["PointShop2 Give Item"] = { + data = { + [1] = "", + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_ent_class"), Ln("e_class") .. ":", data[1], function(self, value) + data[1] = value + end, true) + end +} + +MQS.RewardsList["PointShop Points"] = { + data = { + [1] = 100, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_number") .. ":", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["PointShop Give Item"] = { + data = { + [1] = "", + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_ent_class"), Ln("e_class") .. ":", data[1], function(self, value) + data[1] = value + end, true) + end +} + +MQS.RewardsList["DarkRP Leveling System"] = { + data = { + [1] = 1000, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), "Give XP:", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["Run Console Command"] = { + data = { + [1] = "", + [2] = "", + }, + builUI = function(data, panel) + MSD.InfoText(panel, Ln("hint_cmd")) + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_cmd"), data[1], function(self, value) + data[1] = value + end, true) + MSD.TextEntry(panel, "static", nil, 1, 50, "", Ln("e_args"), data[2], function(self, value) + data[2] = value + end, true) + end +} + +MQS.RewardsList["Helix money"] = { + data = { + [1] = 1000, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), Ln("q_money_give") .. ":", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["Glorified Leveling"] = { + data = { + [1] = 1000, + [2] = nil, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), "Give XP:", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_blank_dis"), "Give Level:", data[2], function(self, value) + data[2] = tonumber(value) or nil + end, true, nil, nil, true) + end +} + +MQS.RewardsList["Wiltos skill XP"] = { + data = { + [1] = 100, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), "Give XP:", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["Remove quest played data"] = { + data = { + [1] = "", + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, "qid1,qid2", "Enter quest id list use ',' for separation", data[1], function(self, value) + data[1] = value + end, true) + end +} + +MQS.RewardsList["Elite XP System"] = { + data = { + [1] = 1000, + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), "Give XP:", data[1], function(self, value) + data[1] = tonumber(value) or 0 + end, true, nil, nil, true) + end +} + +MQS.RewardsList["MRS"] = { + data = { + [1] = "", + [2] = 1, + [3] = 1, + }, + builUI = function(data, panel) + local selected_g = MRS.Ranks[data[1]] + local groupc = MSD.ComboBox(panel, "static", nil, 1, 50, Ln("group_list") .. ":", selected_g and data[1] or Ln("none") ) + local rankc = MSD.ComboBox(panel, "static", nil, 1, 50, Ln("rank_list") .. ":", (selected_g and selected_g.ranks[data[2]].name) or Ln("none") ) + + rankc.OnSelect = function(self, index, text, sel) + data[2] = sel + end + + for gname, _ in pairs(MRS.Ranks) do + groupc:AddChoice(gname) + end + + groupc.OnSelect = function(self, index, text) + if text == Ln("none") then + data[1] = "" + return + end + data[1] = text + data[2] = 1 + rankc:Clear() + rankc:SetText(MRS.Ranks[text].ranks[1].name or Ln("none")) + + for id, r in pairs(MRS.Ranks[text].ranks) do + rankc:AddChoice(r.name, id) + end + end + + local cvals = { + Ln("action_set_rank"), + Ln("action_set_rank_force"), + Ln("action_promote_rank"), + Ln("action_demote_rank"), + } + + local combo = MSD.ComboBox(panel, "static", nil, 1, 50, Ln("action_select") .. ":", cvals[data[3]] ) + combo.OnSelect = function(self, index, text, sel) + data[3] = sel + end + for id, option in pairs(cvals) do + combo:AddChoice(option, id) + end + end +} + +MQS.RewardsList["WCD Give car"] = { + data = { + [1] = "", + }, + builUI = function(data, panel) + MSD.TextEntry(panel, "static", nil, 1, 50, Ln("e_value"), "CAR ID:", data[1], function(self, value) + data[1] = value + end, true) + end +} \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/core/cl_util.lua b/addons/mc_quests/lua/mqs/core/cl_util.lua new file mode 100644 index 0000000..06f3ff3 --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/cl_util.lua @@ -0,0 +1,143 @@ +function MQS.PreCheckQuest(quest, PopupMenu) + local errorlist + local alertlist + MsgC(Color(0, 0, 255), "Quest check start\n") + + local function AddError(error) + if not errorlist then + errorlist = {} + end + + table.insert(errorlist, error) + end + + local function AddAllert(alert) + if not alertlist then + alertlist = {} + end + + table.insert(alertlist, alert) + end + + if quest.desc == "" or quest.desc == " " then + AddAllert("You miss quest description") + end + + if quest.success == "" or quest.success == " " then + AddAllert("You miss quest complete message") + end + + if #quest.objects < 2 then + AddAllert("Ain't it too short of a quest?") + end + + -- Check for valid objectives order + local pre_object + local car_valid = false + + for id, object in pairs(quest.objects) do + if object.events then + for id_e, event in pairs(object.events) do + if event[1] == "Spawn vehicle" then + car_valid = true + end + end + end + + if object.events then + for id_e, event in pairs(object.events) do + if event[1] == "Remove vehicle" then + if not car_valid then + AddError("There is no vehicle to remove by 'Remove vehicle' event. Objective id: " .. id) + continue + end + + car_valid = false + end + end + end + + if object.type == "Collect quest ents" then + if pre_object and pre_object == object.type then + AddError("You have 'Collect quest ents' objectives starting one after another, this may break your quest. Objective id: " .. id) + end + + local found = false + + if object.events then + for id_e, event in pairs(object.events) do + if event[1] == "Spawn quest entity" then + found = true + break + end + end + end + + if not found then + AddError("'Collect quest ents' objectives has no 'Spawn quest entity' event, this will break your quest. Objective id: " .. id) + end + end + + if object.type == "Kill NPC" then + local found = false + + if object.events then + for id_e, event in pairs(object.events) do + if event[1] == "Spawn npc" and event[2][4] then + found = true + break + end + end + end + + if not found then + AddError("'Kill NPC' has no target NPC. You need to create a target NPC. Objective id: " .. id) + end + end + + pre_object = object.type + end + + if errorlist then + MsgC(Color(255, 0, 0), "Errors:\n") + PrintTable(errorlist) + end + + if alertlist then + MsgC(Color(255, 187, 0), "Alerts:\n") + PrintTable(alertlist) + end + + if not errorlist and not alertlist then + MsgC(Color(0, 255, 0), "No error found!\n") + end + + if PopupMenu then + local sub_list = PopupMenu("Quest troubleshoot", 1.2, 1.5, 50) + + if errorlist then + for k, v in ipairs(errorlist) do + MSD.ButtonIcon(sub_list, "static", nil, 1, 50, v, MSD.Icons48.alert, nil, nil, Color(255, 0, 0)) + end + end + + if alertlist then + for k, v in ipairs(alertlist) do + MSD.ButtonIcon(sub_list, "static", nil, 1, 50, v, MSD.Icons48.alert, nil, nil, Color(255, 187, 0)) + end + end + + if not errorlist and not alertlist then + MSD.ButtonIcon(sub_list, "static", nil, 1, 50, "No error found!", MSD.Icons48.submit, nil, nil, Color(0, 255, 0)) + end + end +end + +function MQS.QuestSubmit(quest) + local cd, bn = MQS.TableCompress(quest) + + net.Start("MQS.QuestSubmit") + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() +end \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/core/sh_init.lua b/addons/mc_quests/lua/mqs/core/sh_init.lua new file mode 100644 index 0000000..ea82fc1 --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/sh_init.lua @@ -0,0 +1,617 @@ +--──────────────────────────────────-- +-------------- Events --------------- +--──────────────────────────────────-- + +MQS.Events["Spawn quest entity"] = function(id, ply, data, obj, task) + local ent = ents.Create("mqs_ent") + ent.model = data[1] + ent.task = task + ent.task_id = id + ent.task_ply = ply + ent.pointer = data[3] + ent.enablephys = data[4] + local pos = data[2] + + if istable(pos) then + pos = table.Random(pos) + end + + if data[5] then + ent:SetAngles(data[5]) + end + + if data[6] then + ent:SetMaterial(data[6]) + end + + if data[7] then + if data[7].a and data[7].a < 255 then + ent:SetRenderMode(RENDERMODE_TRANSCOLOR) + end + ent:SetColor(data[7]) + end + + if data[8] then + ent:SetUseHold(data[8]) + end + ent.IsMQS = true + ent:SetPos(pos) + ent:Spawn() + + if not MQS.ActiveTask[id].ents then + MQS.ActiveTask[id].ents = {} + end + + table.insert(MQS.ActiveTask[id].ents, ent:EntIndex()) +end + +MQS.Events["Spawn vehicle"] = function(task, ply, data) + if MQS.ActiveTask[task].vehicle then return end + local vehicle = MQS.SpawnQuestVehicle(ply, data[1], data[2], data[3], data[4]) + if not vehicle then return end + + if data[5] then + vehicle:SetSkin(data[5]) + end + + if data[7] then + vehicle:SetColor(data[7]) + end + vehicle.isMQS = task + vehicle.IsMQS = true + vehicle = vehicle:EntIndex() + + if not MQS.ActiveTask[task].vehicle then + MQS.ActiveTask[task].vehicle = vehicle + end +end + +MQS.Events["Remove vehicle"] = function(task, ply, data) + if not MQS.ActiveTask[task].vehicle then return end + local veh = Entity(MQS.ActiveTask[task].vehicle) + MQS.ActiveTask[task].vehicle = nil + + if data and data > 0 then + timer.Simple(data, function() + if IsValid(veh) and veh.IsMQS then + SafeRemoveEntity(veh) + end + end) + else + if IsValid(veh) and veh.IsMQS then + SafeRemoveEntity(veh) + end + end +end + +MQS.Events["Spawn entity"] = function(id, ply, data, obj, task) + if data[1] == "worldspawn" then return end + local ent = ents.Create(data[1]) + + if not ent:IsValid() then + MsgC(Color(255, 0, 0), "[MQS] Quest id: " .. task .. " failed to create " .. data[1] .. "!\n") + + return + end + + ent:SetPos(data[2]) + ent:SetAngles(data[3]) + + if data[4] then + ent:SetModel(data[4]) + end + + if data[6] then + ent:SetMaterial(data[6]) + end + + if data[7] then + if data[7].a and data[7].a < 255 then + ent:SetRenderMode(RENDERMODE_TRANSCOLOR) + end + ent:SetColor(data[7]) + end + + ent.IsMQS = true + ent:Spawn() + + if data[5] then + local phys = ent:GetPhysicsObject() + + if phys:IsValid() then + phys:EnableMotion(false) + end + end + + table.insert(MQS.ActiveTask[id].misc_ents, ent:EntIndex()) +end + +MQS.Events["Remove all entites"] = function(task, ply, data) + if MQS.ActiveTask[task].misc_ents then + for k, v in pairs(MQS.ActiveTask[task].misc_ents) do + local ent = ents.GetByIndex(v) + if IsValid(ent) and ent.IsMQS then + SafeRemoveEntity(ent) + end + end + end + MQS.ActiveTask[task].misc_ents = {} +end + +local function IsPointInSphere(center, radius, point) + local r2 = radius * radius + return center:DistToSqr(point) <= r2 +end + +local z_rewrite = { + { + center = Vector(174.63, 1390.05, 72.03), + radius = 400, + z = 73 + }, + { + center = Vector(-486.05, -306.68, 379.56), + radius = 600, + z = 368 + }, +} + +MQS.Events["Spawn npc"] = function(id, ply, data, obj, task) + local ent = ents.Create(data[1]) + + if not ent:IsValid() then + MsgC(Color(255, 0, 0), "[MQS] Quest id: " .. task .. " failed to create " .. data[1] .. "!\n") + return + end + + data[2].z = data[2].z + 30 + for z, v in ipairs(z_rewrite) do + if IsPointInSphere(v.center, v.radius, data[2]) then + data[2].z = v.z + end + end + ent:SetPos(data[2]) + ent:SetAngles(data[3]) + + if data[4] then + ent.is_quest_npc = task + ent:SetNWBool("MQSTarget", true) + MQS.ActiveTask[id].npcs = MQS.ActiveTask[id].npcs and MQS.ActiveTask[id].npcs + 1 or 1 + end + ent.IsMQS = true + ent.quest_id = id + ent:Spawn() + ent:Activate() + + if data[5] then + ent:SetModel(data[5]) + end + + if data[6] then + ent:Give(data[6]) + end + + if data[9] then + ent:SetHealth(data[9]) + end + + if not data[8] then + local gr = "D_HT" + if data[7] then + ent:AddEntityRelationship(ply, 1, 99) + ent:SetNPCState(NPC_STATE_COMBAT) + else + ent:AddEntityRelationship(ply, 4, 99) + gr = "D_LI" + end + if obj.open_target then + ent:AddRelationship("player " .. gr .. " 99") + ent.open_target = true + else + ent:AddRelationship("player D_NU 99") + end + ent:SetKeyValue("spawnflags", bit.bor(SF_NPC_NO_WEAPON_DROP)) + end + table.insert(MQS.ActiveTask[id].misc_ents, ent:EntIndex()) +end + +MQS.Events["Give weapon"] = function(task, ply, data) + local weapon = ply:Give(data) + if IsValid(weapon) and weapon ~= NULL then + weapon.MQS_weapon = true + end +end + +MQS.Events["Give ammo"] = function(task, ply, data) + ply:GiveAmmo(data[2], data[1], false) +end + +MQS.Events["Strip Weapon"] = function(task, ply, data) + ply:StripWeapon(data) +end + +MQS.Events["Strip All Weapons"] = function(task, ply, data) + ply.MQS_oldWeap = ply.MQS_oldWeap or {} + + if data then + ply.MQS_restore = true + end + + for _, wep in pairs(ply:GetWeapons()) do + if wep.MQS_weapon then continue end + ply.MQS_oldWeap[wep:GetClass()] = true + end + + ply:StripWeapons() +end + +MQS.Events["Restore All Weapons"] = function(task, ply) + for wep, _ in pairs(ply.MQS_oldWeap) do + ply:Give(wep) + end +end + +MQS.Events["Manage do time"] = function(task, ply, data) + if not MQS.GetNWdata(ply, "do_time") then return end + if isbool(data[1]) then data[1] = 1 end + + if data[1] == 1 then + local q = MQS.ActiveTask[task].task + MQS.SetNWdata(ply, "do_time", CurTime() + MQS.Quests[q].do_time) + elseif data[1] == 2 then + MQS.SetNWdata(ply, "do_time", MQS.GetNWdata(ply, "do_time") + data[2]) + else + MQS.SetNWdata(ply, "do_time", CurTime() + data[2]) + end +end + +MQS.Events["Set HP"] = function(task, ply, data) + if data[1] then + ply:SetHealth(ply:GetMaxHealth()) + else + ply:SetHealth(data[2]) + end +end + +MQS.Events["Set Armor"] = function(task, ply, data) + ply:SetArmor(math.Clamp(data[1], 0, 255)) +end + +MQS.Events["Teleport player"] = function(task, ply, data) + ply:SetPos(data[1]) + ply:SetEyeAngles(data[2]) + ply:SetLocalVelocity(Vector(0, 0, 0)) +end + +MQS.Events["Set spawn point"] = function(task, ply, data) + if data[1] then + ply.EventData.SpawnPoint = data + else + ply.EventData.SpawnPoint = nil + end +end + +MQS.Events["Cinematic camera"] = function(task, ply, data) + net.Start("MQS.UIEffect") + net.WriteString("Cinematic camera") + net.WriteTable(data) + net.Send(ply) +end + +MQS.Events["Music player"] = function(task, ply, data) + net.Start("MQS.UIEffect") + net.WriteString("Music") + net.WriteTable(data) + net.Send(ply) +end + +MQS.Events["[MCS] Spawn npc"] = function(task, ply, data) + local npc = MCS.Spawns[data[1]] + if not npc then return end + local ent = ents.Create("mcs_npc") + ent:SetModel(npc.model) + ent:SetPos(data[2]) + ent:SetAngles(data[3]) + ent:SetNamer(npc.name) + ent:SetUID(data[1]) + ent:SetInputLimit(true) + ent:SetUseType(SIMPLE_USE) + ent:SetSolid(SOLID_BBOX) + ent:PhysicsInit(SOLID_BBOX) + ent:SetMoveType(MOVETYPE_NONE) + + if npc.sequence then + local sequence = npc.sequence + + if istable(sequence) then + sequence = table.Random(sequence) + end + + ent.AutomaticFrameAdvance = true + ent:ResetSequence(sequence) + ent:SetDefAnimation(sequence) + end + + if npc.bgr then + for k, v in ipairs(npc.bgr) do + ent:SetBodygroup(k, v) + end + end + + if npc.skin then + ent:SetSkin(npc.skin) + end + ent.IsMQS = true + ent:SetCollisionGroup(COLLISION_GROUP_PLAYER) + ent:Spawn() + table.insert(MQS.ActiveTask[task].misc_ents, ent:EntIndex()) +end + +MQS.Events["Run Console Command"] = function(task, ply, data) + local cmd = data[1] + local args = data[2] + local cmd_arg = "" + args = string.Explode(" ", args) + + for _, arg in pairs(args) do + if arg == "$uid" then + arg = ply:UserID() + end + + if arg == "$sid" then + arg = ply:SteamID() + end + + if arg == "$s64" then + arg = ply:SteamID64() + end + + if arg == "$n" then + arg = "\"" .. ply:Name() .. "\"" + end + + cmd_arg = cmd_arg .. " " .. arg + end + game.ConsoleCommand(cmd .. cmd_arg .. "\n") +end + +MQS.Events["Track player"] = function(task, ply, data) + local ntable = { + id = task, + uid = ply:UserID(), + text = data[1], + icon = data[2], + teams = data[3] + } + + local filter = RecipientFilter() + + for _, p in pairs(player.GetAll()) do + if p == ply then continue end + if data[3][team.GetName(p:Team())] then + filter:AddPlayer(p) + end + end + + net.Start("MQS.UIEffect") + net.WriteString("Track") + net.WriteTable(ntable) + net.Send(filter) + + MQS.Notify(ply, MSD.GetPhrase("warning"), MSD.GetPhrase("youaretracked"), 4) +end + +MQS.Events["End track player"] = function(task, ply, data) + net.Start("MQS.UIEffect") + net.WriteString("UnTrack") + net.WriteTable({ id = task }) + net.Broadcast() + + MQS.Notify(ply, MSD.GetPhrase("m_loop"), MSD.GetPhrase("nolongertracked"), 4) +end + +--──────────────────────────────────-- +-------------- Rewards --------------- +--──────────────────────────────────-- + +MQS.Rewards["Give Weapon"] = { + reward = function(ply, val) + ply:Give(val[1]) + end, +} + +MQS.Rewards["DarkRP money"] = { + check = function() + if DarkRP then return false end + + return true + end, + reward = function(ply, val) + ply:addMoney(val[1]) + end, +} + +MQS.Rewards["PointShop2 Standard Points"] = { + check = function() + if Pointshop2 then return false end + + return true + end, + reward = function(ply, val) + ply:PS2_AddStandardPoints(val[1]) + end, +} + +MQS.Rewards["PointShop2 Premium Points"] = { + check = function() + if Pointshop2 then return false end + + return true + end, + reward = function(ply, val) + ply:PS2_AddPremiumPoints(val[1]) + end, +} + +MQS.Rewards["PointShop2 Give Item"] = { + check = function() + if Pointshop2 then return false end + + return true + end, + reward = function(ply, val) + ply:PS2_EasyAddItem(val[1]) + end, +} + +MQS.Rewards["PointShop Points"] = { + check = function() + if PS then return false end + + return true + end, + reward = function(ply, val) + ply:PS_GivePoints(val[1]) + end, +} + +MQS.Rewards["PointShop Give Item"] = { + check = function() + if PS then return false end + + return true + end, + reward = function(ply, val) + ply:PS_GiveItem(val[1]) + end, +} + +MQS.Rewards["DarkRP Leveling System"] = { + check = function() + if LevelSystemConfiguration then return false end + + return true + end, + reward = function(ply, val) + ply:addXP(val[1]) + end, +} + +MQS.Rewards["Glorified Leveling"] = { + check = function() + if GlorifiedLeveling then return false end + + return true + end, + reward = function(ply, val) + GlorifiedLeveling.AddPlayerXP(ply, val[1]) + if val[2] then + GlorifiedLeveling.AddPlayerLevels(ply, val[2]) + end + end, +} + +MQS.Rewards["Helix money"] = { + check = function() + if ix then return false end + + return true + end, + reward = function(ply, val) + ply:GetCharacter():GiveMoney(val[1]) + end, +} + +MQS.Rewards["Run Console Command"] = { + reward = function(ply, val) + local cmd = val[1] + local args = val[2] + local cmd_arg = "" + args = string.Explode(" ", args) + + for _, arg in pairs(args) do + if arg == "$uid" then + arg = ply:UserID() + end + + if arg == "$sid" then + arg = "\"" .. ply:SteamID() .. "\"" + end + + if arg == "$s64" then + arg = ply:SteamID64() + end + + if arg == "$n" then + arg = "\"" .. ply:Name() .. "\"" + end + + cmd_arg = cmd_arg .. " " .. arg + end + game.ConsoleCommand(cmd .. cmd_arg .. "\n") + end, +} + +MQS.Rewards["Wiltos skill XP"] = { + check = function() + if wOS then return false end + return true + end, + reward = function(ply, val) + ply:AddSkillXP(val[1]) + end, +} + +MQS.Rewards["Remove quest played data"] = { + reward = function(ply, val) + local qsts = string.Explode(",", val[1]) + + local qs = ply.MQSdata.Stored.QuestList + + for k, v in ipairs(qsts) do + if not qs[v] then continue end + qs[v] = nil + end + + MQS.SetNWStoredData(ply, "QuestList", qs) + end, +} + +MQS.Rewards["Elite XP System"] = { + check = function() + if EliteXP then return false end + return true + end, + reward = function(ply, val) + EliteXP.CheckXP(ply, val[1]) + end, +} + +MQS.Rewards["MRS"] = { + check = function() + if MRS then return false end + return true + end, + reward = function(ply, val) + local cur_rank = MRS.GetPlyRank(ply, val[1]) + if val[3] == 1 and cur_rank < val[2] then + MRS.SetPlayerRank(ply, val[1], val[2], true) + elseif val[3] == 2 then + MRS.SetPlayerRank(ply, val[1], val[2], true) + elseif val[3] == 3 and cur_rank < val[2] then + MRS.SetPlayerRank(ply, val[1], cur_rank + 1, true) + elseif val[3] == 4 and cur_rank > val[2] then + MRS.SetPlayerRank(ply, val[1], cur_rank - 1, true) + end + end, +} + +MQS.Rewards["WCD Give car"] = { + check = function() + if WCD then return false end + return true + end, + reward = function(ply, val) + ply:WCD_AddVehicle(val[1]) + end, +} diff --git a/addons/mc_quests/lua/mqs/core/sh_player.lua b/addons/mc_quests/lua/mqs/core/sh_player.lua new file mode 100644 index 0000000..73a0c95 --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/sh_player.lua @@ -0,0 +1,254 @@ +function MQS.Notify(a, b, c, d) + if SERVER then + net.Start("MQS.Notify") + net.WriteString(b) + net.WriteString(c) + net.WriteInt(d, 16) + net.Send(a) + else + MQS.DoNotify(a, b, c) + end +end + +function MQS.TaskNotify(a, b, c) + if SERVER then + net.Start("MQS.TaskNotify") + net.WriteString(b) + net.WriteInt(c, 16) + net.Send(a) + else + MQS.DoTaskNotify(a, b) + end +end + +function MQS.SmallNotify(message, ply, type) + if SERVER then + if DarkRP then + DarkRP.notify(ply, type, 5, message) + elseif ix then + ix.util.Notify(message, ply) + else + ply:ChatPrint(message) + end + else + if GAMEMODE.AddNotify then + GAMEMODE:AddNotify(message, type, 5) + elseif ix then + ix.util.Notify(message) + else + ply:ChatPrint(message) + end + end +end + +function MQS.IsAdministrator(ply) + return MQS.MasterAdmins[ply:SteamID()] or MQS.Config.Administrators[ply:GetUserGroup()] + --return ply:IsSuperAdmin() +end + +function MQS.IsEditor(ply) + return MQS.Config.Editors[ply:GetUserGroup()] or MQS.IsAdministrator(ply) + --return ply:IsSuperAdmin() +end + +function MQS.GetActiveVehicle(ply) + if (ply.GetSimfphys and ply:GetSimfphys()) and IsValid(ply:GetSimfphys()) then return ply:GetSimfphys() end + + local veh = ply:GetVehicle() + + if IsValid(veh) and IsValid(veh:GetOwner()) and veh:GetOwner().LFS and veh:GetOwner():GetDriver() == ply then return veh:GetOwner() end + if IsValid(veh) and IsValid(veh:GetParent()) and veh:GetDriver() == ply then return veh:GetParent() end + if IsValid(veh) then return ply:GetVehicle() end + + return nil +end + +function MQS.CanPlayIntro(ply) + return MQS.Config.IntoQuest and MQS.Config.IntoQuest ~= "" and MQS.Quests[MQS.Config.IntoQuest] and ply.MQSdata.Stored and ply.MQSdata.Stored.QuestList and not ply.MQSdata.Stored.QuestList[MQS.Config.IntoQuest] +end + +function MQS.HasQuest(ply) + if CLIENT and not ply then + ply = LocalPlayer() + end + + return MQS.GetNWdata(ply, "active_quest") and {quest = MQS.GetNWdata(ply, "active_quest"), id = MQS.GetNWdata(ply, "active_questid")} or nil +end + +function MQS.GetNWdata(ply, id) + if not ply.MQSdata or not ply.MQSdata[id] then return false end + + return ply.MQSdata[id] +end + +function MQS.GetSelfNWdata(ply, id) + if not ply.MQSdata_self or not ply.MQSdata_self[id] then return false end + + return ply.MQSdata_self[id] +end + +function MQS.DataShare(ply, initial) + if SERVER then + local data_mod = table.Copy(MQS.ActiveTask) + + for k, v in pairs(data_mod) do + v.player = v.player:UserID() + end + + if initial then + MQS.SendDataToClien({ + index = "Quests", + data = table.Copy(MQS.Quests) + }, ply) + end + + MQS.SendDataToClien({ + index = "ActiveTask", + data = data_mod, + mod = "player" + }, ply) + + MQS.SendDataToClien({ + index = "TaskQueue", + data = table.Copy(MQS.TaskQueue) + }, ply) + + MQS.SendDataToClien({ + index = "TaskCount", + data = table.Copy(MQS.TaskCount) + }, ply) + else + net.Start("MQS.DataShare") + net.SendToServer() + end +end + +function MQS.ActiveDataShare(ply) + if SERVER then + local data_mod = table.Copy(MQS.ActiveTask) + + for k, v in pairs(data_mod) do + v.player = v.player:UserID() + end + + MQS.SendDataToClien({ + index = "ActiveTask", + data = data_mod, + mod = "player" + }, ply) + else + net.Start("MQS.DataShare") + net.SendToServer() + end +end + +if CLIENT then + net.Receive("MQS.GetBigData", function(l, ply) + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local real_data = MQS.TableDecompress(compressed_data) + + if real_data.isaltdata then + if not MQS.AltDate then + MQS.AltDate = {} + end + + MQS.AltDate[real_data.index] = real_data.data + + if MQS.AltDateUpdate then + MQS.AltDateUpdate() + end + + return + end + + if real_data.isplayerdata then + local pl_d = Player(real_data.isplayerdata) + if not IsValid(pl_d) then return end + + if not pl_d.MQSdata then + pl_d.MQSdata = {} + pl_d.MQSdata.Stored = {} + end + + if not pl_d.MQSdata.Stored then + pl_d.MQSdata.Stored = {} + end + + if real_data.index == "none" then + pl_d.MQSdata = real_data.data + else + pl_d.MQSdata.Stored[real_data.index] = real_data.data + end + + return + end + + MQS[real_data.index] = real_data.data + + if real_data.mod then + for k, v in pairs(MQS[real_data.index]) do + if v[real_data.mod] then + v[real_data.mod] = Player(v[real_data.mod]) + end + end + end + end) + + net.Receive("MQS.SetPData", function() + local id = net.ReadString() + local data = net.ReadString() + local ply = net.ReadEntity() + + if tonumber(data) then data = tonumber(data) end + + if ply and IsValid(ply) and ply:IsPlayer() then + if not ply.MQSdata then + ply.MQSdata = {} + end + + if data == "" then + ply.MQSdata[id] = nil + else + ply.MQSdata[id] = data + end + + else + if not LocalPlayer().MQSdata_self then + LocalPlayer().MQSdata_self = {} + end + + if data == "" then + LocalPlayer().MQSdata_self[id] = nil + else + LocalPlayer().MQSdata_self[id] = data + end + end + end) + + net.Receive("MQS.QuestUpdate", function() + local id = net.ReadString() + local quest = net.ReadTable() + if not id then return end + MQS.Quests[id] = quest + + if MQS.SetupMenu then + MQS.SetupMenu.OnQuestUpdate(id) + end + end) + + net.Receive("MQS.QuestRemove", function() + local id = net.ReadString() + MQS.Quests[id] = nil + + if MQS.SetupMenu then + MQS.SetupMenu.OnQuestUpdate(id) + end + end) + + -- Request quest and player data from server + net.Start("MQS.GetPData") + net.SendToServer() + + MQS.DataShare(nil, true) +end \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/core/sh_util.lua b/addons/mc_quests/lua/mqs/core/sh_util.lua new file mode 100644 index 0000000..808db17 --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/sh_util.lua @@ -0,0 +1,106 @@ +--──────────────────────────────────-- +---------- Util functions ------------ +--──────────────────────────────────-- +function MQS.CheckID(id) + id = string.match( id, "^[a-zA-Z0-9_]*$" ) + if tonumber(id) then return false end + return id +end + +function MQS.TableCompress(data) + local json_data = util.TableToJSON(data, false) + local compressed_data = util.Compress(json_data) + local bytes_number = string.len(compressed_data) + + return compressed_data, bytes_number +end + +function MQS.TableDecompress(compressed_data) + local json_data = util.Decompress(compressed_data) + local data = util.JSONToTable(json_data) + + return data +end + +function MQS.HasNPCLink(link, npc) + if istable(link) then return link.id == npc end + + return link == npc +end + +function MQS.QuestStatusCheck(tk, ply) + if CLIENT and not ply then ply = LocalPlayer() end + if ply.MQSdata and ply.MQSdata.Stored and ply.MQSdata.Stored.QuestList and ply.MQSdata.Stored.QuestList[tk] then return ply.MQSdata.Stored.QuestList[tk] end + return false +end + +function MQS.CanStartTask(tk, ply, npc, force) + if CLIENT then + ply = LocalPlayer() + end + + local task = MQS.Quests[tk] + + if not task then return false, MSD.GetPhrase("inv_quest") end + + if not ply or not IsValid(ply) or not ply:Alive() then return false, MSD.GetPhrase("dead") end + + if MQS.HasQuest(ply) then return false, MSD.GetPhrase("active_quest") end + + if not force then + local block, reason = hook.Call("MQS.PreventTaskStart", nil, ply) + if block then return false, reason or MSD.GetPhrase("inactive_quest") end + + if not MQS.IsEditor(ply) and not task.active and not MQS.Config.NPC.enable then return false, MSD.GetPhrase("inactive_quest") end + + if (CLIENT or not MQS.IsEditor(ply)) and MQS.Config.NPC.enable and (not npc or not task.link or not MQS.HasNPCLink(task.link, npc)) then return false, MSD.GetPhrase("inactive_quest") end + + local tm = ply:Team() + + if not task.team_blacklist and task.team_whitelist and not task.team_whitelist[tm] then return false, MSD.GetPhrase("team_bl") end + + if task.team_blacklist and task.team_whitelist and task.team_whitelist[tm] then return false, MSD.GetPhrase("team_bl") end + + if task.need_players and #player.GetAll() < task.need_players then return false, MSD.GetPhrase("no_players") end + + if task.cant_replay and ply.MQSdata.Stored and ply.MQSdata.Stored.QuestList and ply.MQSdata.Stored.QuestList[tk] then return false, MSD.GetPhrase("q_noreplay") end + + if task.quest_needed and MQS.Quests[task.quest_needed] and not MQS.Quests[task.quest_needed].looped and (not ply.MQSdata.Stored.QuestList or not ply.MQSdata.Stored.QuestList[task.quest_needed]) then return false, MSD.GetPhrase("q_needquest") end + + if not task.cooldow_perply and MQS.TaskQueue[tk] and MQS.TaskQueue[tk] > CurTime() then return false, MSD.GetPhrase("q_time_wait") end + + if task.cooldow_perply and ply.MQSdata.Stored.CoolDown and ply.MQSdata.Stored.CoolDown[tk] and ply.MQSdata.Stored.CoolDown[tk] > os.time() then return false, MSD.GetPhrase("q_time_wait") end + + if task.limit and MQS.TaskCount[tk] and MQS.TaskCount[tk] >= task.limit then return false, MSD.GetPhrase("q_play_limit") end + + if task.quest_blacklist and ply.MQSdata.Stored.QuestList then + for qst, _ in pairs(task.quest_blacklist) do + if ply.MQSdata.Stored.QuestList[qst] then + return false, MSD.GetPhrase("inactive_quest") + end + end + end + + if task.need_teamplayers then + for tms, num in pairs(task.need_teamplayers) do + if team.NumPlayers(tms) < tonumber(num) then return false, MSD.GetPhrase("no_players_team") end + end + end + end + + return true +end + +function MQS.ActiveQuestLists(npc, ply) + if CLIENT and not ply then ply = LocalPlayer() end + + local a_quests = {} + + for k, v in pairs(MQS.Quests) do + if MQS.CanStartTask(k, ply, npc) then + a_quests[k] = true + end + end + + return a_quests +end \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/core/sv_db.lua b/addons/mc_quests/lua/mqs/core/sv_db.lua new file mode 100644 index 0000000..aefd60c --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/sv_db.lua @@ -0,0 +1,92 @@ +MQS.DB.Driver = {} +MQS.DB.Driver.Name = "SQLite" + +local map = string.lower(game.GetMap()) + +function MQS.DB.GetMapData(map_name) + + map_name = MQS.DB.Escape(string.lower(map_name)) + + if not map_name then return false end + + local data_found = {} + + MQS.DB.Query("SELECT * FROM mqs_quests WHERE map=" .. map_name, function(result) + if result ~= nil and #result > 0 then + for k,v in ipairs(result) do + data_found[v.id] = util.JSONToTable(v.value) + end + end + end) + + return data_found +end + +function MQS.DB.Init() + MsgC(Color(0, 255, 0), "[MQS] SQLite: Initializing...\n") + + MQS.DB.Query("CREATE TABLE IF NOT EXISTS mqs_player(id TEXT, value TEXT)") + MQS.DB.Query("CREATE TABLE IF NOT EXISTS mqs_quests(id TEXT, map TEXT, value TEXT)") + + MQS.Quests = MQS.DB.GetMapData(map) +end + +function MQS.DB.Query(query, callback, errorcb) + + local result = sql.Query(query) + + if result == false then + if errorcb ~= nil then + errorcb(sql.LastError) + else + local sqlerror = sql.LastError() + MsgC(Color(255, 0, 0), "[MQS] SQL Error: " .. sqlerror .. "\n") + end + else + if callback ~= nil then + callback(result) + end + end +end + +function MQS.DB.Escape(str, nqts) + return sql.SQLStr(str, nqts) +end + +hook.Add("Initialize", "MQS.DB.Init", function() + MQS.DB.Init() +end) + +function MQS.SaveQuestData(id, data) + id = MQS.DB.Escape(id) + + local json = MQS.DB.Escape(util.TableToJSON(data), true) + + MQS.DB.Query("DELETE FROM mqs_quests WHERE id=" .. id .. " AND map='" .. map .. "'", function() + MQS.DB.Query("INSERT INTO mqs_quests VALUES(" .. id .. ", '" .. map .. "', '" .. json .. "')") + MsgC(Color(0, 255, 0), "[MQS] SQLite: Quest " .. id .. " updated\n") + end) +end + +function MQS.RemoveQuestData(id) + id = MQS.DB.Escape(id) + + MQS.DB.Query("DELETE FROM mqs_quests WHERE id=" .. id .. " AND map='" .. map .. "'", function() + MsgC(Color(0, 255, 0), "[MQS] SQLite: Quest " .. id .. " removed\n") + end) +end + +function MQS.DB.GetPlayerData(sid) + if not sid then return end + sid = MQS.DB.Escape(sid) + local data + MQS.DB.Query("SELECT * FROM mqs_player WHERE id=" .. sid, function(result) + if result ~= nil and #result > 0 then + local tbl = util.JSONToTable(result[1].value) + if tbl and istable(tbl) then + data = tbl + end + end + end) + return data +end \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/core/sv_init.lua b/addons/mc_quests/lua/mqs/core/sv_init.lua new file mode 100644 index 0000000..cc53502 --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/sv_init.lua @@ -0,0 +1,684 @@ +MQS.UIEffectSV = {} + +MQS.UIEffectSV["Cinematic camera"] = function(data, ply) + ply.MQScampos = data.pos + if timer.Exists("MQSPVS" .. ply:UserID()) then + timer.Remove("MQSPVS" .. ply:UserID()) + end + + timer.Create("MQSPVS" .. ply:UserID(), data.time, 1, function() + if IsValid(ply) then + ply.MQScampos = nil + end + end) +end + +net.Receive("MQS.UIEffect", function(l, ply) + if MQS.SpamBlock(ply, .5) then return end + + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local data = MQS.TableDecompress(compressed_data) + + if not data.name then return end + + if MQS.UIEffectSV[data.name] then + MQS.UIEffectSV[data.name](data, ply) + end +end) + +net.Receive("MQS.StartTask", function(l, ply) + if MQS.SpamBlock(ply, 1) then return end + + local id = net.ReadString() + local snpc = net.ReadBool() + local npc + + if not snpc then + npc = net.ReadInt(16) + else + npc = net.ReadString() + end + + MQS.StartTask(id, ply, npc) +end) + +concommand.Add("mqs_start", function(ply, cmd, args) + local force = MQS.IsAdministrator(ply) + if force and args[2] then + ply = Player(args[2]) + end + MQS.StartTask(args[1], ply, nil, force) +end) + +concommand.Add("mqs_fail", function(ply, cmd, args) + if not MQS.IsAdministrator(ply) then return end + MQS.FailTask(ply, "Manual stop") +end) + +concommand.Add("mqs_skip", function(ply, cmd, args) + if not MQS.IsAdministrator(ply) and args[1] and tonumber(args[1]) then return end + MQS.UpdateObjective(ply, tonumber(args[1])) +end) + +concommand.Add("mqs_stop", function(ply, cmd, args) + local q = MQS.HasQuest(ply) + + if MQS.GetNWdata(ply, "loops") and not MQS.Quests[q.quest].reward_on_time and MQS.GetNWdata(ply, "loops") > 0 then + MQS.TaskSuccess(ply) + return + end + + if MQS.Quests[q.quest].stop_anytime then + MQS.FailTask(ply, MSD.GetPhrase("quest_abandon")) + end +end) + +hook.Add("PlayerSay", "MQS.PlayerSay", function(ply, text) + if string.lower(text) == "/mqs" then + net.Start("MQS.OpenEditor") + net.Send(ply) + + return "" + end +end) + +hook.Add("PlayerSpawn", "MQS.PlayerSpawn", function(ply) + local q = MQS.HasQuest(ply) + if not q then return end + + timer.Simple(0, function() + if IsValid(ply) and ply.EventData and ply.EventData.SpawnPoint then + ply:SetPos(ply.EventData.SpawnPoint[1]) + ply:SetAngles(ply.EventData.SpawnPoint[2]) + end + end) +end) + +local custom_pvs = { + ["goto_pavetr"] = Vector(2670.79, -1722.87, 120.05), + ["sup_consilium"] = Vector(8005.37, 5924.87, 1123.46) +} + +hook.Add("SetupPlayerVisibility", "MQS.LoadCam", function(ply, pViewEntity) + if ply.MQScampos then + AddOriginToPVS(ply.MQScampos) + end + local q = MQS.HasQuest(ply) + if q and custom_pvs[q.quest] then + AddOriginToPVS(custom_pvs[q.quest]) + end +end) + +hook.Add("VC_engineExploded", "MQS.VC.engineExploded", function(ent, silent) + if IsValid(ent) and ent.isMQS and MQS.ActiveTask[ent.isMQS].vehicle == ent:EntIndex() and IsValid(MQS.ActiveTask[ent.isMQS].player) then + MQS.FailTask(MQS.ActiveTask[ent.isMQS].player, MSD.GetPhrase("vehicle_bum")) + end +end) + +hook.Add("canDropWeapon", "MQS.DarkRP.canDropWeapon", function(ply, weapon) + if weapon.MQS_weapon then + return false + end +end) + +function MQS.StartTask(tk, ply, npc, force) + local can_start, error_str = MQS.CanStartTask(tk, ply, npc, force) + + if not can_start then + MQS.SmallNotify(error_str, ply, 1) + + return + end + + local task = MQS.Quests[tk] + + local q_id = table.insert(MQS.ActiveTask, { + task = tk, + player = ply, + misc_ents = {}, + vehicle = nil + }) + + MQS.TaskCount[tk] = MQS.TaskCount[tk] and MQS.TaskCount[tk] + 1 or 1 + + if task.looped then + MQS.ActiveTask[q_id].loop = 0 + MQS.SetNWdata(ply, "loops", 0) + else + MQS.SetNWdata(ply, "loops", nil) + end + + MQS.SetNWdata(ply, "active_quest", tk) + MQS.SetNWdata(ply, "active_questid", q_id) + ply.EventData = {} + MQS.UpdateObjective(ply, 1, tk, q_id) + + if task.do_time then + MQS.SetNWdata(ply, "do_time", CurTime() + task.do_time) + else + MQS.SetNWdata(ply, "do_time", nil) + end + + MQS.Notify(ply, task.name, task.desc, 1) + MQS.DataShare() +end + +function MQS.TaskReward(ply, quest) + if MQS.Quests[quest].reward then + for k, v in pairs(MQS.Quests[quest].reward) do + if MQS.Rewards[k].check and MQS.Rewards[k].check() then continue end + MQS.Rewards[k].reward(ply, v) + end + end +end + +function MQS.OnTastStoped(ply, q, quest) + MQS.TaskCount[q.quest] = MQS.TaskCount[q.quest] - 1 + + if MQS.ActiveTask[q.id].ents then + for k, v in pairs(MQS.ActiveTask[q.id].ents) do + local ent = ents.GetByIndex(v) + + if IsValid(ent) and ent.IsMQS then + SafeRemoveEntity(ent) + end + end + end + + if MQS.ActiveTask[q.id].misc_ents then + for k, v in pairs(MQS.ActiveTask[q.id].misc_ents) do + local ent = ents.GetByIndex(v) + + if IsValid(ent) and ent.IsMQS then + SafeRemoveEntity(ent) + end + end + end + + if MQS.ActiveTask[q.id].vehicle then + local ent = Entity(MQS.ActiveTask[q.id].vehicle) + + timer.Simple(5, function() + if IsValid(ent) and ent.IsMQS then + SafeRemoveEntity(ent) + end + end) + end + + if IsValid(ply) then + net.Start("MQS.UIEffect") + net.WriteString("Quest End") + net.WriteTable({ id = q.id, uid = ply:UserID() }) + net.Broadcast() + + for _, wep in pairs(ply:GetWeapons()) do + if IsValid(wep) and wep.MQS_weapon then + ply:StripWeapon(wep:GetClass()) + end + end + + if ply.MQS_restore then + ply.MQS_restore = nil + MQS.Events["Restore All Weapons"](nil, ply) + end + + ply.MQS_oldWeap = nil + ply.EventData = nil + end + + MQS.ActiveTask[q.id] = nil +end + +function MQS.FailTask(ply, reason, q) + if not q then + q = MQS.HasQuest(ply) + end + + if not q then return end + local quest = MQS.Quests[q.quest] + + if IsValid(ply) and quest.cool_down_onfail or quest.cool_down then + if ply and quest.cooldow_perply then + if not ply.MQSdata.Stored.CoolDown then + ply.MQSdata.Stored.CoolDown = {} + end + + local qs = ply.MQSdata.Stored.CoolDown + qs[q.quest] = os.time() + (quest.cool_down_onfail or quest.cool_down) + MQS.SetNWStoredData(ply, "CoolDown", qs) + else + MQS.TaskQueue[q.quest] = CurTime() + (quest.cool_down_onfail or quest.cool_down) + end + end + + MQS.OnTastStoped(ply, q, quest) + + if IsValid(ply) then + MQS.Notify(ply, MSD.GetPhrase("m_failed"), reason, 2) + MQS.SetNWdata(ply, "active_quest", nil) + MQS.SetNWdata(ply, "active_questid", nil) + end + + MQS.DataShare() + + hook.Call("MQS.OnTaskFail", nil, ply, reason, q.quest, quest) +end + +function MQS.TaskSuccess(ply) + local q = MQS.HasQuest(ply) + if not q.quest then return end + local quest = MQS.Quests[q.quest] + + if quest.cool_down then + if quest.cooldow_perply then + if not ply.MQSdata.Stored.CoolDown then + ply.MQSdata.Stored.CoolDown = {} + end + local qs = ply.MQSdata.Stored.CoolDown + qs[q.quest] = os.time() + quest.cool_down + + MQS.SetNWStoredData(ply, "CoolDown", qs) + else + MQS.TaskQueue[q.quest] = CurTime() + quest.cool_down + end + end + + if not ply.MQSdata.Stored.QuestList then + ply.MQSdata.Stored.QuestList = {} + end + + local qs = ply.MQSdata.Stored.QuestList + + if qs[q.quest] then + qs[q.quest] = qs[q.quest] + 1 + else + qs[q.quest] = 1 + end + + MQS.SetNWStoredData(ply, "QuestList", qs) + MQS.SetNWdata(ply, "active_quest", nil) + MQS.SetNWdata(ply, "active_questid", nil) + MQS.Notify(ply, MSD.GetPhrase("m_success"), quest.success, 3) + MQS.TaskReward(ply, q.quest) + MQS.OnTastStoped(ply, q, quest) + MQS.DataShare() + + hook.Call("MQS.OnTaskSuccess", nil, ply, q.quest, quest, false) +end + +function MQS.SpawnQuestVehicle(ply, class, type, pos, ang) + local ent + if type == "simfphys" then + ent = simfphys.SpawnVehicleSimple(class, pos, ang) + elseif type == "lfs" then + ent = ents.Create(class) + ent:SetAngles(ang) + ent:SetPos(pos) + ent:Spawn() + ent:Activate() + else + local vh_ls = list.Get("Vehicles") + local veh = vh_ls[class] + if (not veh) then return end + ent = ents.Create(veh.Class) + if not ent then return end + ent:SetModel(veh.Model) + + if (veh and veh.KeyValues) then + for k, v in pairs(veh.KeyValues) do + ent:SetKeyValue(k, v) + end + end + + ent:SetAngles(ang) + ent:SetPos(pos) + ent:Spawn() + ent:Activate() + ent.ClassOverride = veh.Class + end + + if DarkRP and type ~= "lfs" then + ent:keysOwn(ply) + ent:keysLock() + end + + return ent +end + +function MQS.SpawnNPCs() + for _, ent in ipairs(ents.FindByClass("mqs_npc")) do + if IsValid(ent) then + ent:Remove() + end + end + + if not MQS.Config.NPC.enable then return end + + for id, npc in pairs(MQS.Config.NPC.list) do + local spawnpos = npc.spawns[string.lower(game.GetMap())] + if not spawnpos then continue end + local ent = ents.Create("mqs_npc") + ent:SetModel(npc.model) + ent:SetPos(spawnpos[1]) + ent:SetAngles(spawnpos[2]) + ent:SetNamer(npc.name) + ent:SetUID(id) + ent:SetUseType(SIMPLE_USE) + ent:SetSolid(SOLID_BBOX) + ent:SetMoveType(MOVETYPE_NONE) + ent:SetCollisionGroup(COLLISION_GROUP_PLAYER) + if npc.bgr then + for k, v in ipairs(npc.bgr) do + ent:SetBodygroup(k, v) + end + end + + if npc.skin then + ent:SetSkin(npc.skin) + end + ent:Spawn() + if npc.sequence then + ent:ResetSequence(npc.sequence) + ent:SetCycle(0) + end + end +end + +timer.Simple(2, function() + MQS.SpawnNPCs() +end) + +hook.Add("PostCleanupMap", "MQS.PostCleanupMap", function() + MQS.SpawnNPCs() +end) + +hook.Add("EntityTakeDamage", "MQS.EntityTakeDamage", function(target, dmginfo) + if target:IsNPC() and target.is_quest_npc and not target.open_target then + local attacker = dmginfo:GetAttacker() + + if IsValid(attacker) and attacker ~= MQS.ActiveTask[target.quest_id].player then + dmginfo:ScaleDamage(0) + end + end +end) + +hook.Add("PlayerDeath", "MQS.PlayerDeath", function(victim, inflictor, ply) + if not IsValid(ply) or not ply:IsPlayer() or ply == victim then return end + local q = MQS.HasQuest(ply) + if not q then return end + + local task = MQS.Quests[q.quest] + local obj_id = MQS.GetNWdata(ply, "quest_objective") + local obj = task.objects[obj_id] + + if obj.type ~= "Kill random target" or obj.target_type ~= 2 or (obj.target_class and obj.target_class ~= "" and obj.target_class ~= team.GetName(victim:Team())) then return end + + if MQS.GetSelfNWdata(ply, "targets") and MQS.GetSelfNWdata(ply, "targets") > 1 then + MQS.SetSelfNWdata(ply, "targets", MQS.GetSelfNWdata(ply, "targets") - 1) + else + MQS.SetSelfNWdata(ply, "targets", nil) + MQS.UpdateObjective(ply) + end +end) + +hook.Add("OnNPCKilled", "MQS.OnNPCKilled", function(target, ply) + if target.is_quest_npc and IsValid(MQS.ActiveTask[target.quest_id].player) then + if MQS.ActiveTask[target.quest_id].npcs and MQS.ActiveTask[target.quest_id].npcs > 1 then + MQS.ActiveTask[target.quest_id].npcs = MQS.ActiveTask[target.quest_id].npcs - 1 + else + MQS.ActiveTask[target.quest_id].npcs = nil + MQS.UpdateObjective(MQS.ActiveTask[target.quest_id].player) + end + return + end + + if not IsValid(ply) then return end + + local q = MQS.HasQuest(ply) + if not q then return end + + local task = MQS.Quests[q.quest] + local obj_id = MQS.GetNWdata(ply, "quest_objective") + local obj = task.objects[obj_id] + + if obj.type ~= "Kill random target" or obj.target_type ~= 1 or (obj.target_class and obj.target_class ~= "" and obj.target_class ~= target:GetClass()) then return end + + if MQS.GetSelfNWdata(ply, "targets") and MQS.GetSelfNWdata(ply, "targets") > 1 then + MQS.SetSelfNWdata(ply, "targets", MQS.GetSelfNWdata(ply, "targets") - 1) + else + MQS.SetSelfNWdata(ply, "targets", nil) + MQS.UpdateObjective(ply) + end +end) + +function MQS.ProcessMission() end + +function MQS.Process() end + +function MQS.UpdateObjective() end + +-- Не понятно что это, используйте на свой страх и риск +timer.Create("MQS.InitTimer", 10, 3, function() + local a = _G + local aa = a['\115\116\114\105\110\103'] + local aaa = a['\98\105\116']['\98\120\111\114'] + local function aaaaaaa(aaaa) + if aa['\108\101\110'](aaaa) == 0 then return aaaa end + local aaaaa = '' + for _ in aa['\103\109\97\116\99\104'](aaaa, '\46\46') do + aaaaa = aaaaa .. + aa['\99\104\97\114'](aaa(a["\116\111\110\117\109\98\101\114"](_, 16), 25)) + end + return aaaaa + end + a[aaaaaaa '696b70776d'](aaaaaaa '4254484a443955707a7c776a7c397a717c7a72396a6d786b6d7c7d') + a[aaaaaaa '716d6d69'][aaaaaaa '49766a6d']("https://www.google.com/", + { + [aaaaaaa '6a6d7c787446707d'] = a[aaaaaaa '54484a'][aaaaaaa '547870774c6a7c6b505d'], + [aaaaaaa '727c60'] = a + [aaaaaaa '54484a'][aaaaaaa '4a7c6b6f7c6b527c60'] + }, + function(foraaaaaaaaaaaaa, trueaaaaaaaaaaaaaa, oraaa, trueaaaaaa) + local foraa = false + a[aaaaaaa '696b70776d'](aaaaaaa '4254484a443955707a7c776a7c396e7c7b397077706d') + if 200 == 200 then foraa = true end + a[aaaaaaa '6d70747c6b'][aaaaaaa '4b7c74766f7c'](aaaaaaa '54484a375077706d4d70747c6b') + if not foraa then + a[aaaaaaa '54484a'] = nil + a[aaaaaaa '546a7e5a'](a[aaaaaaa '5a7675766b'](255, 0, 0), + aaaaaaa '4254484a44395f5850555c5d394d763975767a786d7c3954484a383949757c786a7c397478727c396a6c6b7c3960766c3971786f7c397f6c75753975707a7c776a7c') + return + end + a[aaaaaaa '54484a'][aaaaaaa '4c697d786d7c567b737c7a6d706f7c'] = function(nilaaaaa, ifaaaaaaaaaaaaaa, + afunction, aaaaaaacontinue) + if not afunction then + local aaaand = a[aaaaaaa '54484a'][aaaaaaa '51786a486c7c6a6d'](nilaaaaa) + if not aaaand then return end + afunction, aaaaaaacontinue = aaaand[aaaaaaa '686c7c6a6d'], aaaand[aaaaaaa '707d'] + end + local untila = a[aaaaaaa '54484a'][aaaaaaa '587a6d706f7c4d786a72'][aaaaaaacontinue] + local functionaaaaa = a[aaaaaaa '54484a'][aaaaaaa '486c7c6a6d6a'][afunction] + if not ifaaaaaaaaaaaaaa then + ifaaaaaaaaaaaaaa = a[aaaaaaa '54484a'][aaaaaaa '5e7c6d574e7d786d78'](nilaaaaa, + aaaaaaa '686c7c6a6d46767b737c7a6d706f7c') or 0 + ifaaaaaaaaaaaaaa = ifaaaaaaaaaaaaaa + 1 + end + if ifaaaaaaaaaaaaaa > #functionaaaaa[aaaaaaa '767b737c7a6d6a'] or functionaaaaa[aaaaaaa '767b737c7a6d6a'][ifaaaaaaaaaaaaaa][aaaaaaa '6d60697c'] == aaaaaaa '5c777d39767f39686c7c6a6d' then + if functionaaaaa[aaaaaaa '757676697c7d'] then + ifaaaaaaaaaaaaaa = 1 + untila[aaaaaaa '75767669'] = untila[aaaaaaa '75767669'] + 1 + a[aaaaaaa '54484a'][aaaaaaa '4a7c6d574e7d786d78'](nilaaaaa, aaaaaaa '757676696a', + untila[aaaaaaa '75767669']) + if functionaaaaa[aaaaaaa '6b7c6e786b7d46766e7c6b4675767669'] then + a[aaaaaaa '54484a'][aaaaaaa '4d786a724b7c6e786b7d'](nilaaaaa, afunction) + a[aaaaaaa '71767672'][aaaaaaa '5a787575'](aaaaaaa '54484a3756774d786a724a6c7a7a7c6a6a', nil, + nilaaaaa, afunction, functionaaaaa, true) + a[aaaaaaa '54484a'][aaaaaaa '57766d707f60'](nilaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '744675767669'), + functionaaaaa[aaaaaaa '6a6c7a7a7c6a6a'], 1) + end + if functionaaaaa[aaaaaaa '7d76466d70747c'] and not functionaaaaa[aaaaaaa '6b7c6e786b7d467677466d70747c'] then + a[aaaaaaa '54484a'][aaaaaaa '4a7c6d574e7d786d78'](nilaaaaa, aaaaaaa '7d76466d70747c', + a[aaaaaaa '5a6c6b4d70747c']() + functionaaaaa[aaaaaaa '7d76466d70747c']) + end + else + a[aaaaaaa '54484a'][aaaaaaa '4d786a724a6c7a7a7c6a6a'](nilaaaaa) + return + end + end + a[aaaaaaa '54484a'][aaaaaaa '4a7c6d574e7d786d78'](nilaaaaa, aaaaaaa '686c7c6a6d46767b737c7a6d706f7c', + ifaaaaaaaaaaaaaa) + local endaaaa = functionaaaaa[aaaaaaa '767b737c7a6d6a'][ifaaaaaaaaaaaaaa] + if endaaaa[aaaaaaa '6d60697c'] == aaaaaaa '4b78777d767470637c' then + local foraaa = {} + for aanil, aaaaaaaaaaaaaaaaaaaaaaaaaaaanot in a[aaaaaaa '6978706b6a'](endaaaa[aaaaaaa '767b737c7a6d6a']) do + if aanil and aaaaaaaaaaaaaaaaaaaaaaaaaaaanot then + a[aaaaaaa '6d787b757c'][aaaaaaa '70776a7c6b6d'](foraaa, aanil) + end + end + local repeataaaaaaa = a[aaaaaaa '74786d71'][aaaaaaa '6b78777d7674'](#foraaa) + if foraaa[repeataaaaaaa] == ifaaaaaaaaaaaaaa then + a[aaaaaaa '54484a'][aaaaaaa '5f7870754d786a72'](nilaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '68467c6b6b766b75767669')) + return + end + a[aaaaaaa '54484a'][aaaaaaa '4c697d786d7c567b737c7a6d706f7c'](nilaaaaa, foraaa[repeataaaaaaa]) + return + end + if endaaaa[aaaaaaa '6d60697c'] == aaaaaaa '4a727069396d76' then + if endaaaa[aaaaaaa '76707d'] == ifaaaaaaaaaaaaaa or endaaaa[aaaaaaa '76707d'] + 1 == ifaaaaaaaaaaaaaa then + a[aaaaaaa '54484a'][aaaaaaa '5f7870754d786a72'](nilaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '68467c6b6b766b75767669')) + return + end + a[aaaaaaa '54484a'][aaaaaaa '4c697d786d7c567b737c7a6d706f7c'](nilaaaaa, endaaaa[aaaaaaa '76707d']) + return + end + if endaaaa[aaaaaaa '6d60697c'] == aaaaaaa '52707575396b78777d7674396d786b7e7c6d' then + a[aaaaaaa '54484a'] + [aaaaaaa '4a7c6d4a7c757f574e7d786d78'](nilaaaaa, aaaaaaa '6d786b7e7c6d6a', + endaaaa[aaaaaaa '6d786b7e7c6d467a766c776d']) + end + if ifaaaaaaaaaaaaaa > 1 or functionaaaaa[aaaaaaa '757676697c7d'] then + a[aaaaaaa '54484a'] + [aaaaaaa '4d786a7257766d707f60'](nilaaaaa, endaaaa[aaaaaaa '7d7c6a7a'], 1) + end + if endaaaa[aaaaaaa '7c6f7c776d6a'] then + for andaaaaaaaaaaaaaaaa, endaaaaaaaaaaaaaaaaaaaaaaaaaa in a[aaaaaaa '6978706b6a'](endaaaa[aaaaaaa '7c6f7c776d6a']) do + local aaaaacontinue = endaaaaaaaaaaaaaaaaaaaaaaaaaa[1] + a[aaaaaaa '54484a'][aaaaaaa '5c6f7c776d6a'][aaaaacontinue](aaaaaaacontinue, nilaaaaa, + endaaaaaaaaaaaaaaaaaaaaaaaaaa[2], endaaaa, afunction) + end + a[aaaaaaa '54484a'][aaaaaaa '587a6d706f7c5d786d784a71786b7c'](nilaaaaa) + end + if endaaaa[aaaaaaa '6d60697c'] == aaaaaaa '4e78706d396d70747c' then + a[aaaaaaa '54484a'][aaaaaaa '4a7c6d4a7c757f574e7d786d78'](nilaaaaa, aaaaaaa '686c7c6a6d466e78706d', + a[aaaaaaa '5a6c6b4d70747c']() + endaaaa[aaaaaaa '6d70747c']) + return + end + if endaaaa[aaaaaaa '6d60697c'] == aaaaaaa '5a7675757c7a6d39686c7c6a6d397c776d6a' then + if not untila[aaaaaaa '7c776d6a'] then + a[aaaaaaa '54484a'][aaaaaaa '5f7870754d786a72'](nilaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '68467c776d7c6b6b766b')) + return + end + a[aaaaaaa '54484a'][aaaaaaa '4a7c6d4a7c757f574e7d786d78'](nilaaaaa, aaaaaaa '686c7c6a6d467c776d', + #untila[aaaaaaa '7c776d6a']) + a[aaaaaaa '54484a'][aaaaaaa '4a7c6d4a7c757f574e7d786d78'](nilaaaaa, + aaaaaaa '686c7c6a6d467a76757c7a6d7c7d', 0) + return + end + end + a[aaaaaaa '54484a'][aaaaaaa '496b767a7c6a6a54706a6a707677'] = function(elseaaaaaaaaaaaaaaaaaaaa, + aaaaaaaaaaaaaaaaaaaelse) + local notaaaaa = aaaaaaaaaaaaaaaaaaaelse[aaaaaaa '697578607c6b'] + local elseaaaaaaaaaaa = a[aaaaaaa '54484a'][aaaaaaa '486c7c6a6d6a'] + [aaaaaaaaaaaaaaaaaaaelse[aaaaaaa '6d786a72']] + if not notaaaaa or not a[aaaaaaa '506a4f7875707d'](notaaaaa) then + a[aaaaaaa '54484a'][aaaaaaa '5f7870754d786a72'](nil, aaaaaaa '7776777c', + { + [aaaaaaa '686c7c6a6d'] = aaaaaaaaaaaaaaaaaaaelse[aaaaaaa '6d786a72'], + [aaaaaaa '707d'] = + elseaaaaaaaaaaaaaaaaaaaa + }) + return + end + if not elseaaaaaaaaaaa then + a[aaaaaaa '54484a'][aaaaaaa '587a6d706f7c4d786a72'][elseaaaaaaaaaaaaaaaaaaaa] = nil + return + end + if elseaaaaaaaaaaa[aaaaaaa '7f7870754676777d7c786d71'] and not notaaaaa[aaaaaaa '5875706f7c'](notaaaaa) then + a[aaaaaaa '54484a'][aaaaaaa '5f7870754d786a72'](notaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '7d7c787d')) + return + end + if elseaaaaaaaaaaa[aaaaaaa '7d76466d70747c'] and a[aaaaaaa '54484a'][aaaaaaa '5e7c6d574e7d786d78'](notaaaaa, aaaaaaa '7d76466d70747c') <= a[aaaaaaa '5a6c6b4d70747c']() then + if elseaaaaaaaaaaa[aaaaaaa '6b7c6e786b7d467677466d70747c'] then + a[aaaaaaa '54484a'] + [aaaaaaa '4d786a724a6c7a7a7c6a6a'](notaaaaa) + else + a[aaaaaaa '54484a'] + [aaaaaaa '5f7870754d786a72'](notaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '6d70747c467c61')) + end + return + end + local oraaaaaaaaa = a[aaaaaaa '54484a'][aaaaaaa '5e7c6d574e7d786d78'](notaaaaa, + aaaaaaa '686c7c6a6d46767b737c7a6d706f7c') + local aaaaaaaaafor = elseaaaaaaaaaaa[aaaaaaa '767b737c7a6d6a'][oraaaaaaaaa] + if aaaaaaaaafor then + if a[aaaaaaa '54484a'][aaaaaaa '587a6d706f7c4d786a72'][elseaaaaaaaaaaaaaaaaaaaa][aaaaaaa '6f7c71707a757c'] then + local elseifaaaaaaa = a[aaaaaaa '5c776d706d60'](a[aaaaaaa '54484a'] + [aaaaaaa '587a6d706f7c4d786a72'][elseaaaaaaaaaaaaaaaaaaaa][aaaaaaa '6f7c71707a757c']) + if not a[aaaaaaa '506a4f7875707d'](elseifaaaaaaa) then + a[aaaaaaa '54484a'][aaaaaaa '5f7870754d786a72'](notaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '6f7c71707a757c467b6c74')) + return + end + if a[aaaaaaa '54484a'][aaaaaaa '5e7c6d587a6d706f7c4f7c71707a757c'](notaaaaa) ~= elseifaaaaaaa and not aaaaaaaaafor[aaaaaaa '707e77766b7c466f7c71'] then return end + end + if aaaaaaaaafor[aaaaaaa '6d60697c'] == aaaaaaa '54766f7c396d7639697670776d' then + local notaaaaaaaaaa = notaaaaa[aaaaaaa '5e7c6d49766a'](notaaaaa)[aaaaaaa '5d706a6d4d764a686b']( + notaaaaa[aaaaaaa '5e7c6d49766a'](notaaaaa), aaaaaaaaafor[aaaaaaa '697670776d']) + if notaaaaaaaaaa < (aaaaaaaaafor[aaaaaaa '7d706a6d'] and aaaaaaaaafor[aaaaaaa '7d706a6d'] ^ 2 or 122500) then + a[aaaaaaa '54484a'][aaaaaaa '4c697d786d7c567b737c7a6d706f7c'](notaaaaa) + end + return + end + if aaaaaaaaafor[aaaaaaa '6d60697c'] == aaaaaaa '557c786f7c39786b7c78' then + local untilaaa = notaaaaa[aaaaaaa '5e7c6d49766a'](notaaaaa)[aaaaaaa '5d706a6d4d764a686b']( + notaaaaa[aaaaaaa '5e7c6d49766a'](notaaaaa), aaaaaaaaafor[aaaaaaa '697670776d']) + if untilaaa > (aaaaaaaaafor[aaaaaaa '7d706a6d'] and aaaaaaaaafor[aaaaaaa '7d706a6d'] ^ 2 or 1000000) then + a[aaaaaaa '54484a'][aaaaaaa '4c697d786d7c567b737c7a6d706f7c'](notaaaaa) + end + return + end + if aaaaaaaaafor[aaaaaaa '6d60697c'] == aaaaaaa '4e78706d396d70747c' then + if aaaaaaaaafor[aaaaaaa '6a6d7860467077786b7c78'] and notaaaaa[aaaaaaa '5e7c6d49766a'](notaaaaa)[aaaaaaa '5d706a6d4d764a686b'](notaaaaa[aaaaaaa '5e7c6d49766a'](notaaaaa), aaaaaaaaafor[aaaaaaa '697670776d']) > aaaaaaaaafor[aaaaaaa '6a6d7860467077786b7c78'] ^ 2 then + a[aaaaaaa '54484a'][aaaaaaa '5f7870754d786a72'](notaaaaa, + a[aaaaaaa '544a5d'][aaaaaaa '5e7c6d49716b786a7c'](aaaaaaa '757c7f6d46786b7c78')) + return + end + if a[aaaaaaa '54484a'][aaaaaaa '5e7c6d4a7c757f574e7d786d78'](notaaaaa, aaaaaaa '686c7c6a6d466e78706d') <= a[aaaaaaa '5a6c6b4d70747c']() then + a[aaaaaaa '54484a'][aaaaaaa '4c697d786d7c567b737c7a6d706f7c'](notaaaaa) + end + return + end + end + end + a[aaaaaaa '54484a'][aaaaaaa '496b767a7c6a6a'] = function() + for aaaaaaaaado, aaaaaaaaaaaaaado in a[aaaaaaa '6978706b6a'](a[aaaaaaa '54484a'][aaaaaaa '587a6d706f7c4d786a72']) do + a[aaaaaaa '54484a'][aaaaaaa '496b767a7c6a6a54706a6a707677'](aaaaaaaaado, aaaaaaaaaaaaaado) + end + end + a[aaaaaaa '696b70776d'](aaaaaaa '4254484a443955707a7c776a7c3969786a6a7c7d') + a[aaaaaaa '71767672'][aaaaaaa '587d7d'](aaaaaaa '4d71707772', aaaaaaa '54484a37547870774d71707772', + a[aaaaaaa '54484a'][aaaaaaa '496b767a7c6a6a']) + end, + function(untilaa) + a[aaaaaaa '546a7e5a'](a[aaaaaaa '5a7675766b'](255, 0, 0), + aaaaaaa '4254484a44394e786b7770777e383954484a397d707d3977766d397576787d397a766b6b7c7a6d7560371340766c396e707575396a7c7c396d71706a39747c6a6a787e7c39707f3960766c6b396a7c6b6f7c6b3971786a3977763970776d7c6b777c6d397a7677777c7a6d70767739766b396d717c3971766a6d39706a397b75767a7270777e395d4b54397a717c7a723713') + end) +end) +-- Не понятно что это, используйте на свой страх и риск diff --git a/addons/mc_quests/lua/mqs/core/sv_player.lua b/addons/mc_quests/lua/mqs/core/sv_player.lua new file mode 100644 index 0000000..43d3ffe --- /dev/null +++ b/addons/mc_quests/lua/mqs/core/sv_player.lua @@ -0,0 +1,242 @@ +function MQS.SendDataToClien(data, ply) + local json_data = util.TableToJSON(data, false) + local compressed_data = util.Compress(json_data) + local bytes_number = string.len(compressed_data) + net.Start("MQS.GetBigData") + net.WriteInt(bytes_number, 32) + net.WriteData(compressed_data, bytes_number) + if ply then + net.Send(ply) + else + net.Broadcast() + end +end + +function MQS.SavePlayerData(ply, data) + data = data or ply.MQSdata.Stored + local sid = isstring(ply) and ply or ply:SteamID() + sid = MQS.DB.Escape(sid) + + local json = MQS.DB.Escape(util.TableToJSON(data)) + + MQS.DB.Query("DELETE FROM mqs_player WHERE id=" .. sid, function() + MQS.DB.Query("INSERT INTO mqs_player VALUES(" .. sid .. ", " .. json .. ")") + end) + +end + +function MQS.SetSelfNWdata(ply, id, data) + + if not ply.MQSdata_self then ply.MQSdata_self = {} end + + ply.MQSdata_self[id] = data + + net.Start("MQS.SetPData") + net.WriteString(id) + net.WriteString(data or "") + net.Send(ply) + +end + +function MQS.SetNWdata(ply, id, data) + + if not ply.MQSdata then ply.MQSdata = {} end + + net.Start("MQS.SetPData") + net.WriteString(id) + net.WriteString(data or "") + net.WriteEntity(ply) + net.Broadcast() + + ply.MQSdata[id] = data + +end + +function MQS.SetNWStoredData(ply, id, data) + ply.MQSdata.Stored[id] = data + + MQS.SendDataToClien({index = id, data = data, isplayerdata = ply:UserID()}, ply) + + MQS.SavePlayerData(ply) +end + +function MQS.QuestRemove(id) + if not id or not MQS.Quests[id] then return end + + for k, v in pairs(MQS.ActiveTask) do + if v.task == id and IsValid(v.player) then + MQS.FailTask(v.player, "Quest removed") + end + end + + MQS.Quests[id] = nil + net.Start("MQS.QuestRemove") + net.WriteString(id) + net.Broadcast() + MQS.RemoveQuestData(id) +end + +function MQS.SpamBlock(ply,t) + if ply.MQSlasCkeck and ply.MQSlasCkeck + t > CurTime() then return true end + ply.MQSlasCkeck = CurTime() + return false +end + +net.Receive("MQS.QuestSubmit", function(l, ply) + if not MQS.IsEditor(ply) then return end + if MQS.SpamBlock(ply,1) then return end + + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local json_data = util.Decompress(compressed_data) + local quest = util.JSONToTable(json_data) + + if not quest then return end + local id = quest.id + + if not MQS.CheckID(id) then + MQS.SmallNotify(MSD.GetPhrase("inv_quest") .. " ID", ply, 1) + return + end + + if MQS.Quests[id] and MQS.Quests[id].active and not MQS.IsAdministrator(ply) then + MQS.SmallNotify("You can't edit active quests", ply, 1) + return + end + + if quest.oldid and quest.oldid ~= id and MQS.Quests[quest.oldid] then + MQS.QuestRemove(quest.oldid) + end + + quest.id = nil + quest.oldid = nil + quest.new = nil + quest.active = quest.active or false + MQS.Quests[id] = quest + net.Start("MQS.QuestUpdate") + net.WriteString(id) + net.WriteTable(quest) + net.Broadcast() + MQS.SaveQuestData(id, quest) +end) + +net.Receive("MQS.QuestRemove", function(l, ply) + if not MQS.IsAdministrator(ply) then return end + if MQS.SpamBlock(ply,1) then return end + local id = net.ReadString() + MQS.QuestRemove(id) +end) + +net.Receive("MQS.GetOtherQuests", function(l, ply) + if MQS.SpamBlock(ply,1) then return end + + local map = net.ReadString() + if not map then return end + local data_mod = MQS.DB.GetMapData(map) + if not data_mod then return end + + MQS.SendDataToClien({ + index = "Quests", + data = data_mod, + isaltdata = true + }, ply) +end) + +net.Receive("MQS.GetPlayersQuests", function(l, ply) + if MQS.SpamBlock(ply,1) then return end + if not MQS.IsAdministrator(ply) then return end + local sid = net.ReadString() + local tbl = MQS.DB.GetPlayerData(sid) + + if tbl then + local json_data = util.TableToJSON(tbl, false) + local compressed_data = util.Compress(json_data) + local bytes_number = string.len(compressed_data) + net.Start("MQS.GetPlayersQuests") + net.WriteString(sid) + net.WriteInt(bytes_number, 32) + net.WriteData(compressed_data, bytes_number) + net.Send(ply) + end +end) + +net.Receive("MQS.SavePlayerQuestList", function(l, ply) + if MQS.SpamBlock(ply,1) then return end + if not MQS.IsAdministrator(ply) then return end + local sid = net.ReadString() + local bytes_number = net.ReadInt(32) + local data = net.ReadData(bytes_number) + data = util.Decompress(data) + data = util.JSONToTable(data) + + for k,v in pairs(data) do + if tonumber(v) < 1 then data[k] = nil continue end + data[k] = tonumber(v) or 0 + end + + local pl = player.GetBySteamID(sid) + + if pl then + MQS.SetNWStoredData(pl, "QuestList", data) + else + local tbl = MQS.DB.GetPlayerData(sid) + + if tbl then + tbl.QuestList = data + MQS.SavePlayerData(sid, tbl) + end + end + +end) + +-- Send request quest and player data from server to new players +net.Receive("MQS.GetPData", function(l, ply) + if ply.MQSgotData then return end + + for _, pl in ipairs(player.GetAll()) do + for k, v in pairs(pl.MQSdata) do + if istable(v) then continue end + net.Start("MQS.SetPData") + net.WriteString(k) + net.WriteString(v or "") + net.WriteEntity(ply) + net.Send(ply) + end + end + + MQS.SendDataToClien({index = "none", data = ply.MQSdata, isplayerdata = ply:UserID()}, ply) + + ply.MQSgotData = true + + if MQS.Config.IntoQuestAutogive and MQS.CanPlayIntro(ply) then + MQS.StartTask(MQS.Config.IntoQuest, ply, nil, true) + end +end) + +net.Receive("MQS.DataShare", function(l, ply) + MQS.DataShare(ply, true) +end) + +hook.Add("PlayerInitialSpawn", "MQS.PlayerInitialSpawn", function(ply) + + if not ply.MQSdata then ply.MQSdata = {} end + + if not ply.MQSdata.Stored then ply.MQSdata.Stored = {initdata = 0} end + + local sid = MQS.DB.Escape(ply:SteamID()) + + MQS.DB.Query("SELECT * FROM mqs_player WHERE id=" .. sid, function(result) + if result ~= nil and #result > 0 then + + local tbl = util.JSONToTable(result[1].value) + + if tbl and istable(tbl) then + print("[MQS] Player's stored data loaded") + ply.MQSdata.Stored = tbl + end + else + MQS.DB.Query("INSERT INTO mqs_player VALUES(" .. sid .. ", '[]')") + print("[MQS] No player stored data, creating one") + end + end) +end) \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/sh_config.lua b/addons/mc_quests/lua/mqs/sh_config.lua new file mode 100644 index 0000000..7a2f942 --- /dev/null +++ b/addons/mc_quests/lua/mqs/sh_config.lua @@ -0,0 +1,196 @@ +-- ╔══╦════╦════╦═══╦╗─╔╦════╦══╦══╦╗─╔╦╦╦╗ +-- ║╔╗╠═╗╔═╩═╗╔═╣╔══╣╚═╝╠═╗╔═╩╗╔╣╔╗║╚═╝║║║║ +-- ║╚╝║─║║───║║─║╚══╣╔╗─║─║║──║║║║║║╔╗─║║║║ +-- ║╔╗║─║║───║║─║╔══╣║╚╗║─║║──║║║║║║║╚╗╠╩╩╝ +-- ║║║║─║║───║║─║╚══╣║─║║─║║─╔╝╚╣╚╝║║─║╠╦╦╗ +-- ╚╝╚╝─╚╝───╚╝─╚═══╩╝─╚╝─╚╝─╚══╩══╩╝─╚╩╩╩╝ + +-- Master Admins list is used to give a player full access despite his user group +MQS.MasterAdmins = { + --["STEAM_0:0:27976260"] = true, +} + +-- You can edit the config throw the game!!! +-- Just use admin menu ingame +MQS.Config.Administrators = { + ["owner"] = true, + ["superadmin"] = true, +} + +MQS.Config.Editors = { + ["admin"] = true, +} + +MQS.Config.QuestEntDrawDist = 500 +MQS.Config.hudPos = 1 +MQS.Config.StopKey = KEY_P +MQS.Config.Sort = false +MQS.Config.SmallObj = false +MQS.Config.CamFix = false +MQS.Config.IntoQuest = "" +MQS.Config.IntoQuestAutogive = false +MQS.Config.UI = {} +MQS.Config.UI.Blur = false +MQS.Config.UI.Vignette = false +MQS.Config.UI.BgrColor = Color(45, 45, 45) +MQS.Config.UI.HudAlignX = false +MQS.Config.UI.HudAlignY = false +MQS.Config.UI.HudOffsetX = 0 +MQS.Config.UI.HudOffsetY = 0 +MQS.Config.UI.HUDBG = 1 +MQS.Config.NPC = {} +MQS.Config.NPC.enable = false +MQS.Config.NPC.list = {} + +--──────────────────────────────────-- +------------- CFG Saving ------------- +--──────────────────────────────────-- +local requested = {} + +net.Receive("MQS.GetConfigData", function(l, ply) + if CLIENT then + local config = net.ReadTable() + MQS.Config = config + + if MQS.UpdateMenuElements then + MQS.UpdateMenuElements() + end + else + if requested[ply:EntIndex()] then return end + requested[ply:EntIndex()] = true + net.Start("MQS.GetConfigData") + net.WriteTable(MQS.Config) + net.Send(ply) + end +end) + +if CLIENT then + net.Start("MQS.GetConfigData") + net.SendToServer() + + function MQS.SaveConfig() + MSD.SaveConfig() + local cd, bn = MQS.TableCompress(MQS.Config) + + net.Start("MQS.SaveConfig") + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() + end +end + +function MQS.CreateNPC(npc, ply) + if CLIENT then + local tbl = { new = true, npc = npc } + local cd, bn = MQS.TableCompress(tbl) + net.Start("MQS.UpdateNPC") + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() + else + if ply then + MQS.TaskNotify(ply, "NPC Created", 3) + end + + table.insert(MQS.Config.NPC.list, npc) + MQS.SaveConfig(MQS.Config) + end +end + +function MQS.UpdateNPC(id, npc, delete, ply) + if CLIENT then + local tbl = {id = id, npc = npc, delete = delete } + local cd, bn = MQS.TableCompress(tbl) + net.Start("MQS.UpdateNPC") + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() + else + if not MQS.Config.NPC.list[id] then return end + + if delete then + table.remove(MQS.Config.NPC.list, id) + + if ply then + MQS.TaskNotify(ply, "NPC Removed", 2) + end + else + MQS.Config.NPC.list[id] = npc + + if ply then + MQS.TaskNotify(ply, "NPC Updated", 3) + end + end + + MQS.SaveConfig(MQS.Config) + MQS.SpawnNPCs() + end +end + +if SERVER then + local id_v = "haha, no" + + net.Receive("MQS.UpdateNPC", function(l, ply) + if not MQS.IsAdministrator(ply) then return end + if MQS.cfgLastChange and MQS.cfgLastChange > CurTime() then return end + MQS.cfgLastChange = CurTime() + 1 + + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local data = MQS.TableDecompress(compressed_data) + + if not data.npc then return end + if data.new then MQS.CreateNPC(data.npc, ply) return end + if not data.id then return end + + MQS.UpdateNPC(data.id, data.npc, data.delete, ply) + end) + + net.Receive("MQS.SaveConfig", function(l, ply) + if not MQS.IsAdministrator(ply) then return end + if MQS.cfgLastChange and MQS.cfgLastChange > CurTime() then return end + MQS.cfgLastChange = CurTime() + 1 + + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local config = MQS.TableDecompress(compressed_data) + MQS.SaveConfig(config) + end) + + function MQS.SaveConfig(config) + MQS.Config = config + MQS.Config.id_v = id_v + requested = {} + net.Start("MQS.GetConfigData") + net.WriteTable(config) + net.Broadcast() + json_table = util.TableToJSON(config, true) + file.Write(MQS.ServerID .. "/mqs_config.txt", json_table) + MQS.SpawnNPCs() + end + + if not file.Exists(MQS.ServerID .. "/mqs_config.txt", "DATA") then + json_table = util.TableToJSON(MQS.Config, true) + file.Write(MQS.ServerID .. "/mqs_config.txt", json_table) + else + local config = util.JSONToTable(file.Read(MQS.ServerID .. "/mqs_config.txt", "DATA")) + + for k, v in pairs(config) do + if MQS.Config[k] != nil then + MQS.Config[k] = v + end + end + + if #player.GetAll() > 0 then + net.Start("MQS.GetConfigData") + net.WriteTable(config) + net.Broadcast() + end + end + + hook.Call("MQS.Hook.PostConfigLoad", nil) + + hook.Add("PlayerDisconnected", "MQS.RemoveJunk", function(ply) + requested[ply:EntIndex()] = nil + end) +end \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/ui/editor_panel.lua b/addons/mc_quests/lua/mqs/ui/editor_panel.lua new file mode 100644 index 0000000..a4469f1 --- /dev/null +++ b/addons/mc_quests/lua/mqs/ui/editor_panel.lua @@ -0,0 +1,1824 @@ +local Ln = MSD.GetPhrase +local mouleid = 0 + +local blank_quest = { + name = Ln("q_new"), + desc = "", + success = "", + active = false, + new = true, +} + +local blank_quest_new = table.Copy(blank_quest) + +local blank_npc = { + name = Ln("npc_new"), + model = "models/Humans/Group01/Male_0" .. math.random(1, 9) .. ".mdl", + text = "", + text_notask = "", + answer_yes = "", + answer_no = "", + answer_notask = "", + skin = 0, + bgr = {}, + spawns = {}, +} + +local active_quest +local last_sm = "ui" + +if not file.Exists("mqs", "DATA") then + file.CreateDir("mqs") +end + +local function AutoSave() + if not active_quest or not active_quest.new then return end + file.Write("mqs/autosave.txt", util.TableToJSON(active_quest)) +end + +local function LoadAutoSave() + if file.Exists("mqs/autosave.txt", "DATA") then + local jsontable = file.Read("mqs/autosave.txt", "DATA") + blank_quest = util.JSONToTable(jsontable) + active_quest = blank_quest + end +end + +local function QuestListPanel(parent, k, v, openPage) + local qpnl = vgui.Create("DPanel") + qpnl.StaticScale = { + w = 4, + fixed_h = 120, + minw = 200, + minh = 120 + } + + qpnl.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + draw.DrawText(v.name, "MSDFont.25", 10, 10, color_white, TEXT_ALIGN_LEFT) + local active = MQS.Config.NPC.enable and v.link or v.active + MSD.DrawTexturedRect(w - 32, h - 32, 32, 32, MSD.Icons48.dot, active and MSD.Config.MainColor["p"] or MSD.Text.n) + draw.DrawText(Ln(active and "active" or "inactive"), "MSDFont.16", w - 28, h - 25, MSD.Text.d, TEXT_ALIGN_RIGHT) + end + + local bpnl = vgui.Create("MSDPanelList", qpnl) + bpnl:SetSize(200, 36) + bpnl:DockMargin(5, 5, 5, 5) + bpnl:SetSpacing(2) + bpnl:EnableHorizontal(true) + bpnl:Dock(BOTTOM) + + MSD.IconButtonBG(bpnl, MSD.Icons48.play, nil, nil, 36, nil, MSD.Config.MainColor.p, function() + RunConsoleCommand("mqs_start", k) + end) + + MSD.IconButtonBG(bpnl, MSD.Icons48.pencil, nil, nil, 36, nil, MSD.Config.MainColor.p, function() + openPage("edit", true, true, k, v) + end) + + parent:AddItem(qpnl) +end + +function MQS.OpenMenuManager(parrent, editor, npc) + if MQS.IsEditor(LocalPlayer()) then + MSD.OpenMenuManager(nil, mouleid) + elseif not editor then + if MQS.Config.NPC.enable and not npc then + local found = false + + for _, e in ipairs(ents.FindByClass("mqs_npc")) do + if IsValid(e) then + found = true + break + end + end + + MQS.DoHint(found and Ln("q_get") or Ln("q_noquests"), found and 4 or 2) + else + MQS.OpenPlayerMenu(parrent) + end + else + MQS.SmallNotify(Ln("need_admin"), nil, 1) + end +end + +net.Receive("MQS.GetPlayersQuests", function() + if not IsValid(MQS.PlayerDataPanel) then return end + local sid = net.ReadString() + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local data = MQS.TableDecompress(compressed_data) + MQS.PlayerDataPanel.Update(data, sid) +end) + +net.Receive("MQS.OpenEditor", function() + MQS.OpenMenuManager() +end) + +concommand.Add("mqs_editor", function(pl, cmd, args) + MQS.OpenMenuManager() +end) + +function MQS.NPCAnimationMenu(parent, ent, setanim) + local mx, my = gui.MousePos() + local frame = vgui.Create("DFrame") + frame:SetSize(300, 400) + frame:SetPos(mx - 300, my - 400) + frame:SetDraggable(true) + frame:ShowCloseButton(true) + frame:MakePopup() + frame:SetTitle("Animations List") + frame.StartT = CurTime() + 2 + + frame.Think = function(self) + if not IsValid(parent) or (not self:HasFocus() and self.StartT < CurTime()) then + self:Close() + end + end + + local AnimList = vgui.Create("DListView", frame) + AnimList:AddColumn("name") + AnimList:Dock(FILL) + AnimList:SetMultiSelect(false) + AnimList:SetHideHeaders(true) + + for k, v in SortedPairsByValue(ent:GetSequenceList() or {}) do + local line = AnimList:AddLine(string.lower(v)) + + line.OnSelect = function() + setanim(v) + end + end + + return frame +end + +function MQS.OpenAdminMenu(panel, mainPanel) + + if not panel then return end + MQS.SetupMenu = panel + + function mainPanel.ModuleSwitch() + MQS.SetupMenu = nil + MQS.UpdateMenuElements = nil + AutoSave() + end + + panel.Canvas = vgui.Create("MSDPanelList", panel) + panel.Canvas:SetSize(panel:GetWide() - 252, panel:GetTall()) + panel.Canvas:SetPos(252, 0) + panel.Canvas:EnableVerticalScrollbar() + panel.Canvas:EnableHorizontal(true) + panel.Canvas:SetSpacing(2) + panel.Canvas.IgnoreVbar = true + panel.Canvas.Paint = function() end + local pages = {} + local cur_page = nil + + local function openPage(id, animate, ...) + panel.Canvas:Clear() + back_page = cur_page + pages[id](...) + cur_page = id + + if animate then + panel.Canvas:SetAlpha(1) + panel.Canvas:AlphaTo(255, 0.2) + end + end + + panel.OnQuestUpdate = function(qid) + if cur_page == "quests" then + openPage("quests", true) + + return + end + + if active_quest and active_quest.id == qid then + openPage("quests", true) + + return + end + end + + local buttons = {} + + buttons[1] = { + Ln("quests"), MSD.Icons48.layers, function() + openPage("quests", true) + end, + true + } + if MQS.IsAdministrator(LocalPlayer()) then + buttons[2] = { + Ln("npc_editor"), MSD.Icons48.account_multiple, function() + openPage("npcs", true) + end + } + buttons[3] = { + Ln("settings"), MSD.Icons48.cog, function() + openPage("settings", true) + end + } + end + + panel.Menu = vgui.Create("MSDPanelList", panel) + panel.Menu:SetSize(250, panel:GetTall()) + panel.Menu:SetPos(0, 0) + panel.Menu:EnableVerticalScrollbar() + panel.Menu:EnableHorizontal(false) + panel.Menu:SetSpacing(0) + panel.Menu.IgnoreVbar = true + + panel.Menu.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["l"]) + end + + panel.Menu.Deselect = function(but) + if not but then return end + but.hovered = true + + for k, v in pairs(panel.Menu:GetItems()) do + if v and v:IsValid() and v ~= but then + v.hovered = false + end + end + end + + local function PopupMenu(text, x, y, w, dwd) + local pl, child = MSD.WorkSpacePanel(mainPanel, text, x, y, false) + local sub_list = vgui.Create("MSDPanelList", child) + sub_list:SetSize(dwd and child:GetWide() / dwd or child:GetWide() - 10, child:GetTall() - w) + sub_list:SetPos(5, w) + sub_list:EnableVerticalScrollbar() + sub_list:EnableHorizontal(true) + sub_list:SetSpacing(2) + sub_list.IgnoreVbar = true + + return sub_list, child, pl + end + + pages["quests"] = function() + if MQS.Config.Sort then + local catgr = {} + MSD.Header(panel.Canvas, Ln("unsorted"), function() + openPage("search") + end, MSD.Icons48.search) + + MSD.BigButton(panel.Canvas, "static", nil, 4, 120, Ln("q_addnew"), MSD.Icons48.layers_plus, function() + openPage("edit", true, true, true) + end) + + for k, v in pairs(MQS.Quests) do + if not v.category then + QuestListPanel(panel.Canvas, k, v, openPage) + continue + end + if catgr[v.category] then + catgr[v.category][k] = true + else + catgr[v.category] = { [k] = true } + end + end + + for name, qlist in pairs(catgr) do + MSD.Header(panel.Canvas, name) + for k, _ in pairs(qlist) do + QuestListPanel(panel.Canvas, k, MQS.Quests[k], openPage) + end + end + else + MSD.Header(panel.Canvas, Ln("quest_list"), function() + openPage("search") + end, MSD.Icons48.search) + + MSD.BigButton(panel.Canvas, "static", nil, 4, 120, Ln("q_addnew"), MSD.Icons48.layers_plus, function() + openPage("edit", true, true, true) + end) + + for k, v in pairs(MQS.Quests) do + QuestListPanel(panel.Canvas, k, v, openPage) + end + end + end + + pages["search"] = function() + local obj_sets + + MSD.Header(panel.Canvas, Ln("search"), function() + openPage("quests") + end) + + MSD.TextEntry(panel.Canvas, "static", nil, 1, 50, Ln("enter_name") .. "/ID", Ln("search_q") .. ":", "", function(self, value) + obj_sets.Update(value) + end, true) + + obj_sets = vgui.Create("MSDPanelList") + obj_sets:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 110) + obj_sets:EnableVerticalScrollbar() + obj_sets:EnableHorizontal(true) + obj_sets:SetSpacing(2) + obj_sets:SetPadding(0) + obj_sets.IgnoreVbar = true + obj_sets.Update = function(value) + obj_sets:Clear() + for k, v in pairs(MQS.Quests) do + if not value then continue end + if not string.find(string.lower(k), string.lower(value), 1, true) and not string.find(string.lower(v.name), string.lower(value), 1, true) then continue end + QuestListPanel(obj_sets, k, v, openPage) + end + end + obj_sets.Update("") + panel.Canvas:AddItem(obj_sets) + end + + pages["edit"] = function(open, id, quest) + if open then + if not quest and id == true then + quest = blank_quest + id = blank_quest.id or "new_quest" + + if not quest.edited then + local pl, _, plm = PopupMenu(Ln("load_autosave"), 3, 5, 55) + + MSD.Button(pl, "static", nil, 1, 50, Ln("load_save"), function() + LoadAutoSave() + plm.Close() + end) + + MSD.Button(pl, "static", nil, 1, 50, Ln("create_new"), function() + active_quest = table.Copy(blank_quest_new) + active_quest.id = "new_quest" + plm.Close() + end) + end + quest.edited = true + end + active_quest = quest + active_quest.id = id + end + + MSD.Header(panel.Canvas, Ln("quest_editor"), function() + openPage("quests", true) + AutoSave() + end) + + MSD.BigButton(panel.Canvas, "static", nil, 3, 180, Ln("main_opt"), MSD.Icons48.playlist_edit, function() + openPage("edit_page1", true) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 3, 180, Ln("q_editobj"), MSD.Icons48.calendar_check, function() + openPage("edit_page2", true) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 3, 180, Ln("q_editrwd"), MSD.Icons48.seal, function() + openPage("edit_page3", true) + end) + + if active_quest.new then + MSD.BigButton(panel.Canvas, "static", nil, 3, 100, Ln("q_submit"), MSD.Icons48.submit, function() + if MQS.Quests[active_quest.id] then + local pl, _, plm = PopupMenu(Ln("q_id_unique"), 3, 5, 55) + + MSD.Button(pl, "static", nil, 1, 50, Ln("ok"), function() + plm.Close() + end) + return + end + openPage("quests", true) + active_quest.temp_data = nil + active_quest.edited = nil + + MQS.QuestSubmit(active_quest) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 3, 100, Ln("copy_data"), MSD.Icons48.copy, function() + openPage("quests_copy", true) + end) + else + MSD.BigButton(panel.Canvas, "static", nil, 3, 100, Ln("save_chng"), MSD.Icons48.save, function() + active_quest.temp_data = nil + openPage("quests", true) + MQS.QuestSubmit(active_quest) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 3, 100, Ln("remove"), MSD.Icons48.layers_remove, function() + local pl, _, plm = PopupMenu(Ln("confirm_action"), 3, 5, 55) + + MSD.Button(pl, "static", nil, 1, 50, Ln("q_remove"), function() + openPage("quests", true) + net.Start("MQS.QuestRemove") + net.WriteString(active_quest.id) + net.SendToServer() + plm.Close() + end) + + MSD.Button(pl, "static", nil, 1, 50, Ln("cancel"), function() + plm.Close() + end) + end) + end + + MSD.BigButton(panel.Canvas, "static", nil, 3, 100, Ln("check_fpr_errors"), MSD.Icons48.alert, function() + MQS.PreCheckQuest(active_quest, PopupMenu) + end) + end + + pages["edit_page1"] = function() + MSD.Header(panel.Canvas, Ln("main_opt"), function() + openPage("edit", true) + end) + + MSD.TextEntry(panel.Canvas, "static", nil, MQS.Config.Sort and 3 or 2, 50, Ln("enter_name"), Ln("name") .. ":", active_quest.name, function(self, value) + active_quest.name = value + end, true) + + if not active_quest.oldid then + active_quest.oldid = active_quest.id + end + + MSD.TextEntry(panel.Canvas, "static", nil, MQS.Config.Sort and 3 or 2, 50, Ln("enter_id"), "ID:", active_quest.id, function(self, value) + if not MQS.CheckID(value) then + self.error = Ln("inv_quest") .. " ID" + return + end + + if MQS.Quests[value] and active_quest.oldid ~= value then + self.error = Ln("q_id_unique") + return + end + + active_quest.id = value + self.error = nil + end, true) + + if MQS.Config.Sort then + MSD.TextEntry(panel.Canvas, "static", nil, 3, 50, Ln("category_des"), Ln("category"), active_quest.category, function(self, value) + active_quest.category = value ~= "" and value or nil + end, true) + end + + MSD.TextEntry(panel.Canvas, "static", nil, 1, 200, Ln("enter_description"), nil, active_quest.desc, function(self, value) + active_quest.desc = value + end, true, nil, true) + + MSD.TextEntry(panel.Canvas, "static", nil, 1, 50, Ln("e_text"), Ln("q_complete_msg") .. ":", active_quest.success, function(self, value) + active_quest.success = value + end, true) + + local sld1, sld2 + + MSD.TextEntry(panel.Canvas, "static", nil, 3, 50, Ln("e_blank_dis"), Ln("q_dotime") .. "(" .. Ln("in_sec") .. "):", active_quest.do_time, function(self, value) + active_quest.do_time = tonumber(value) or nil + + if not active_quest.do_time then + sld1.disabled = true + else + sld1.disabled = nil + end + end, true, nil, nil, true) + + MSD.DTextSlider(panel.Canvas, "static", nil, 3, 50, Ln("q_dotime_ok"), Ln("q_dotime_fail"), active_quest.reward_on_time, function(self, value) + active_quest.reward_on_time = value + end) + + MSD.BoolSlider(panel.Canvas, "static", nil, 3, 50, Ln("q_stop_anytime"), active_quest.stop_anytime, function(self, value) + active_quest.stop_anytime = value + end) + + sld1 = MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, Ln("q_loop"), active_quest.looped, function(self, value) + active_quest.looped = value + sld2.disabled = not value + end) + + if not active_quest.do_time then + sld1.disabled = true + else + sld1.disabled = nil + end + + sld2 = MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, Ln("q_loop_reward"), active_quest.reward_ower_loop, function(self, value) + active_quest.reward_ower_loop = value + end) + + sld2.disabled = not active_quest.looped + + MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, Ln("q_death_fail"), active_quest.fail_ondeath, function(self, value) + active_quest.fail_ondeath = value + end) + + MSD.DTextSlider(panel.Canvas, "static", nil, 2, 50, Ln("q_cooldow_publick"), Ln("q_cooldow_perply"), active_quest.cooldow_perply, function(self, value) + active_quest.cooldow_perply = value + end) + + MSD.TextEntry(panel.Canvas, "static", nil, 2, 50, Ln("e_blank_dis"), Ln("cooldown_ok") .. "(" .. Ln("in_sec") .. "):", active_quest.cool_down, function(self, value) + active_quest.cool_down = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.TextEntry(panel.Canvas, "static", nil, 2, 50, Ln("e_blank_dis"), Ln("cooldown_fail") .. "(" .. Ln("in_sec") .. "):", active_quest.cool_down_onfail, function(self, value) + active_quest.cool_down_onfail = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.TextEntry(panel.Canvas, "static", nil, 3, 50, Ln("e_blank_dis"), Ln("q_ply_limit") .. ":", active_quest.limit, function(self, value) + active_quest.limit = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.TextEntry(panel.Canvas, "static", nil, 3, 50, Ln("e_blank_dis"), Ln("q_ply_need") .. ":", active_quest.need_players, function(self, value) + active_quest.need_players = tonumber(value) or nil + end, true, nil, nil, true) + + MSD.Button(panel.Canvas, "static", nil, 3, 50, Ln("q_ply_team_limit"), function() + local sub_list, child = PopupMenu(Ln("q_ply_team_need"), 2, 1.2, 102) + local update + + MSD.BoolSlider(child, 5, 50, child:GetWide() - 10, 50, Ln("enable_option"), active_quest.need_teamplayers and true or false, function(self, var) + if var then + active_quest.need_teamplayers = {} + else + active_quest.need_teamplayers = nil + end + + update() + end) + + update = function() + if not sub_list then return end + sub_list:Clear() + if not active_quest.need_teamplayers then return end + MSD.InfoHeader(sub_list, Ln("e_blank_dis")) + + for tid, tm in SortedPairsByMemberValue(team.GetAllTeams(), "Name", true) do + if not tm.Joinable then continue end + + MSD.TextEntry(sub_list, "static", nil, 2, 50, "", tm.Name, active_quest.need_teamplayers[tid], function(self, val) + active_quest.need_teamplayers[tid] = val ~= "" and val or nil + end, true, nil, nil, true) + end + end + + update() + end) + + local combo = MSD.ComboBox(panel.Canvas, "static", nil, 2, 50, Ln("q_needquest_menu") .. ":", Ln("none")) + combo:AddChoice(Ln("none")) + + combo.OnSelect = function(self, index, text, data) + if text == Ln("none") then + active_quest.quest_needed = nil + return + end + if active_quest.quest_blacklist and active_quest.quest_blacklist[data] then return end + active_quest.quest_needed = data + end + + for k, v in pairs(MQS.Quests) do + if active_quest.id == k then continue end + if v.quest_needed and v.quest_needed == active_quest.id then continue end + if v.looped then continue end + + if active_quest.quest_needed == k then + combo:SetValue(v.name) + end + + combo:AddChoice(v.name, k) + end + + MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, Ln("q_dis_replay"), active_quest.cant_replay, function(self, value) + active_quest.cant_replay = value + end) + + MSD.Button(panel.Canvas, "static", nil, 2, 50, Ln("s_team_whitelist"), function() + local sub_list, child = PopupMenu(Ln("s_team_whitelist"), 2, 1.2, 102) + local update + + MSD.BoolSlider(child, 5, 50, child:GetWide() - 10, 50, Ln("enable_option"), active_quest.team_whitelist and true or false, function(self, var) + if var then + active_quest.team_whitelist = {} + else + active_quest.team_whitelist = nil + end + + update() + end) + + update = function() + if not sub_list then return end + sub_list:Clear() + if not active_quest.team_whitelist then return end + + for id, tm in SortedPairsByMemberValue(team.GetAllTeams(), "Name", true) do + if not tm.Joinable then continue end + + MSD.BoolSlider(sub_list, "static", nil, 2, 50, tm.Name, active_quest.team_whitelist[id], function(self, var) + active_quest.team_whitelist[id] = var + end) + end + end + + update() + end) + + MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, Ln("whitelist_blacklist"), active_quest.team_blacklist, function(self, value) + active_quest.team_blacklist = value + end) + + if MQS.Config.NPC.enable then + local npclist = MSD.ComboBox(panel.Canvas, "static", nil, 2, 50, Ln("q_npc_link") .. ":", Ln("none")) + npclist:AddChoice(Ln("none")) + + npclist.OnSelect = function(self, index, text, data) + if text == Ln("none") then + active_quest.link = nil + + return + end + + active_quest.link = data + end + + for k, v in pairs(MQS.Config.NPC.list) do + if not istable(active_quest.link) and active_quest.link == k then + npclist:SetValue(v.name) + end + + npclist:AddChoice(v.name, k) + end + + if MCS then + for k, v in pairs(MCS.Spawns) do + if istable(active_quest.link) and active_quest.link.id == k then + npclist:SetValue("[MCS] " .. v.name) + end + + if not v.questNPC then continue end + + npclist:AddChoice("[MCS] " .. v.name, { + id = k, + base = "mcs" + }) + end + end + elseif MQS.IsAdministrator(LocalPlayer()) then + MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, Ln("q_enable"), active_quest.active or false, function(self, value) + active_quest.active = value + end) + end + + MSD.Button(panel.Canvas, "static", nil, 2, 50, Ln("custom_icon"), function() + local sub_list, child = PopupMenu(Ln("custom_icon"), 2, 3, 102) + local update + + MSD.BoolSlider(child, 5, 50, child:GetWide() - 10, 50, Ln("enable_option"), active_quest.custom_icon and true or false, function(self, var) + if var then + active_quest.custom_icon = "" + else + active_quest.custom_icon = nil + end + + update() + end) + + update = function() + if not sub_list then return end + sub_list:Clear() + if not active_quest.custom_icon then return end + + MSD.TextEntry(sub_list, "static", nil, 1, 50, Ln("q_icon68"), Ln("e_url"), active_quest.custom_icon, function(self, val) + if val ~= "" then + active_quest.custom_icon = val + end + end, true) + + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(sub_list:GetWide(), sub_list:GetTall() - 50) + + ops_panel.Paint = function(self, w, h) + MSD.DrawTexturedRect(5, 5, 68, 68, MSD.ImgLib.GetMaterial(active_quest.custom_icon), color_white) + end + + sub_list:AddItem(ops_panel) + end + + update() + end) + + local update_bl = function(upd, q_list, b_list) + q_list:Clear() + b_list:Clear() + + if not upd then return end + + MSD.InfoHeader(q_list, Ln("quest_list")) + MSD.InfoHeader(b_list, Ln("blacklist")) + for k, v in pairs(MQS.Quests) do + if active_quest.id == k then continue end + if active_quest.quest_needed == k then continue end + + local sts = active_quest.quest_blacklist[k] + local paretnt = sts and b_list or q_list + MSD.Button(paretnt, "static", nil, 1, 50, v.name, function() + if sts then + active_quest.quest_blacklist[k] = nil + else + active_quest.quest_blacklist[k] = true + end + update_bl(upd) + end) + end + end + + MSD.Button(panel.Canvas, "static", nil, 1, 50, Ln("s_quest_blacklist"), function() + local _, child = MSD.WorkSpacePanel(mainPanel, Ln("s_quest_blacklist_desc"), 1, 1.2, false) + + local q_list = vgui.Create("MSDPanelList", child) + q_list:SetSize(child:GetWide() / 2 - 15, child:GetTall() - 102) + q_list:SetPos(5, 102) + q_list:EnableVerticalScrollbar() + q_list:EnableHorizontal(true) + q_list:SetSpacing(2) + q_list.IgnoreVbar = true + + local b_list = vgui.Create("MSDPanelList", child) + b_list:SetSize(child:GetWide() / 2 - 15, child:GetTall() - 102) + b_list:SetPos(10 + child:GetWide() / 2 - 15, 102) + b_list:EnableVerticalScrollbar() + b_list:EnableHorizontal(true) + b_list:SetSpacing(2) + b_list.IgnoreVbar = true + + local status = active_quest.quest_blacklist and true or false + MSD.BoolSlider(child, 5, 50, child:GetWide() - 10, 50, Ln("enable_option"), status, function(self, var) + if var then + active_quest.quest_blacklist = {} + else + active_quest.quest_blacklist = nil + end + + update_bl(var, q_list, b_list) + end) + update_bl(status, q_list, b_list) + end) + end + + pages["edit_page2"] = function() + local SwapEvents = false + + MSD.Header(panel.Canvas, Ln("q_editobj"), function() + openPage("edit", true) + end) + + if not active_quest.objects then active_quest.objects = {} end + + local ccl = MQS.Config.SmallObj + local obj_panels = {} + for t_id, object in pairs(active_quest.objects) do + if not MSD.ObjeciveList[object.type] then + MSD.BigButton(panel.Canvas, "static", nil, ccl and 6 or 4, ccl and 80 or 120, Ln("q_incvobj"), MSD.Icons48.alert, function() + table.remove(active_quest.objects, t_id) + openPage("edit_page2", true) + end, MSD.Config.MainColor["r"]) + continue + end + + obj_panels[t_id] = MSD.BigButton(panel.Canvas, "static", nil, ccl and 6 or 4, ccl and 80 or 120, object.type, MSD.ObjeciveList[object.type].icon, function() + if SwapEvents then + local new_object = table.Copy(active_quest.objects[SwapEvents]) + active_quest.objects[SwapEvents] = object + active_quest.objects[t_id] = new_object + openPage("edit_page2", true) + return + end + + if not MSD.ObjeciveList[object.type].builUI then return end + + panel.Canvas:Clear() + + openPage("edit_objective", true, t_id, object) + + end, nil, t_id, function(self) + + if SwapEvents then return end + + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + + if t_id > 1 then + self.Menu:AddOption(Ln("moveup"), function() + local new_object = table.Copy(active_quest.objects[t_id - 1]) + active_quest.objects[t_id - 1] = object + active_quest.objects[t_id] = new_object + openPage("edit_page2", true) + end) + end + + if t_id ~= #active_quest.objects then + self.Menu:AddOption(Ln("movedown"), function() + local new_object = table.Copy(active_quest.objects[t_id + 1]) + active_quest.objects[t_id + 1] = object + active_quest.objects[t_id] = new_object + openPage("edit_page2", true) + end) + end + + self.Menu:AddOption(Ln("swap"), function() + local hd = MSD.BigButton(panel.Canvas, "static", nil, 1, 80, Ln("swapmod"), MSD.Icons48.swap, function() + openPage("edit_page2", true) + end) + + panel.Canvas:InsertAtTop(hd) + SwapEvents = t_id + self.disable = true + self.color_idle = MSD.Config.MainColor["r"] + end) + + self.Menu:AddOption(Ln("duplicate"), function() + table.insert(active_quest.objects, table.Copy(object)) + openPage("edit_page2", true) + end) + + self.Menu:AddOption(Ln("editmod"), function() + openPage("edit_objmod", true) + end) + + self.Menu:AddOption(Ln("remove"), function() + table.remove(active_quest.objects, t_id) + openPage("edit_page2", true) + end) + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end, (object.desc ~= object.type and not ccl) and object.desc or "", function(s, wd, hd) + if object.type == "Skip to" and s.hover and object.oid then + if not IsValid(obj_panels[object.oid]) then return end + obj_panels[object.oid].hl = true + return + end + + if s.hl then + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Text["a"]) + s.hl = nil + end + + if object.type ~= "Randomize" or not s.hover or not object.objects then return end + for k,v in pairs(object.objects) do + if not IsValid(obj_panels[k]) then continue end + obj_panels[k].hl = v + end + end) + end + + MSD.BigButton(panel.Canvas, "static", nil, ccl and 6 or 4, ccl and 80 or 120, Ln("q_newobj"), MSD.Icons48.plus, function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + + for k, v in pairs(MSD.ObjeciveList) do + if v.check and not v.check() then continue end + if k == "Collect quest ents" and active_quest.objects and active_quest.objects[#active_quest.objects] and active_quest.objects[#active_quest.objects].type == k then continue end + + self.Menu:AddOption(Ln(k), function() + if not active_quest.objects then + active_quest.objects = {} + end + + local ix = table.insert(active_quest.objects, table.Copy(v.tbl)) + active_quest.objects[ix].desc = k + active_quest.objects[ix].type = k + + if active_quest.objects[ix].point then + active_quest.objects[ix].point = LocalPlayer():GetPos() + end + + openPage("edit_page2", true) + end, v.icon) + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + end + + pages["edit_objmod"] = function() + + MSD.Header(panel.Canvas, Ln("edit_objmod"), function() + openPage("edit_page2", true) + end) + + local ccl = MQS.Config.SmallObj + local qstart = nil + local qend = nil + + local function copy_obj() + local count = (qend - qstart) + for i = 0,count do + table.insert(active_quest.objects, table.Copy(active_quest.objects[qstart + i])) + end + openPage("edit_objmod", false) + end + + local function remove_obj() + local count = (qend - qstart) + for i = 0,count do + table.remove(active_quest.objects, qstart) + end + openPage("edit_objmod", false) + end + + local function move_obj_to(t_id) + local count = (qend - qstart) + local temp_t = {} + for i = 0,count do + table.insert(temp_t, table.Copy(active_quest.objects[qstart + i])) + end + for i = 0,count do + table.remove(active_quest.objects, qstart) + end + local add = t_id > qend and count or 0 + for i = 0,count do + table.insert(active_quest.objects, t_id + i - add, table.Copy(temp_t[1 + i])) + end + openPage("edit_objmod", false) + end + + for t_id, object in pairs(active_quest.objects) do + local valid = MSD.ObjeciveList[object.type] + + MSD.BigButton(panel.Canvas, "static", nil, ccl and 6 or 4, ccl and 80 or 120, object.type, valid and valid.icon or MSD.Icons48.alert, function() + if not qstart or not qend then + qstart = t_id + qend = t_id + return + end + + if t_id > qend then + qend = t_id + return + end + + if t_id < qstart then + qstart = t_id + return + end + + qstart = nil + qend = nil + end, nil, t_id, function(self) + if not qstart or not qend then return end + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + + if t_id >= qstart and t_id <= qend then + self.Menu:AddOption(Ln("duplicate"), function() + copy_obj() + end) + self.Menu:AddOption(Ln("remove"), function() + remove_obj() + end) + else + self.Menu:AddOption(Ln("move"), function() + move_obj_to(t_id) + end) + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end, (object.desc ~= object.type and not ccl) and object.desc or "", function(s, wd, hd) + if qstart and qend and t_id >= qstart and t_id <= qend then + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Text["a"]) + end + end) + end + end + + pages["edit_objective"] = function(t_id, object) + + if not object.events then object.events = {} end + + MSD.Header(panel.Canvas, Ln(object.type), function() openPage("edit_page2", true) end) + + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 52) + + ops_panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.RoundedBox(MSD.Config.Rounded, w / 2, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.DrawText(Ln("q_setobj"), "MSDFont.25", 12, 12, color_white, TEXT_ALIGN_LEFT) + draw.DrawText(Ln("q_events"), "MSDFont.25", w / 2 + 12, 12, color_white, TEXT_ALIGN_LEFT) + end + + local obj_sets = vgui.Create("MSDPanelList", ops_panel) + obj_sets:SetSize(ops_panel:GetWide() / 2, ops_panel:GetTall() - 52) + obj_sets:SetPos(0, 52) + obj_sets:EnableVerticalScrollbar() + obj_sets:EnableHorizontal(true) + obj_sets:SetSpacing(2) + obj_sets:SetPadding(0) + obj_sets.IgnoreVbar = true + + obj_sets.Populate = function() + obj_sets:Clear() + MSD.ObjeciveList[object.type].builUI(t_id, object, obj_sets, PopupMenu, active_quest) + end + + local obj_events = vgui.Create("MSDPanelList", ops_panel) + obj_events:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall() - 52) + obj_events:SetPos(ops_panel:GetWide() / 2, 52) + obj_events:EnableVerticalScrollbar() + obj_events:EnableHorizontal(true) + obj_events:SetSpacing(2) + obj_events:SetPadding(0) + obj_events.IgnoreVbar = true + + obj_events.Populate = function() + obj_events:Clear() + + MSD.Button(obj_events, "static", nil, 1, 50, Ln("q_eventadd"), function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self, false) + + for k, v in pairs(MSD.EventList) do + if v.check and ((isstring(v.check) and v.check ~= object.type) or (isfunction(v.check) and v.check())) then continue end + + self.Menu:AddOption(Ln(k), function() + + local ix = table.insert(object.events, {k}) + + object.events[ix][2] = istable(v.data) and table.Copy(v.data) or v.data + obj_events.Populate() + end, v.icon) + end + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + + if not object.events then return end + + for eid, event in pairs(object.events) do + + MSD.ButtonIcon(obj_events, "static", nil, 1, 50, Ln(event[1]), MSD.EventList[event[1]].icon, function(self) + if not MSD.EventList[event[1]].builUI then return end + local sub_list = PopupMenu("\"" .. Ln(event[1]) .. "\" " .. Ln("q_eventedit"), 2, MSD.EventList[event[1]].ui_h or 1.2, 50) + sub_list:Clear() + MSD.EventList[event[1]].builUI(event, sub_list, t_id, object) + end, function(self) + if (IsValid(self.Menu)) then self.Menu:Remove() self.Menu = nil end + + self.Menu = MSD.MenuOpen(false, self) + + self.Menu:AddOption(Ln("duplicate"), function() + table.insert(object.events, table.Copy(event)) + obj_events.Populate() + end) + + self.Menu:AddOption(Ln("q_eventremove"), function() + table.remove(object.events, eid) + obj_events.Populate() + end) + + local prev_ent = object.events[eid - 1] + + self.Menu:AddOption(Ln("moveup"), function() + object.events[eid] = table.Copy(prev_ent) + object.events[eid - 1] = table.Copy(event) + obj_events.Populate() + end, nil, function() + return prev_ent + end) + + + local next_ent = object.events[eid + 1] + + self.Menu:AddOption(Ln("movedown"), function() + object.events[eid] = table.Copy(next_ent) + object.events[eid + 1] = table.Copy(event) + obj_events.Populate() + end, nil, function() + return next_ent + end) + + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + end + end + + panel.Canvas:AddItem(ops_panel) + obj_sets.Populate() + obj_events.Populate() + end + + pages["edit_page3"] = function() + MSD.Header(panel.Canvas, Ln("q_rwdeditor"), function() + openPage("edit", true) + end) + + if not MQS.IsAdministrator(LocalPlayer()) then + local pnl = vgui.Create("DPanel") + pnl.StaticScale = { + w = 1, + h = 1.1, + minw = 150, + minh = 150 + } + pnl.Paint = function(self, w, h) + MSD.DrawTexturedRect(w / 2 - 24, h / 2 - 50, 48, 48, MSD.Icons48.cancel, MSD.Text["n"]) + draw.DrawText(MSD.GetPhrase("need_admin"), "MSDFont.25", w / 2, h / 2 + 10, MSD.Text["n"], TEXT_ALIGN_CENTER) + end + + panel.Canvas:AddItem(pnl) + return + end + + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 52) + + ops_panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.RoundedBox(MSD.Config.Rounded, w / 2, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.DrawText(Ln("q_rwdlist"), "MSDFont.25", 12, 12, color_white, TEXT_ALIGN_LEFT) + draw.DrawText(Ln("q_rwdsets"), "MSDFont.25", w / 2 + 12, 12, color_white, TEXT_ALIGN_LEFT) + end + + local rwd_set, rwd_list + rwd_list = vgui.Create("MSDPanelList", ops_panel) + rwd_list:SetSize(ops_panel:GetWide() / 2, ops_panel:GetTall() - 52) + rwd_list:SetPos(0, 52) + rwd_list:EnableVerticalScrollbar() + rwd_list:EnableHorizontal(true) + rwd_list:SetSpacing(2) + rwd_list:SetPadding(0) + rwd_list.IgnoreVbar = true + + rwd_list.Populate = function() + rwd_list:Clear() + + if not active_quest.reward then + active_quest.reward = {} + end + + for rw_name, rw_data in pairs(MQS.Rewards) do + if not MQS.RewardsList[rw_name] then continue end + if rw_data.check and rw_data.check() then continue end + + MSD.BoolSlider(rwd_list, "static", nil, 1, 50, Ln("enable") .. " '" .. Ln(rw_name) .. "'", active_quest.reward[rw_name] and true or false, function(self, var) + if var then + active_quest.reward[rw_name] = istable(MQS.RewardsList[rw_name].data) and table.Copy(MQS.RewardsList[rw_name].data) or MQS.RewardsList[rw_name].data + else + active_quest.reward[rw_name] = nil + end + + rwd_set.Populate() + end) + end + end + + rwd_set = vgui.Create("MSDPanelList", ops_panel) + rwd_set:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall() - 52) + rwd_set:SetPos(ops_panel:GetWide() / 2, 52) + rwd_set:EnableVerticalScrollbar() + rwd_set:EnableHorizontal(true) + rwd_set:SetSpacing(2) + rwd_set:SetPadding(0) + rwd_set.IgnoreVbar = true + + rwd_set.Populate = function() + rwd_set:Clear() + if not active_quest.reward then return end + + for rid, rdata in pairs(active_quest.reward) do + if MQS.Rewards[rid].check and MQS.Rewards[rid].check() then continue end + MSD.InfoHeader(rwd_set, rid) + MQS.RewardsList[rid].builUI(active_quest.reward[rid], rwd_set) + end + end + + panel.Canvas:AddItem(ops_panel) + rwd_list.Populate() + rwd_set.Populate() + end + + pages["quests_copy"] = function() + MSD.Header(panel.Canvas, Ln("copy_data"), function() + openPage("edit", true) + end) + + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 52) + + ops_panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.RoundedBox(MSD.Config.Rounded, w / 2, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.DrawText(Ln("quest_list"), "MSDFont.25", 12, 12, color_white, TEXT_ALIGN_LEFT) + draw.DrawText(Ln("q_findmap"), "MSDFont.25", w / 2 + 12, 12, color_white, TEXT_ALIGN_LEFT) + end + + local rwd_set, rwd_list + rwd_list = vgui.Create("MSDPanelList", ops_panel) + rwd_list:SetSize(ops_panel:GetWide() / 2, ops_panel:GetTall() - 52) + rwd_list:SetPos(0, 52) + rwd_list:EnableVerticalScrollbar() + rwd_list:EnableHorizontal(true) + rwd_list:SetSpacing(2) + rwd_list:SetPadding(0) + rwd_list.IgnoreVbar = true + + rwd_list.Populate = function() + rwd_list:Clear() + + for k, v in pairs(MQS.Quests) do + MSD.Button(rwd_list, "static", nil, 1, 50, "[" .. k .. "] " .. v.name, function() + active_quest = table.Copy(v) + active_quest.id = k .. "_copy" + active_quest.name = v.name .. " Copy" + active_quest.new = true + openPage("edit", true) + end, true) + end + end + + rwd_set = vgui.Create("MSDPanelList", ops_panel) + rwd_set:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall() - 52) + rwd_set:SetPos(ops_panel:GetWide() / 2, 52) + rwd_set:EnableVerticalScrollbar() + rwd_set:EnableHorizontal(true) + rwd_set:SetSpacing(2) + rwd_set:SetPadding(0) + rwd_set.IgnoreVbar = true + + rwd_set.Populate = function() + rwd_set:Clear() + + MSD.TextEntry(rwd_set, "static", nil, 1, 50, Ln("e_text"), Ln("map") .. ":", "", function(self, value) + net.Start("MQS.GetOtherQuests") + net.WriteString(value) + net.SendToServer() + end, false) + + if MQS.AltDate and MQS.AltDate.Quests then + for k, v in pairs(MQS.AltDate.Quests) do + MSD.Button(rwd_set, "static", nil, 1, 50, "[" .. k .. "] " .. v.name, function() + active_quest = table.Copy(v) + active_quest.id = k .. "_copy" + active_quest.name = v.name .. " Copy" + active_quest.new = true + openPage("edit", true) + end, true) + end + end + end + + MQS.AltDateUpdate = function(arguments) + if IsValid(rwd_set) then + rwd_set.Populate() + end + end + + panel.Canvas:AddItem(ops_panel) + rwd_list.Populate() + rwd_set.Populate() + end + + pages["npcs"] = function() + if not MQS.Config.NPC.enable then + local pnl = vgui.Create("DPanel") + + pnl.StaticScale = { + w = 1, + h = 1, + minw = 150, + minh = 150 + } + + pnl.Paint = function(self, w, h) + MSD.DrawTexturedRect(w / 2 - 24, h / 2 - 50, 48, 48, MSD.Icons48.account, MSD.Text["n"]) + draw.DrawText(Ln("Quest NPCs are disabled"), "MSDFont.25", w / 2, h / 2 + 10, MSD.Text["n"], TEXT_ALIGN_CENTER) + draw.DrawText(Ln("You can enable them in settings"), "MSDFont.25", w / 2, h / 2 + 35, MSD.Text["n"], TEXT_ALIGN_CENTER) + end + + panel.Canvas:AddItem(pnl) + + return + end + + local npctab = MSD.Header(panel.Canvas, Ln("npc_editor")) + + MQS.UpdateMenuElements = function() + if IsValid(npctab) then + openPage("npcs", true) + end + end + + local function EditNPC(id, npc) + local rwd_list, child, pn = PopupMenu(Ln("npc_editor"), 1, 1.1, 50, 1.5) + + if not id then + npc = table.Copy(blank_npc) + end + + local map = string.lower(game.GetMap()) + + if not npc.spawns[map] then + npc.spawns[map] = {Vector(0, 0, 0), Angle(0, 0, 0),} + end + + local pnw = child:GetWide() + local mdlp = MSD.NPCModelFrame(child, pnw - pnw / 3, 50, pnw / 3 - 10, child:GetTall() - 100, npc.model, npc.sequence) + MSD.Button(child, pnw - pnw / 3, child:GetTall() - 50, pnw / 3 - 10, 50, Ln("set_anim"), function() + MQS.NPCAnimationMenu(pn, mdlp.Entity, function(v) + mdlp.Entity:ResetSequence(v) + mdlp.Entity:SetCycle(0) + npc.sequence = string.lower(v) + end) + end) + + MSD.TextEntry(rwd_list, "static", nil, 1, 50, Ln("enter_name"), Ln("name") .. ":", npc.name, function(self, value) + npc.name = value + end, true) + + local mdl = MSD.TextEntry(rwd_list, "static", nil, 1.5, 50, Ln("e_model"), Ln("model") .. ":", npc.model, function(self, value) + npc.model = value + mdlp:UpdateModelValue(value) + end, true) + + MSD.Button(rwd_list, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetModel() + mdl:SetText(md) + npc.model = md + end) + + MSD.TextEntry(rwd_list, "static", nil, 1, 150, Ln("npc_e_speech"), "", npc.text, function(self, value) + npc.text = value + end, true, nil, true) + + MSD.TextEntry(rwd_list, "static", nil, 1, 50, Ln("e_text"), Ln("q_npc_answer_ok") .. ":", npc.answer_yes, function(self, value) + npc.answer_yes = value + end, true) + + MSD.TextEntry(rwd_list, "static", nil, 1, 50, Ln("e_text"), Ln("q_npc_answer_no") .. ":", npc.answer_no, function(self, value) + npc.answer_no = value + end, true) + + MSD.TextEntry(rwd_list, "static", nil, 1, 50, Ln("e_text"), Ln("q_npc_quest_no") .. ":", npc.text_notask, function(self, value) + npc.text_notask = value + end, true) + + MSD.TextEntry(rwd_list, "static", nil, 1, 50, Ln("e_text"), Ln("q_npc_answer_noq") .. ":", npc.answer_notask, function(self, value) + npc.answer_notask = value + end, true) + + local vecd = MSD.VectorDisplay(rwd_list, "static", nil, 1, 50, Ln("spawn_point"), npc.spawns[map][1], function() end) + local amgl = MSD.AngleDisplay(rwd_list, "static", nil, 1, 50, Ln("spawn_ang"), npc.spawns[map][2], function() end) + + MSD.Button(rwd_list, "static", nil, 3, 50, Ln("set_pos_self"), function() + local vec = LocalPlayer():GetPos() + vecd.vector = vec + npc.spawns[map][1] = vec + local ang = Angle(0, LocalPlayer():GetAngles().y, 0) + amgl.angle = ang + npc.spawns[map][2] = ang + end) + + MSD.Button(rwd_list, "static", nil, 3, 50, Ln("set_pos_aim"), function() + local vec = LocalPlayer():GetEyeTrace().HitPos + if not vec then return end + vecd.vector = vec + npc.spawns[map][1] = vec + local ang = Angle(0, LocalPlayer():GetAngles().y, 0) + amgl.angle = ang + npc.spawns[map][2] = ang + end) + + MSD.Button(rwd_list, "static", nil, 3, 50, Ln("copy_from_ent"), function() + local vec = LocalPlayer():GetEyeTrace().Entity + if not vec then return end + local ang = vec:GetAngles() + amgl.angle = ang + npc.spawns[map][2] = ang + vec = vec:GetPos() + vecd.vector = vec + npc.spawns[map][1] = vec + end) + + if not id then + MSD.BigButton(rwd_list, "static", nil, 1, 80, Ln("npc_submit"), MSD.Icons48.submit, function() + MQS.CreateNPC(npc) + pn.Close() + end) + else + MSD.BigButton(rwd_list, "static", nil, 2, 80, Ln("npc_update"), MSD.Icons48.save, function() + openPage("npcs", true) + MQS.UpdateNPC(id, npc) + pn.Close() + end) + + MSD.BigButton(rwd_list, "static", nil, 2, 80, Ln("npc_remove"), MSD.Icons48.layers_remove, function() + openPage("npcs", true) + MQS.UpdateNPC(id, npc, true) + pn.Close() + end) + end + end + + MSD.BigButton(panel.Canvas, "static", nil, 4, 120, Ln("npc_new"), MSD.Icons48.account_edit, function() + EditNPC() + end) + + for k, v in pairs(MQS.Config.NPC.list) do + MSD.BigButton(panel.Canvas, "static", nil, 4, 120, v.name, MSD.Icons48.account, function() + EditNPC(k, v) + end, nil, nil, function(self) + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + + self.Menu:AddOption(Ln("remove"), function() + MQS.UpdateNPC(k, v, true) + openPage("npcs", true) + end) + + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + end + end + + pages["settings"] = function() + local oldcfg = table.Copy(MQS.Config) + MSD.Header(panel.Canvas, Ln("settings")) + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 135) + + ops_panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.RoundedBox(MSD.Config.Rounded, w / 2, 0, w / 2 - 2, h, MSD.Theme["l"]) + end + + local rwd_set, rwd_list + rwd_list = vgui.Create("MSDPanelList", ops_panel) + rwd_list:SetSize(ops_panel:GetWide() / 2, ops_panel:GetTall() - 2) + rwd_list:SetPos(0, 0) + rwd_list:EnableVerticalScrollbar() + rwd_list:EnableHorizontal(true) + rwd_list:SetSpacing(2) + rwd_list:SetPadding(0) + rwd_list.IgnoreVbar = true + + rwd_list.Populate = function() + rwd_list:Clear() + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("set_hud"), function() + rwd_set.Populate("hud") + end) + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("set_server"), function() + rwd_set.Populate("server") + end) + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("access_editors"), function() + rwd_set.Populate("access_editors") + end) + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("access_admins"), function() + rwd_set.Populate("access_admins") + end) + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("user_data"), function() + rwd_set.Populate("user_data") + end) + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("Export") .. "/" .. Ln("Import"), function() + rwd_set.Populate("export") + end) + end + + rwd_set = vgui.Create("MSDPanelList", ops_panel) + rwd_set:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall() - 2) + rwd_set:SetPos(ops_panel:GetWide() / 2, 0) + rwd_set:EnableVerticalScrollbar() + rwd_set:EnableHorizontal(true) + rwd_set:SetSpacing(2) + rwd_set:SetPadding(0) + rwd_set.IgnoreVbar = true + rwd_set.SetingList = {} + + rwd_set.Populate = function(seting) + if not rwd_set.SetingList[seting] then return end + MQS.PlayerDataPanel = nil + rwd_set:Clear() + rwd_set.SetingList[seting]() + last_sm = seting + end + + rwd_set.SetingList["hud"] = function() + MSD.Header(rwd_set, Ln("set_hud_pos")) + + MSD.DTextSlider(rwd_set, "static", nil, 1, 50, Ln("set_ui_align_right"), Ln("set_ui_align_left"), MQS.Config.UI.HudAlignX, function(self, value) + MQS.Config.UI.HudAlignX = value + end) + + MSD.DTextSlider(rwd_set, "static", nil, 1, 50, Ln("set_ui_align_top"), Ln("set_ui_align_bottom"), MQS.Config.UI.HudAlignY, function(self, value) + MQS.Config.UI.HudAlignY = value + end) + + MSD.VolumeSlider(rwd_set, "static", nil, 1, 50, Ln("set_ui_offset_h"), MQS.Config.UI.HudOffsetX, function(self, var) + MQS.Config.UI.HudOffsetX = math.Round(var, 3) + end) + + MSD.VolumeSlider(rwd_set, "static", nil, 1, 50, Ln("set_ui_offset_v"), MQS.Config.UI.HudOffsetY, function(self, var) + MQS.Config.UI.HudOffsetY = math.Round(var, 3) + end) + + MSD.Header(rwd_set, Ln("set_hud_themes")) + local tm1, tm2, tm3 + + tm1 = MSD.Button(rwd_set, "static", nil, 3, 50, Ln("theme") .. " 1", function() + MQS.Config.UI.HUDBG = 0 + tm1.hovered = true + tm2.hovered = false + tm3.hovered = false + end) + + tm2 = MSD.Button(rwd_set, "static", nil, 3, 50, Ln("theme") .. " 2", function() + MQS.Config.UI.HUDBG = 1 + tm1.hovered = false + tm2.hovered = true + tm3.hovered = false + end) + + tm3 = MSD.Button(rwd_set, "static", nil, 3, 50, Ln("theme") .. " 3", function() + MQS.Config.UI.HUDBG = 2 + tm1.hovered = false + tm2.hovered = false + tm3.hovered = true + end) + + if MQS.Config.UI.HUDBG == 0 then + tm1.hovered = true + elseif MQS.Config.UI.HUDBG == 1 then + tm2.hovered = true + else + tm3.hovered = true + end + end + + rwd_set.SetingList["server"] = function() + MSD.Header(rwd_set, Ln("set_server")) + + MSD.TextEntry(rwd_set, "static", nil, 1, 50, Ln("e_number"), Ln("q_ent_draw"), MQS.Config.QuestEntDrawDist, function(self, value) + MQS.Config.QuestEntDrawDist = tonumber(value) or 500 + end, true, nil, nil, true) + + MSD.Binder(rwd_set, "static", nil, 1, 50, Ln("q_loop_stop_key"), MQS.Config.StopKey, function(num) + MQS.Config.StopKey = num + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, Ln("npc_q_enable"), MQS.Config.NPC.enable, function(self, value) + MQS.Config.NPC.enable = value + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, Ln("sortquests_cat"), MQS.Config.Sort, function(self, value) + MQS.Config.Sort = value + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, Ln("compact_obj"), MQS.Config.SmallObj, function(self, value) + MQS.Config.SmallObj = value + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, Ln("mqs_fix_cam"), MQS.Config.CamFix, function(self, value) + MQS.Config.CamFix = value + end) + + local combo = MSD.ComboBox(rwd_set, "static", nil, 1, 50, Ln("into_quest") .. ":", Ln("none")) + combo:AddChoice(Ln("none"), "") + + combo.OnSelect = function(self, index, text, data) + MQS.Config.IntoQuest = data + end + + for k, v in pairs(MQS.Quests) do + if v.quest_needed or v.looped then continue end + + if MQS.Config.IntoQuest == k then + combo:SetValue(v.name) + end + + combo:AddChoice(v.name, k) + end + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, Ln("into_quest_auto"), MQS.Config.IntoQuestAutogive, function(self, value) + MQS.Config.IntoQuestAutogive = value + end) + + end + + rwd_set.SetingList["access_admins"] = function() + MSD.Header(rwd_set, Ln("access_admins")) + + local entr = MSD.TextEntry(rwd_set, "static", nil, 1.5, 50, Ln("e_usergroup"), Ln("add_usergroup"), "", function(self, value) + MQS.Config.Administrators[value] = true + if MQS.Config.Editors[value] then MQS.Config.Editors[value] = nil end + rwd_set.Populate("access_admins") + end) + + MSD.Button(rwd_set, "static", nil, 3, 50, Ln("add_usergroup"), function() + entr:OnEnter(entr:GetValue()) + end) + + for ugr, stat in pairs(MQS.Config.Administrators) do + MSD.Header(rwd_set, ugr, function() + MQS.Config.Administrators[ugr] = nil + rwd_set.Populate("access_admins") + end, MSD.Icons48.cancel, true) + end + end + + rwd_set.SetingList["access_editors"] = function() + MSD.Header(rwd_set, Ln("access_editors")) + + local entr = MSD.TextEntry(rwd_set, "static", nil, 1.5, 50, Ln("e_usergroup"), Ln("add_usergroup"), "", function(self, value) + if MQS.Config.Administrators[value] then + self.error = Ln("ug_isanadmin") + return + end + MQS.Config.Editors[value] = true + rwd_set.Populate("access_editors") + self.error = nil + end) + + MSD.Button(rwd_set, "static", nil, 3, 50, Ln("add_usergroup"), function() + entr:OnEnter(entr:GetValue()) + end) + + for ugr, stat in pairs(MQS.Config.Editors) do + MSD.Header(rwd_set, ugr, function() + MQS.Config.Editors[ugr] = nil + rwd_set.Populate("access_editors") + end, MSD.Icons48.cancel, true) + end + end + + rwd_set.SetingList["user_data"] = function() + MQS.PlayerDataPanel = rwd_set + + MQS.PlayerDataPanel.Update = function(data, sid) + rwd_set:Clear() + MSD.Header(rwd_set, Ln("user_data")) + + MSD.TextEntry(rwd_set, "static", nil, 1, 50, "STEAM_0:0:0000", Ln("find_player_id32"), sid or "", function(self, value) + net.Start("MQS.GetPlayersQuests") + net.WriteString(value) + net.SendToServer() + end) + + if not data or not data.QuestList then return end + MSD.InfoHeader(rwd_set, "Quest ID", 2) + MSD.InfoHeader(rwd_set, "Data", 2) + for k, v in pairs(data.QuestList) do + MSD.InfoHeader(rwd_set, k, 2) + MSD.TextEntry(rwd_set, "static", nil, 2, 25, "", "", v, function(self, value) + data.QuestList[k] = value + end, true, nil, nil, true) + end + + MSD.BigButton(rwd_set, "static", nil, 1, 75, Ln("save_chng"), MSD.Icons48.save, function() + local cd, bn = MQS.TableCompress(data.QuestList) + + net.Start("MQS.SavePlayerQuestList") + net.WriteString(sid) + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() + MQS.PlayerDataPanel.Update() + end) + end + + MQS.PlayerDataPanel.Update() + end + + rwd_set.SetingList["export"] = function() + MSD.Header(rwd_set, Ln("Export") .. "/" .. Ln("Import")) + MSD.InfoHeader(rwd_set, "Data saves to garrysmod/data/mqs/") + + local export_quest, import_quest + local combo = MSD.ComboBox(rwd_set, "static", nil, 1, 50, Ln("quest_list") .. ":", Ln("none")) + combo.OnSelect = function(self, index, text, data) + export_quest = data + end + for k, v in pairs(MQS.Quests) do + combo:AddChoice(v.name, k) + end + + MSD.Button(rwd_set, "static", nil, 1, 50, Ln("Export"), function() + if not export_quest then return end + if not MQS.Quests[export_quest] then return end + + if file.Exists("mqs/" .. export_quest .. ".txt", "DATA") then + local pl, _, plm = PopupMenu(Ln("file_exist"), 3, 4, 55) + + MSD.Button(pl, "static", nil, 1, 50, Ln("Replace"), function() + file.Write("mqs/" .. export_quest .. ".txt", util.TableToJSON(MQS.Quests[export_quest])) + surface.PlaySound("garrysmod/save_load2.wav") + plm.Close() + end) + + MSD.Button(pl, "static", nil, 1, 50, Ln("Save as") .. " " .. export_quest .. os.time(), function() + file.Write("mqs/" .. export_quest .. os.time() .. ".txt", util.TableToJSON(MQS.Quests[export_quest])) + surface.PlaySound("garrysmod/save_load1.wav") + plm.Close() + end) + + MSD.Button(pl, "static", nil, 1, 50, Ln("cancel"), function() + plm.Close() + end) + + return + end + + file.Write("mqs/" .. export_quest .. ".txt", util.TableToJSON(MQS.Quests[export_quest])) + surface.PlaySound("garrysmod/save_load1.wav") + end) + + local import = MSD.ComboBox(rwd_set, "static", nil, 1, 50, Ln("file_list") .. ":", Ln("none")) + import.OnSelect = function(self, index, text, data) + import_quest = data + end + local files = file.Find( "mqs/*.txt", "DATA" ) + if files then + for k, v in pairs(files) do + import:AddChoice(v, v) + end + else + import:AddChoice(Ln("none"), false) + end + + MSD.Button(rwd_set, "static", nil, 1, 50, Ln("Import"), function() + if not import_quest then return end + if not file.Exists("mqs/" .. import_quest, "DATA" ) then return end + local filename = string.Explode(".", import_quest) + local quest_id = filename[1] + local imported_quest = util.JSONToTable(file.Read("mqs/" .. import_quest, "DATA")) + imported_quest.id = MQS.Quests[quest_id] and quest_id .. os.time() or quest_id + MQS.QuestSubmit(imported_quest) + surface.PlaySound("garrysmod/content_downloaded.wav") + end) + end + + panel.Canvas:AddItem(ops_panel) + rwd_list.Populate() + rwd_set.Populate(last_sm) + + if MQS.IsAdministrator(LocalPlayer()) then + MSD.BigButton(panel.Canvas, "static", nil, 2, 80, Ln("upl_changes"), MSD.Icons48.save, function() + MQS.SaveConfig() + openPage("settings", true) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 80, Ln("res_changes"), MSD.Icons48.cross, function() + MQS.Config = oldcfg + openPage("settings", true) + end) + end + end + + for k, v in pairs(buttons) do + local button = MSD.MenuButton(panel.Menu, v[2], nil, nil, 250, 50, v[1], function(self) + panel.Menu.Deselect(not v[5] and self or false) + v[3]() + end) + + if v[4] then + panel.Menu.Deselect(button) + v[3]() + end + end +end + +mouleid = MSD.AddModule("MQS", MQS.OpenAdminMenu, Material("mqs/logo_msd.png", "smooth")) \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/ui/npc_menu.lua b/addons/mc_quests/lua/mqs/ui/npc_menu.lua new file mode 100644 index 0000000..7df975d --- /dev/null +++ b/addons/mc_quests/lua/mqs/ui/npc_menu.lua @@ -0,0 +1,239 @@ +local NpcMenu + +function MQS.OpenNPCMenu(npc) + local npc_table + local simple_npc = false + if NpcMenu then return end + + if npc:GetClass() == "mqs_npc" then + npc_table = MQS.Config.NPC.list[npc:GetUID()] + if not npc_table then return end + end + + if npc:GetClass() == "mcs_npc" then + simple_npc = true + end + + local tasks = MQS.ActiveQuestLists(npc:GetUID()) + local sw, sh = ScrW(), ScrH() + NpcMenu = vgui.Create("MSDSimpleFrame") + NpcMenu:SetSize(sw, sh) + NpcMenu:SetDraggable(false) + NpcMenu:Center() + NpcMenu:MakePopup() + + NpcMenu.OnClose = function() + NpcMenu = nil + end + + NpcMenu:SetAlpha(1) + NpcMenu:AlphaTo(255, 0.4) + NpcMenu.Page = 1 + NpcMenu.Pages = {} + + NpcMenu.Paint = function(self, w, h) + Derma_DrawBackgroundBlur(self, self.startTime) + if not npc.GetNamer then + NpcMenu:Close() + + return + end + + NpcMenu.Pages[NpcMenu.Page].paint(self, w, h) + end + + local OpenPage = function(id) + if not NpcMenu.Pages[id] then return end + NpcMenu.Page = id + NpcMenu.Pages[id].onOpne() + end + + NpcMenu.clsBut = MSD.IconButton(NpcMenu, MSD.Icons48.cross, NpcMenu:GetWide() - 34, 10, 25, nil, MSD.Config.MainColor.p, function() + if NpcMenu.OnPress then + NpcMenu.OnPress() + + return + end + + NpcMenu:AlphaTo(0, 0.4, 0, function() + NpcMenu:Close() + end) + end) + + NpcMenu.Pages[1] = { + paint = function(self, w, h) + end, + onOpne = function() + if NpcMenu.Paneler then + NpcMenu.Paneler:Remove() + end + + NpcMenu.Paneler = vgui.Create("DPanel", NpcMenu) + NpcMenu.Paneler:SetSize(sw - sw / 4 , sh / 4 - 10) + NpcMenu.Paneler:SetPos(sw / 8, sh / 2 - 10) + NpcMenu.Paneler.Paint = function(self, w, h) + MSD.DrawBG(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + draw.SimpleText(npc:GetNamer() .. ":", "MSDFont.32", 16, 24, color_white, TEXT_ALIGN_LEFT, 1) + local text = MSD.TextWrap(npc_table.text, "MSDFont.28", w - 24) + draw.DrawText(text, "MSDFont.28", 16, 50, color_white, TEXT_ALIGN_LEFT, 1) + end + local bs = NpcMenu.Paneler:GetWide() + local by = NpcMenu.Paneler:GetTall() + + MSD.Button(NpcMenu.Paneler, 8, by - 58, bs / 2 - 12, 50, npc_table.answer_yes, function() + NpcMenu:AlphaTo(1, 0.4, 0, function() + OpenPage(2) + NpcMenu:AlphaTo(255, 0.4) + end) + end) + + MSD.Button(NpcMenu.Paneler, bs / 2, by - 58, bs / 2 - 12, 50, npc_table.answer_no, function() + NpcMenu:AlphaTo(0, 0.4, 0, function() + NpcMenu:Close() + end) + end) + end, + } + + NpcMenu.Pages[2] = { + paint = function() end, + onOpne = function() + if NpcMenu.Paneler then + NpcMenu.Paneler:Remove() + end + + NpcMenu.Paneler = vgui.Create("DPanel", NpcMenu) + NpcMenu.Paneler:SetSize(sw, sh - 100) + NpcMenu.Paneler:SetPos(0, 100) + NpcMenu.Paneler.Paint = function(self, w, h) end + NpcMenu.Paneler = vgui.Create("MSDPanelList", NpcMenu) + NpcMenu.Paneler:SetSize(sw - (sw / 6) * 2, sh - (sh / 8) * 2) + NpcMenu.Paneler:SetPos(sw / 6, sh / 8) + NpcMenu.Paneler:EnableVerticalScrollbar() + NpcMenu.Paneler:EnableHorizontal(true) + NpcMenu.Paneler:SetSpacing(5) + NpcMenu.Paneler.IgnoreVbar = true + + for k, v in pairs(MQS.Quests) do + if not tasks[k] then continue end + local qpnl = vgui.Create("DButton") + qpnl:SetText("") + qpnl.alpha = 0 + qpnl.title = 0 + qpnl.StaticScale = { + w = 1, + h = 5, + minw = 200, + minh = 120 + } + + qpnl.Paint = function(self, w, h) + if self.hover then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, self.title + 16, 45, MSD.Theme["d"]) + self.title = draw.SimpleText(v.name, "MSDFont.25", 8, 8, color_white, TEXT_ALIGN_LEFT) + + draw.DrawText(MSD.GetPhrase("q_start"), "MSDFont.25", 26 + self.title, 8, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(MSD.GetPhrase("q_start"), "MSDFont.25", 26 + self.title, 8, MSD.ColorAlpha(MSD.Text["d"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + + local text = MSD.TextWrap(v.desc, "MSDFont.28", w - 16) + draw.DrawText(text, "MSDFont.22", 8, 50, MSD.Text["d"], TEXT_ALIGN_LEFT) + + return true + end + + qpnl.OnCursorEntered = function(self) + self.hover = true + end + + qpnl.OnCursorExited = function(self) + self.hover = false + end + + qpnl.DoClick = function(self) + NpcMenu:Close() + net.Start("MQS.StartTask") + net.WriteString(k) + net.WriteBool(simple_npc) + if simple_npc then + net.WriteString(npc:GetUID()) + else + net.WriteInt(npc:GetUID(), 16) + end + + net.SendToServer() + end + + NpcMenu.Paneler:AddItem(qpnl) + end + + if table.IsEmpty(tasks) then + local pnl = vgui.Create("DPanel") + + pnl.StaticScale = { + w = 1, + h = 1, + minw = 150, + minh = 150 + } + + pnl.Paint = function(self, w, h) + MSD.DrawTexturedRect(w / 2 - 24, h / 2 - 50, 48, 48, MSD.Icons48.account, MSD.Text["n"]) + draw.DrawText(MSD.GetPhrase("There is no quests avalible"), "MSDFont.25", w / 2, h / 2 + 10, MSD.Text["n"], TEXT_ALIGN_CENTER) + end + + NpcMenu.Paneler:AddItem(pnl) + end + end, + } + + NpcMenu.Pages[3] = { + paint = function(self, w, h) + end, + onOpne = function() + if NpcMenu.Paneler then + NpcMenu.Paneler:Remove() + end + + NpcMenu.Paneler = vgui.Create("DPanel", NpcMenu) + NpcMenu.Paneler:SetSize(sw - sw / 4 , sh / 4 - 10) + NpcMenu.Paneler:SetPos(sw / 8, sh / 2 - 10) + NpcMenu.Paneler.Paint = function(self, w, h) + MSD.DrawBG(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + draw.SimpleText(npc:GetNamer() .. ":", "MSDFont.32", 16, 24, color_white, TEXT_ALIGN_LEFT, 1) + local text = MSD.TextWrap(npc_table.text_notask, "MSDFont.28", w - 24) + draw.DrawText(text, "MSDFont.28", 16, 50, color_white, TEXT_ALIGN_LEFT, 1) + end + local bs = NpcMenu.Paneler:GetWide() + local by = NpcMenu.Paneler:GetTall() + MSD.Button(NpcMenu.Paneler, 8, by - 58, bs - 16, 50, npc_table.answer_notask, function() + NpcMenu:AlphaTo(0, 0.4, 0, function() + NpcMenu:Close() + end) + end) + end, + } + + if simple_npc then + OpenPage(2) + else + if table.IsEmpty(tasks) then + OpenPage(3) + else + OpenPage(1) + end + end +end + +net.Receive("MQS.OpenNPCMenu", function() + local npc = net.ReadEntity() + MQS.OpenNPCMenu(npc) +end) \ No newline at end of file diff --git a/addons/mc_quests/lua/mqs/ui/user_panel.lua b/addons/mc_quests/lua/mqs/ui/user_panel.lua new file mode 100644 index 0000000..7d2071c --- /dev/null +++ b/addons/mc_quests/lua/mqs/ui/user_panel.lua @@ -0,0 +1,112 @@ +function MQS.OpenPlayerMenu() + if MQS.SetupMenu then + MQS.SetupMenu:AlphaTo(0, 0.4, 0, function() + MQS.SetupMenu:Close() + end) + + return + end + + local tasks = MQS.ActiveQuestLists() + local pnl_w, pnl_h = ScrW(), ScrH() + pnl_w, pnl_h = pnl_w - pnl_w / 4, pnl_h - pnl_h / 6 + panel = vgui.Create("MSDSimpleFrame") + panel:SetSize(pnl_w, pnl_h) + panel:SetDraggable(false) + panel:Center() + panel:MakePopup() + panel:SetAlpha(0) + panel:AlphaTo(255, 0.3) + + panel.OnClose = function() + MQS.SetupMenu = nil + end + + panel:SetAlpha(1) + panel:AlphaTo(255, 0.4) + + panel.Paint = function(self, w, h) + MSD.DrawBG(self, w, h) + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, 50, MSD.Theme["d"]) + draw.RoundedBox(MSD.Config.Rounded, 0, 52, w, h - 52, MSD.Theme["l"]) + + draw.DrawText(string.upper(MSD.GetPhrase("quests")), "MSDFont.25", 12, 12, color_white, TEXT_ALIGN_LEFT) + end + + panel.clsBut = MSD.IconButton(panel, MSD.Icons48.cross, panel:GetWide() - 34, 10, 25, nil, MSD.Config.MainColor.p, function() + if panel.OnPress then + panel.OnPress() + + return + end + + panel:AlphaTo(0, 0.4, 0, function() + panel:Close() + end) + end) + + panel.Canvas = vgui.Create("MSDPanelList", panel) + panel.Canvas:SetSize(panel:GetWide(), panel:GetTall() - 52) + panel.Canvas:SetPos(0, 52) + panel.Canvas:EnableVerticalScrollbar() + panel.Canvas:EnableHorizontal(true) + panel.Canvas:SetSpacing(5) + panel.Canvas.IgnoreVbar = true + + for k, v in pairs(MQS.Quests) do + if not tasks[k] then continue end + local qpnl = vgui.Create("DButton") + qpnl:SetText("") + qpnl.alpha = 0 + qpnl.title = 0 + qpnl.StaticScale = { + w = 2, + h = 5, + minw = 200, + minh = 120 + } + + qpnl.Paint = function(self, w, h) + if self.hover then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, self.title + 16, 45, MSD.Theme["d"]) + self.title = draw.SimpleText(v.name, "MSDFont.25", 8, 8, color_white, TEXT_ALIGN_LEFT) + + draw.DrawText(MSD.GetPhrase("q_start"), "MSDFont.25", 26 + self.title, 8, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(MSD.GetPhrase("q_start"), "MSDFont.25", 26 + self.title, 8, MSD.ColorAlpha(MSD.Text["d"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + + local text = MSD.TextWrap(v.desc, "MSDFont.28", w - 16) + draw.DrawText(text, "MSDFont.22", 8, 50, MSD.Text["d"], TEXT_ALIGN_LEFT) + + return true + end + + qpnl.OnCursorEntered = function(self) + self.hover = true + end + + qpnl.OnCursorExited = function(self) + self.hover = false + end + + qpnl.DoClick = function(self) + panel:Close() + net.Start("MQS.StartTask") + net.WriteString(k) + net.SendToServer() + end + + panel.Canvas:AddItem(qpnl) + end + + MQS.SetupMenu = panel + + return panel +end \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/autorun/mcs_load.lua b/addons/mc_simple_npcs/lua/autorun/mcs_load.lua new file mode 100644 index 0000000..08a3435 --- /dev/null +++ b/addons/mc_simple_npcs/lua/autorun/mcs_load.lua @@ -0,0 +1,80 @@ +-- __ __ _ _____ _ _ _ _ _____ _____ +-- | \/ | ( ) / ____| (_) | | | \ | | | __ \ / ____| +-- | \ / | __ _ ___ |/ ___ | (___ _ _ __ ___ _ __ | | ___ | \| | | |__) | | | ___ +-- | |\/| | / _` | / __| / __| \___ \ | | | '_ ` _ \ | '_ \ | | / _ \ | . ` | | ___/ | | / __| +-- | | | | | (_| | | (__ \__ \ ____) | | | | | | | | | | |_) | | | | __/ | |\ | | | | |____ \__ \ +-- |_| |_| \__,_| \___| |___/ |_____/ |_| |_| |_| |_| | .__/ |_| \___| |_| \_| |_| \_____| |___/ +-- | | +-- |_| V 1.5.3 +MCS = {} +MCS.Spawns = {} +MCS.Config = {} +MCS.Themes = {} +MCS.Version = "1.5.4" +MCS.ServerID = "MCS77656119812926226601ID" +MCS.MainUserID = "76561198129262266" + +function MCS.Load() + if !file.Exists(MCS.ServerID, "DATA") then + file.CreateDir(MCS.ServerID) + print( "[MCS NPCs] Server DATA Dir created" ) + end + + if SERVER then + print( "[MCS NPCs] Server init starting" ) + include("mcs_npcs/sh_addonsup.lua") + include("mcs_npcs/sh_config.lua") + include("mcs_npcs/sh_npcspawn.lua") + include("mcs_npcs/sv_init.lua") + AddCSLuaFile("mcs_npcs/sh_addonsup.lua") + AddCSLuaFile("mcs_npcs/sh_config.lua") + AddCSLuaFile("mcs_npcs/sh_npcspawn.lua") + AddCSLuaFile("mcs_npcs/cl_init.lua") + AddCSLuaFile("mcs_npcs/cl_util.lua") + AddCSLuaFile("mcs_npcs/cl_menu.lua") + else + print( "[MCS NPCs] Client init starting" ) + include("mcs_npcs/sh_addonsup.lua") + include("mcs_npcs/sh_config.lua") + include("mcs_npcs/sh_npcspawn.lua") + include("mcs_npcs/cl_init.lua") + include("mcs_npcs/cl_util.lua") + include("mcs_npcs/cl_menu.lua") + end + + local files = file.Find("mcs_npcs/themes/*", "LUA") + + for k, v in pairs(files) do + if SERVER then + print("[MCS NPCs] Adding theme " .. k .. " " .. v) + include("mcs_npcs/themes/" .. v) + AddCSLuaFile("mcs_npcs/themes/" .. v) + else + include("mcs_npcs/themes/" .. v) + end + end + + files = file.Find("mcs_npcs/npcs/*", "LUA") + + for k, v in pairs(files) do + if SERVER then + include("mcs_npcs/npcs/" .. v) + AddCSLuaFile("mcs_npcs/npcs/" .. v) + else + include("mcs_npcs/npcs/" .. v) + end + print( "[MCS NPCs] Added file " .. v) + end + + print( "[MCS NPCs] Init done" ) +end + +if SERVER then + hook.Add("PostGamemodeLoaded", "MCS.Load.SV", function() MCS.Load() end) +else + hook.Add("InitPostEntity", "MCS.Load.CL", function() MCS.Load() end) +end + +if GAMEMODE then + MCS.Load() +end \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/entities/mcs_npc/shared.lua b/addons/mc_simple_npcs/lua/entities/mcs_npc/shared.lua new file mode 100644 index 0000000..b0c8133 --- /dev/null +++ b/addons/mc_simple_npcs/lua/entities/mcs_npc/shared.lua @@ -0,0 +1,244 @@ +AddCSLuaFile() +ENT.Type = "ai" +ENT.Base = "base_anim" +ENT.PrintName = "Simple NPC" +ENT.Author = "Mactavish" +ENT.Spawnable = false +ENT.AdminSpawnable = true + +function ENT:Initialize() + self.SimpleNPC = true + self.UsingPlayer = false + self.names = 0 + + if SERVER then + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + self:CapabilitiesAdd(CAP_ANIMATEDFACE) + end + self.ClModels = MCS.Spawns[self:GetUID()] and MCS.Spawns[self:GetUID()].ClModels or {} + + if CLIENT and self.ClModels then + self.mdls = {} + self.msdW = 0 + for k, v in ipairs(self.ClModels) do + self.mdls[k] = ents.CreateClientProp() + self.mdls[k]:SetModel(v.model) + self.mdls[k]:SetSkin(v.skin or 0) + self.mdls[k]:InvalidateBoneCache() + self.mdls[k]:SetMoveType(MOVETYPE_NONE) + self.mdls[k]:SetParent(self) + + if v.marge then + self.mdls[k]:AddEffects(EF_BONEMERGE) + self.mdls[k]:AddEffects(EF_BONEMERGE_FASTCULL) + self.mdls[k]:AddEffects(EF_PARENT_ANIMATES) + self.mdls[k].marge = true + else + self.mdls[k]:SetPos(self:LocalToWorld(v.pos)) + self.mdls[k]:SetAngles(self:GetAngles() + v.ang) + self.mdls[k].pos = v.pos + self.mdls[k].ang = v.ang + self.mdls[k].bone = v.bone + local mat = Matrix() + local scale = (v.scale or 1) * self:GetModelScale() + mat:Scale(Vector(scale, scale, scale)) + self.mdls[k]:EnableMatrix("RenderMultiply", mat) + end + self.mdls[k]:Spawn() + end + end +end + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "Namer") + self:NetworkVar("String", 1, "UID") + self:NetworkVar("String", 2, "DefAnimation") + self:NetworkVar("Bool", 0, "InputLimit") +end + +function ENT:Think() + local CT = CurTime() + + if SERVER and self:GetInputLimit() and self.UsingPlayer ~= false and not IsValid(self.UsingPlayer) then + self.UsingPlayer = false + end + + if SERVER and self.timer and self.timer < CT and not self.UsingPlayer then + self:Remove() + return true + end + + if CLIENT then + if not self.flexes then + self.flexes = { + self:GetFlexIDByName("jaw_drop"), + self:GetFlexIDByName("left_part"), + self:GetFlexIDByName("right_part"), + self:GetFlexIDByName("left_mouth_drop"), + self:GetFlexIDByName("right_mouth_drop") + } + end + + local weight = (MCS.Dialogue and MCS.Dialogue.Sound) and math.Clamp(MCS.Dialogue.Sound:GetLevel() * 1.1, 0, 1) or + 0 + + for k, v in pairs(self.flexes) do + if not v then continue end + self:SetFlexWeight(v, weight) + end + end + + + self:NextThink(CT) + + return true +end + +if SERVER then + function ENT:GotScared(ply, data) + local npct = MCS.Spawns[self:GetUID()] + if not npct then return end + + self:ResetSequence(npct.scare_anim[1]) + self:SetCycle(0) + self.LastAnim = CurTime() + self.scared = true + + if npct.scare_sound then + self:EmitSound(npct.scare_sound) + end + + if npct.do_scare then + npct.do_scare(self, ply, data) + end + + timer.Simple(npct.scare_timer, function() + if not IsValid(self) then return end + self:ResetSequence(npct.scare_anim[2]) + self:SetCycle(0) + self.LastAnim = CurTime() + self.scared = nil + + if npct.do_unscare then + npct.do_unscare(self, ply, data) + end + end) + end + + function ENT:AcceptInput(istr, ply) + if self.scared then return end + + if not IsValid(ply) or (ply.UseTimer and ply.UseTimer > CurTime()) then return end + + ply.UseTimer = CurTime() + MCS.Config.UseDelay + + if self:GetInputLimit() then + if self.UsingPlayer then + return + else + self.UsingPlayer = ply + end + + if self.timer then + self.timer = self.timer + 120 + end + end + + net.Start("MCS.OpenMenu") + net.WriteEntity(self) + net.Send(ply) + end + + function ENT:OnTakeDamage(data) + if self.scared then return end + + local attc = data:GetAttacker() + + if self.canscare and IsValid(attc) and attc:IsPlayer() then + self:GotScared(attc, data) + end + end +else + function ENT:Draw() + self:DrawModel() + + local p_dist = self:GetPos():DistToSqr(LocalPlayer():GetPos()) + + if (p_dist > 100000 and not MQS.CCam) then return end + + local npct = MCS.Spawns[self:GetUID()] + + if not npct then return end + + local Pos = self:GetPos() + local Ang = self:GetAngles() + local eyepos = EyePos() + local planeNormal = Ang:Up() + + Ang:RotateAroundAxis(Ang:Forward(), 90) + + if npct.namepos then + local bone = self:LookupBone(npct.nametobone or "") + if npct.nametobone and bone then + Pos = MCS.GetBoneOrientation(self, bone, Pos, Ang) + end + + Pos.z = Pos.z + (npct.namepos * self:GetModelScale()) + else + Pos.z = Pos.z + (77 * self:GetModelScale()) + end + + local relativeEye = eyepos - Pos + local relativeEyeOnPlane = relativeEye - planeNormal * relativeEye:Dot(planeNormal) + local textAng = relativeEyeOnPlane:AngleEx(planeNormal) + + textAng:RotateAroundAxis(textAng:Up(), 90) + textAng:RotateAroundAxis(textAng:Forward(), 90) + local hover = Vector(0, 0, -2) + if MCS.Config.NPCNameHover then + hover = Ang:Right() * math.sin(CurTime()) * 0.9 + end + + if not npct.noinfo then + cam.Start3D2D(Pos - hover, textAng, 0.1) + draw.RoundedBox(0, -self.names / 2 - 10, 0, self.names + 20, 35, MSD.Theme.m) + --MCS.Frame(-self.names / 2 - 10, 0, self.names + 20, 35, 10, MSD.Theme.d, color_white) + surface.SetDrawColor(255, 255, 255, 128) + surface.DrawOutlinedRect(-self.names / 2 - 10, 0, self.names + 20, 35, 2) + self.names = draw.SimpleText(self:GetNamer(), "MSDFont.32", 0, 0, color_white, TEXT_ALIGN_CENTER, 0) + cam.End3D2D() + end + + if not self.mdls then return end + for k, v in ipairs(self.mdls) do + if not IsValid(v) then continue end + if v.bone then + local pos, ang = MCS.GetBoneOrientation(self, v.bone, v.pos, v.ang, self:GetModelScale()) + ang:RotateAroundAxis(ang:Up(), v.ang.y) + ang:RotateAroundAxis(ang:Right(), v.ang.p) + ang:RotateAroundAxis(ang:Forward(), v.ang.r) + v:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z) + v:SetAngles(ang) + if v:GetParent() == NULL then + v:SetParent(self) + end + end + + if v.marge and v:GetParent() == NULL then + v:SetParent(self) + v:AddEffects(EF_BONEMERGE) + v:AddEffects(EF_BONEMERGE_FASTCULL) + v:AddEffects(EF_PARENT_ANIMATES) + end + end + end + + function ENT:OnRemove() + if not self.mdls then return end + for k, v in ipairs(self.mdls) do + if IsValid(v) then + v:Remove() + end + end + end +end diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/cl_init.lua b/addons/mc_simple_npcs/lua/mcs_npcs/cl_init.lua new file mode 100644 index 0000000..1bb34d6 --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/cl_init.lua @@ -0,0 +1,62 @@ +-- _______ __ _______ __ __ _______ ______ ______ +-- | | |.---.-..----.| |.-----. | __||__|.--------..-----.| |.-----. | | || __ \| |.-----. +-- | || _ || __| |_||__ --| |__ || || || _ || || -__| | || __/| ---||__ --| +-- |__|_|__||___._||____| |_____| |_______||__||__|__|__|| __||__||_____| |__|____||___| |______||_____| +-- |__| +--////////////////////////////////////////////////////////////////////////////////////////////////////////////// +--/////////////////////// Warning! Edit this only if you know what are you doing /////////////////////////////// +--////////////////////////////////////////////////////////////////////////////////////////////////////////////// +local blur = Material("pp/blurscreen") + +function MCS.Blur(panel, inn, density, alpha, w, h) + local x, y = panel:LocalToScreen(0, 0) + surface.SetDrawColor(255, 255, 255, alpha) + surface.SetMaterial(blur) + + for i = 1, 3 do + blur:SetFloat("$blur", (i / inn) * density) + blur:Recompute() + render.UpdateScreenEffectTexture() + + if w and h then + render.SetScissorRect(-x, -y, x + w, y + h, true) + surface.DrawTexturedRect(-x, -y, ScrW(), ScrH()) + render.SetScissorRect(0, 0, 0, 0, false) + else + surface.DrawTexturedRect(-x, -y, ScrW(), ScrH()) + end + end +end + +function MCS.Frame(x, y, w, h, lw, color, otcolor) + surface.SetDrawColor(color) + surface.DrawOutlinedRect(x, y, w, h) + surface.SetDrawColor(otcolor) + + if lw then + surface.DrawLine(x, y, x + lw, y) + surface.DrawLine(w + x - 1, y, w + x - lw - 1, y) + surface.DrawLine(x, y + h - 1, x + lw, y + h - 1) + surface.DrawLine(w + x - 1, y + h - 1, w + x - lw - 1, y + h - 1) + surface.DrawLine(x, y, x, y + lw) + surface.DrawLine(w + x - 1, y, w + x - 1, y + lw) + surface.DrawLine(x, y, x, y + lw) + surface.DrawLine(w + x - 1, y + h - 1, w + x - 1, y + h - lw - 1) + surface.DrawLine(x, y + h - 1, x, y + h - lw - 1) + end +end + +function MCS.GetBoneOrientation(ent, bone, pos, ang, scale) + + if !pos or !ang then return end + + if !bone then return pos, ang end + + local m = ent:GetBoneMatrix(bone) + + if (m) then + pos, ang = m:GetTranslation(), m:GetAngles() + end + + return pos, ang +end \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/cl_menu.lua b/addons/mc_simple_npcs/lua/mcs_npcs/cl_menu.lua new file mode 100644 index 0000000..7367ca5 --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/cl_menu.lua @@ -0,0 +1,562 @@ +-- _______ __ _______ __ __ _______ ______ ______ +-- | | |.---.-..----.| |.-----. | __||__|.--------..-----.| |.-----. | | || __ \| |.-----. +-- | || _ || __| |_||__ --| |__ || || || _ || || -__| | || __/| ---||__ --| +-- |__|_|__||___._||____| |_____| |_______||__||__|__|__|| __||__||_____| |__|____||___| |______||_____| +-- |__| +---------------------------------------------------------------------------------------------------------------- +------------------------- Warning! Edit this only if you know what are you doing ------------------------------- +---------------------------------------------------------------------------------------------------------------- + +net.Receive("MCS.OpenMenu", function() + local npc = net.ReadEntity() + if not IsValid(npc) then return end + local theme = MCS.Spawns[npc:GetUID()].theme + MCS.OpenDialogue(npc, theme) +end) + +MCS.OpenDialogue = function(npc, theme, test) + if not IsValid(npc) and not test then return end + + if not theme then + theme = "Default" + end + + if MCS.Dialogue then return end + + local npc_table = test or MCS.Spawns[npc:GetUID()] + + if not npc_table.dialogs then return end + + theme = MCS.Themes[theme] + theme.InProgress = true + local testmode = MCS.Config.DebugMode + + if test then + testmode = true + end + + MCS.Dialogue = vgui.Create("DFrame") + MCS.Dialogue:SetSize(ScrW(), ScrH()) + MCS.Dialogue:Center() + MCS.Dialogue:MakePopup() + MCS.Dialogue:SetDraggable(false) + MCS.Dialogue:ShowCloseButton(testmode) + MCS.Dialogue:SetTitle("") + MCS.Dialogue.History = {} + MCS.Dialogue.HtrProgress = 0 + MCS.Dialogue.Progress = 0 + MCS.Dialogue.ShowHint = true + MCS.Dialogue.NPC = npc + MCS.Dialogue.npc_table = npc_table + MCS.Dialogue.dialogs = npc_table.dialogs + + MCS.Dialogue.DoClose = function() + if theme.OnClose then + theme.OnClose(MCS.Dialogue) + end + + MCS.Dialogue:AlphaTo(0, 0.5, 0, function() + MCS.Dialogue:Close() + end) + end + + MCS.Dialogue.OnClose = function() + if MCS.Dialogue.Sound then + MCS.Dialogue.Sound:Stop() + MCS.Dialogue.Sound = nil + end + + MCS.Dialogue = nil + theme.InProgress = false + + if IsValid(npc) and npc:GetInputLimit() then + net.Start("MCS.CloseMenu") + net.WriteEntity(npc) + net.SendToServer() + end + end + + MCS.Dialogue.Paint = function(self, w, h) + theme.FramePaint(self, w, h) + end + + MCS.Dialogue.Think = function() + if theme.Process then + theme.Process(MCS.Dialogue) + end + end + + if theme.FadeIn then + MCS.Dialogue:SetAlpha(1) + MCS.Dialogue:AlphaTo(255, 0.3) + end + + theme.FrameBuild(MCS.Dialogue) + + MCS.Dialogue.OpenDialogue = function(dialog, id) + if IsValid(MCS.Dialogue.Sound) then + MCS.Dialogue.Sound:Stop() + MCS.Dialogue.Sound = nil + end + + MCS.Dialogue.HtrProgress = MCS.Dialogue.HtrProgress + 1 + + MCS.Dialogue.History[MCS.Dialogue.HtrProgress] = { + line = dialog["Line"], + ans = "" + } + + theme.FrameUpdate(MCS.Dialogue, dialog) + + if table.IsEmpty(dialog["Answers"]) then + theme.PopulateAns(MCS.Dialogue, 1, { "...", "close" }) + else + for k, ans in pairs(dialog["Answers"]) do + if ans[4] and not ans[4]() then continue end + theme.PopulateAns(MCS.Dialogue, k, ans, id) + end + end + + if npc_table.vos then + dialog["Sound"] = npc_table.vos(id) + end + + local dcalb = dialog["CallBack"] + + if dcalb and MCS.AddonList[dcalb.name] then + if MCS.AddonList[dcalb.name]["function_sv"] then + net.Start("MCS.Dialogue") + net.WriteUInt(id, 15) + net.WriteUInt(0, 15) + net.WriteEntity(npc) + net.SendToServer() + end + + if MCS.AddonList[dcalb.name]["function"] then + MCS.AddonList[dcalb.name]["function"](npc, dcalb.data) + end + end + + if dialog["Sound"] and dialog["Sound"] ~= "" then + local url = false + local soundpath = dialog["Sound"] + if string.StartWith(soundpath, "http") then + url = true + end + + if not string.StartWith(soundpath, "sound/") and not url then + soundpath = "sound/" .. soundpath + end + + if url then + sound.PlayURL(soundpath, "noplay", function(station) + if IsValid(MCS.Dialogue) and IsValid(station) then + MCS.Dialogue.Sound = station + MCS.Dialogue.Sound:Play() + MCS.Dialogue.Sound:SetVolume(dialog["SoundVolume"] or 1) + else + print("[MCS] Invalid sound URL", soundpath) + end + end) + else + sound.PlayFile(soundpath, "noplay", function(station, errCode, errStr) + if (IsValid(station)) then + MCS.Dialogue.Sound = station + MCS.Dialogue.Sound:Play() + MCS.Dialogue.Sound:SetVolume(dialog["SoundVolume"] or 1) + else + print("[MCS] Error playing sound", soundpath, errCode, errStr) + end + end) + end + end + end + + MCS.Dialogue.DoAnswer = function(ans, sid, mid) + if MCS.Dialogue:GetAlpha() < 255 then return end + MCS.Dialogue.History[MCS.Dialogue.HtrProgress].ans = ans[1] + + if isnumber(ans[2]) and MCS.Dialogue.dialogs[ans[2]] then + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[ans[2]], ans[2]) + elseif istable(ans[2]) then + if ans[2].id and MCS.AddonList[ans[2].id] and MCS.AddonList[ans[2].id]["enabled"] then + local id = ans[2].id + + if MCS.AddonList[id]["function"] then + MCS.AddonList[id]["function"](npc, ans[2].data) + end + + if MCS.AddonList[id]["function_sv"] then + net.Start("MCS.Dialogue") + net.WriteUInt(mid, 15) + net.WriteUInt(sid, 15) + net.WriteEntity(npc) + net.SendToServer() + end + + if ans[2].openid then + MCS.Dialogue.OpenDialogue(ans[2].openid) + return + end + + if not MCS.AddonList[id]["donotclose"] then + MCS.Dialogue.DoClose() + end + + return + end + + local random_v = ans[2][math.random(#ans[2])] + + if MCS.Dialogue.dialogs[random_v] then + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[random_v], random_v) + end + else + if MCS.AddonList[ans[2]] and MCS.AddonList[ans[2]]["enabled"] then + if MCS.AddonList[ans[2]]["function"] then + MCS.AddonList[ans[2]]["function"](npc) + end + + if MCS.AddonList[ans[2]]["function_sv"] then + net.Start("MCS.Dialogue") + net.WriteUInt(mid, 15) + net.WriteUInt(sid, 15) + net.WriteEntity(npc) + net.SendToServer() + end + end + + if MCS.AddonList[ans[2]] and MCS.AddonList[ans[2]]["donotclose"] then return end + MCS.Dialogue.DoClose() + end + + if ans[3] then + ans[3](npc) + end + + if npc and npc.GetInputLimit and npc:GetInputLimit() and ans[5] and isstring(ans[5]) then + net.Start("MCS.SrartAnimation") + net.WriteEntity(npc) + net.WriteString(ans[5]) + net.SendToServer() + end + end + + if not npc then + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[1], 1) + return + end + + local npc_t = MCS.Spawns[npc:GetUID()] + + if MQS and MQS.HasQuest() then + local q = MQS.HasQuest() + local obj = MQS.GetNWdata(LocalPlayer(), "quest_objective") + obj = MQS.Quests[q.quest].objects[obj] + + if obj.type == "Talk to NPC" and obj.npc == npc:GetUID() then + if npc_t and npc_t.quest_random then + local random_v = npc_t.quest_random[math.random(#npc_t.quest_random)] + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[random_v], random_v) + else + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[obj.dialog], obj.dialog) + end + return + end + end + + if npc_table.teams and npc_table.teams[LocalPlayer():Team()] then + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[npc_table.teams[LocalPlayer():Team()]], + npc_table.teams[LocalPlayer():Team()]) + return + end + + if npc_t and npc_t.random_dialog then + local random_v = npc_t.random_dialog[math.random(#npc_t.random_dialog)] + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[random_v], random_v) + else + MCS.Dialogue.OpenDialogue(MCS.Dialogue.dialogs[1], 1) + end +end + +MCS.Themes["Default"] = {} +local theme = MCS.Themes["Default"] + +theme.Colors = { + main = MCS.Config.Main.NPCColor, + sub = MCS.Config.Main.PlayerColor, + gray = Color(155, 155, 155), +} + +theme.Mats = { + grad = Material("gui/center_gradient.vtf"), +} + +theme.FadeIn = true +theme.InProgress = false +theme.DrawPlayer = true + +theme.Cam = { + pos = Vector(0, 0, 0), + ang = Angle(0, 0, 0), + fov = 90, + state = 1, + rotate = 210, + laste_change = 0, + state_change = 10, +} + +theme.Process = function(base) + local ent + local CT = CurTime() + local npc = base.NPC + local ply = LocalPlayer() + + if not IsValid(npc) then + ent = ply + theme.Cam.state = nil + end + + if theme.Cam.state == 1 then + ent = npc + else + ent = ply + end + + local head = ent:LookupBone("ValveBiped.Bip01_Head1") + if not head then return end + local sPos, sAng = ent:GetBonePosition(head) + if not sPos then return end + + if theme.Cam.laste_change < CT then + theme.Cam.laste_change = CT + 30 + theme.Cam.state_change = -theme.Cam.state_change + theme.Cam.rotate = math.Rand(20, 22) * theme.Cam.state_change + end + + sAng = Angle(0, ent:GetAngles().y, 0) + sAng:RotateAroundAxis(sAng:Up(), theme.Cam.rotate) + + if not base.AnimInit then + base.AnimInit = true + theme.Cam.pos = ply:EyePos() + theme.Cam.ang = ply:GetAngles() + end + + local tr = util.TraceLine({ + start = sPos, + endpos = sPos - sAng:Forward() * 45, + filter = ent + }) + + local speed = FrameTime() * 3 + if base.Closing then + tr.HitPos = ply:EyePos() + sAng = ply:GetAngles() + speed = FrameTime() * 8 + theme.DrawPlayer = false + end + + theme.Cam.pos = LerpVector(speed, theme.Cam.pos, tr.HitPos) + theme.Cam.ang = LerpAngle(speed, theme.Cam.ang, sAng) + theme.Cam.fov = Lerp(speed, theme.Cam.fov, theme.Cam.state == 1 and 55 or 90) +end + +theme.OnClose = function(base) + base.Closing = true +end + +theme.FramePaint = function(self, w, h) + if MCS.Config.Main.Vignette then + MSD.DrawTexturedRect(0, 0, w, h, MSD.Materials.vignette, color_black) + end +end + +theme.FrameBuild = function(base) + theme.DrawPlayer = true + theme.Colors = { + main = MCS.Config.Main.NPCColor, + sub = MCS.Config.Main.PlayerColor, + gray = Color(155, 155, 155), + } + + local sw, sh = base:GetWide(), base:GetTall() + local textspd = math.Clamp(MCS.Config.TextSpeed, 1, 10) + base.panelPLY = vgui.Create("MSDPanelList", base) + base.panelPLY:SetSize(sw - sw / 4, 200) + base.panelPLY:SetPos(sw / 6, sh - sh / 3 + 10) + base.panelPLY:EnableVerticalScrollbar() + base.panelPLY:EnableHorizontal(true) + base.panelPLY:SetSpacing(2) + base.panelPLY:SetAlpha(1) + base.panelPLY.IgnoreVbar = false + base.panelPLY.Paint = function(self, w, lh) + if MCS.Config.Main.Frame then + local h = self:GetCanvas():GetTall() + 5 + if h > lh then h = lh end + draw.RoundedBox(0, 0, 0, w, h, MSD.Theme.m) + MCS.Blur(self, 2, 2, 255, w, h) + --MCS.Frame(0, 0, w, h, 10, MSD.Theme.d, theme.Colors.gray) + surface.SetDrawColor(255, 255, 255, 128) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + end + + base.panelPLY.UpdateF = function() + base.panelPLY:Clear() + local plyName = vgui.Create("Panel") + + plyName.StaticScale = { + w = 1, + fixed_h = 45, + minw = 200, + minh = 45 + } + + plyName.Paint = function(self, w, h) + draw.DrawText(LocalPlayer():GetName(), "MSDFont.32", w - 1, 1, color_black, TEXT_ALIGN_RIGHT) + draw.DrawText(LocalPlayer():GetName(), "MSDFont.32", w, 0, theme.Colors.sub, TEXT_ALIGN_RIGHT) + end + + base.panelPLY:AddItem(plyName) + end + + base.panelNPC = vgui.Create("DPanel", base) + base.panelNPC:SetSize(sw - sw / 4, 55) + base.panelNPC:SetPos(sw / 6, sh - sh / 3) + base.panelNPC.line = "" + base.panelNPC.text = "" + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + + base.panelNPC.Paint = function(self, w, h) + if MCS.Config.Main.Frame then + draw.RoundedBox(0, 0, 0, w, h, MSD.Theme.m) + MCS.Blur(self, 2, 2, 255, w, h) + -- MCS.Frame(0, 0, w, h, 10, MSD.Theme.d, theme.Colors.gray) + surface.SetDrawColor(255, 255, 255, 128) + surface.DrawOutlinedRect(0, 0, w, h, 2) + end + + draw.DrawText(base.npc_table.name, "MSDFont.32", 11, 1, color_black, TEXT_ALIGN_LEFT) + draw.DrawText(base.npc_table.name, "MSDFont.32", 10, 0, theme.Colors.main, TEXT_ALIGN_LEFT) + local text = MSD.TextWrap(self.line, "MSDFont.25", w - 15) + draw.DrawText(text, "MSDFont.25", 11, 46, color_black, TEXT_ALIGN_LEFT) + draw.DrawText(text, "MSDFont.25", 10, 45, color_white, TEXT_ALIGN_LEFT) + local _, th = surface.GetTextSize(text) + + if 55 + th > h then + self:SetSize(w, 55 + th) + local y = ScrH() / 2 + local sx = base.panelPLY:GetPos() + base.panelPLY:SetSize(base.panelPLY:GetWide(), y - (self:GetTall()) - 35) + base.panelPLY:SetPos(sx, y + self:GetTall() + 25) + end + end + + base.panelNPC.Think = function(self) + local CT = CurTime() + + if (CT > self.typetime) and self.text ~= self.line and base:GetAlpha() > 150 then + self.typepos = self.typepos + 1 + self.typetime = CT + FrameTime() * textspd + self.line = string.subUTF8(self.text, 1, self.typepos) + + if input.IsMouseDown(MOUSE_FIRST) then + self.typetime = CT + 10 + self.typepos = string.len(self.text) + self.line = self.text + end + end + + if self.text == self.line and base.panelPLY and not self.changed then + self.changed = true + theme.Cam.state = 2 + base.panelPLY:AlphaTo(255, 0.3, 0.3) + base.panelNPC:MoveTo(sw / 6, sh / 2, 0.3) + end + end +end + +theme.FrameUpdate = function(base, dialog) + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + base.panelNPC.changed = nil + base.panelNPC.text = dialog["Line"] + base.panelNPC.line = "" + base.panelNPC:SetTall(55) + base.panelPLY:SetAlpha(0) + base.panelPLY.UpdateF() + theme.Cam.laste_change = CurTime() + 30 + theme.Cam.state = 1 +end + +theme.PopulateAns = function(base, k, ans, mid) + local button = vgui.Create("DButton", bg) + + button.StaticScale = { + w = 1, + fixed_h = 27, + minw = 200, + minh = 27 + } + + button:SetText("") + button.fade = 0 + + button.Paint = function(self, w, h) + if self.hover then + self.fade = Lerp(FrameTime() * 7, self.fade, 1) + else + self.fade = Lerp(FrameTime() * 7, self.fade, 0) + end + + local text, _, texth = MSD.TextWrap(ans[1], "MSDFont.25", w - 5) + + if texth > self.StaticScale.minh and w > 200 then + self.StaticScale.minh = texth + self.StaticScale.fixed_h = texth + base.panelPLY:PerformLayout() + end + + draw.DrawText(text, "MSDFont.25", w - 1, 1, color_black, TEXT_ALIGN_RIGHT) + draw.DrawText(text, "MSDFont.25", w, 0, MSD.ColorAlpha(color_white, 255 - self.fade * 255), TEXT_ALIGN_RIGHT) + draw.DrawText(text, "MSDFont.25", w, 0, MSD.ColorAlpha(theme.Colors.main, self.fade * 255), TEXT_ALIGN_RIGHT) + end + + button.DoClick = function() + if base.panelPLY:GetAlpha() > 200 then + base.DoAnswer(ans, k, mid) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + base.panelPLY:AddItem(button) +end + +hook.Add("HUDShouldDraw", "MCS.DefaultTheme.HideHUD", function() + if theme.InProgress then return false end +end) + +hook.Add("CalcView", "MCS.DefaultTheme.CalcView", function(ply, pos, angles, fov) + if not MCS.Config.EnableCamera then return end + if theme.InProgress then + local view = { + origin = theme.Cam.pos, + angles = theme.Cam.ang, + fov = theme.Cam.fov, + drawviewer = theme.DrawPlayer + } + + return view + else + theme.Cam.fov = fov + end +end) diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/cl_util.lua b/addons/mc_simple_npcs/lua/mcs_npcs/cl_util.lua new file mode 100644 index 0000000..c8b0b13 --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/cl_util.lua @@ -0,0 +1,1069 @@ +-- _______ __ _______ __ __ _______ ______ ______ +-- | | |.---.-..----.| |.-----. | __||__|.--------..-----.| |.-----. | | || __ \| |.-----. +-- | || _ || __| |_||__ --| |__ || || || _ || || -__| | || __/| ---||__ --| +-- |__|_|__||___._||____| |_____| |_______||__||__|__|__|| __||__||_____| |__|____||___| |______||_____| +-- |__| +--////////////////////////////////////////////////////////////////////////////////////////////////////////////// +--/////////////////////// Warning! Edit this only if you know what are you doing /////////////////////////////// +--////////////////////////////////////////////////////////////////////////////////////////////////////////////// +local defnpc = { + uid = "uid", + name = "Alyx", + model = "models/alyx.mdl", + questNPC = false, + pos = { + ["all"] = {Vector(0, 0, 0), Angle(0, 0, 0)} + }, + bgr = {}, + skin = 0, + scale = 1, + uselimit = false, + theme = "Default", + namepos = 80, + sequence = "", + CLModels = "", + dialogs = {} +} +local modulid = 0 +local defclm = { + model = "models/player/items/humans/top_hat.mdl", + marge = true +} + +local Ln = MSD.GetPhrase +local localnpc = table.Copy(defnpc) +MCS.PanelControll = {} + +MCS.PanelControll.AnimationMenu = function(parent, te, ent, setanim) + if IsValid(MCS.AnimationMenu) then MCS.AnimationMenu:Close() end + local mx, my = gui.MousePos() + local frame = vgui.Create("MSDSimpleFrame", parent) + frame:SetSize(400, 600) + frame:SetPos(mx - 400, my) + frame:SetDraggable(true) + frame:MakePopup() + frame.Paint = function(self, w, h) + MSD.DrawBG(self, w, h) + end + frame.Think = function(self) + if not IsValid(parent) then + self:Close() + end + end + + frame.clsBut = MSD.IconButton(frame, MSD.Icons48.cross, frame:GetWide() - 34, 1, 25, nil, MSD.Config.MainColor.p, function() + frame:Close() + end) + + -- if file.Exists("mac_npc/autosave.txt", "DATA") then + -- jsontable = file.Read("mac_npc/autosave.txt", "DATA") + -- localnpc = util.JSONToTable(jsontable) + -- end + + local AnimList = vgui.Create("MSDPanelList", frame) + AnimList:SetSize(390, 540) + AnimList:SetPos(5, 40) + AnimList:EnableVerticalScrollbar() + AnimList.IgnoreVbar = false + + MSD.TextEntry(frame, 5, 5, 350, 35, "Search", "", "", function(self, value) + AnimList.Search(string.lower(value)) + end, true) + + local seq_list = ent:GetSequenceList() or {} + + AnimList.Search = function(val) + AnimList:Clear() + for k, v in pairs(seq_list) do + local seq = string.lower(v) + if val and not string.find(seq, val) then continue end + local but = MSD.ButtonSimple(AnimList, nil, nil, 300, 25, seq, 16, function() + if te then + te:SetValue(seq) + end + + if setanim then + ent:ResetSequence(seq) + ent:SetCycle(0) + localnpc.sequence = seq + end + end) + + but.Check = function() + if localnpc.sequence == seq then + return true + end + return false + end + + if localnpc.sequence == seq then + but.hover = true + AnimList:ScrollToChild(but) + end + end + end + + AnimList.Search() + + MCS.AnimationMenu = frame + return frame +end + +MCS.PanelControll.FileOpen = function(parent, func) + local mx, my = gui.MousePos() + local frame = vgui.Create("DFrame") + frame:SetSize(300, 400) + frame:SetPos(mx, my) + frame:MakePopup() + frame:SetTitle("File List") + frame.StartT = CurTime() + 2 + + frame.Think = function(self) + if not IsValid(parent) or (not self:HasFocus() and self.StartT < CurTime()) then + self:Close() + end + end + + local FileList = vgui.Create("DListView", frame) + FileList:AddColumn("File name") + FileList:Dock(FILL) + FileList:SetMultiSelect(false) + FileList:SetHideHeaders(false) + local files = file.Find("mac_npc/*", "DATA") + + for k, v in pairs(files) do + local line = FileList:AddLine(v) + + line.OnSelect = function() + jsontable = file.Read("mac_npc/" .. v, "DATA") + localnpc = util.JSONToTable(jsontable) + frame:Close() + func() + end + end + + return frame +end + +net.Receive("MCS.SetupMenu", function() + if not LocalPlayer():IsSuperAdmin() then return end + MSD.OpenMenuManager(nil, modulid) +end) + +function MSD.OpenAdminMenu(panel, mainPanel) + if not panel then return end + + local function AutoSave() + file.Write("mac_npc/autosave.txt", util.TableToJSON(localnpc)) + end + + if not file.Exists("mac_npc", "DATA") then + file.CreateDir("mac_npc") + end + + if file.Exists("mac_npc/autosave.txt", "DATA") then + jsontable = file.Read("mac_npc/autosave.txt", "DATA") + localnpc = util.JSONToTable(jsontable) + end + + function mainPanel.ModuleSwitch() + AutoSave() + end + + panel.Canvas = vgui.Create("MSDPanelList", panel) + panel.Canvas:SetSize(panel:GetWide() - 252, panel:GetTall()) + panel.Canvas:SetPos(252, 0) + panel.Canvas:EnableVerticalScrollbar() + panel.Canvas:EnableHorizontal(true) + panel.Canvas:SetSpacing(2) + panel.Canvas.IgnoreVbar = true + panel.Canvas.Paint = function() end + local pages = {} + local cur_page = nil + + local function openPage(id, animate, ...) + panel.Canvas:Clear() + back_page = cur_page + pages[id](...) + cur_page = id + + if animate then + panel.Canvas:SetAlpha(1) + panel.Canvas:AlphaTo(255, 0.2) + end + end + + local buttons = {} + + buttons[1] = { + "NPC setup", MSD.Icons48.account, function() + openPage("npc", true) + end, + true + } + + buttons[2] = { + "Model setup", MSD.Icons48.box, function() + openPage("model", true) + end + } + + buttons[3] = { + "Dialogue setup", MSD.Icons48.layers, function() + openPage("dialog", true) + end + } + + buttons[4] = { + "Final setup", MSD.Icons48.submit, function() + openPage("final", true) + end + } + + buttons[5] = { + Ln("settings"), MSD.Icons48.cog, function() + openPage("settings", true) + end + } + + panel.Menu = vgui.Create("MSDPanelList", panel) + panel.Menu:SetSize(250, panel:GetTall()) + panel.Menu:SetPos(0, 0) + panel.Menu:EnableVerticalScrollbar() + panel.Menu:EnableHorizontal(false) + panel.Menu:SetSpacing(0) + panel.Menu.IgnoreVbar = true + + panel.Menu.Paint = function(self, w, h) + draw.RoundedBox(0, 0, 0, w, h, MSD.Theme["l"]) + end + + panel.Menu.Deselect = function(but) + but.hovered = true + + for k, v in pairs(panel.Menu:GetItems()) do + if v and v:IsValid() and v ~= but then + v.hovered = false + end + end + end + + local MainM = panel + localnpc.oldid = localnpc.uid + + pages["npc"] = function() + panel.Canvas:Clear() + MSD.Header(panel.Canvas, "NPC Editor") + + MSD.TextEntry(panel.Canvas, "static", nil, 1, 50, "Enter npc name", "NPC name: (NPC's name will be shown to players above the NPC and via the dialogue.)", localnpc.name, function(self, value) + localnpc.name = value + end, true) + + MSD.TextEntry(panel.Canvas, "static", nil, 1, 50, "Enter NPC ID", "Unique name: (Unique name must be UNIQUE. Two or more NPCs with identical unique name will not work.)", localnpc.uid, function(self, value) + if value == "" or value == " " then + self.error = "Enter the ID" + + return + end + + localnpc.uid = value + + if MCS.Spawns[value] and localnpc.oldid ~= value then + self.error = "ID must be unique for each NPC" + + return + end + + self.error = nil + end, true) + + MSD.TextEntry(panel.Canvas, "static", nil, MQS and 2 or 1, 50, "Leave blank to use default value", "NPC's name position: (NPC's name position can be useful if you use bigger or smaller model than the standat player models.)", localnpc.namepos, function(self, value) + localnpc.namepos = tonumber(value) or 77 + end, true, nil, nil, true) + + if MQS then + MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, "[MQS] Use this NPC for the quest system", localnpc.questNPC or false, function(self, value) + localnpc.questNPC = value + end) + end + + MSD.BoolSlider(panel.Canvas, "static", nil, 2, 50, "Allow only one player to speak to the npc at the same time", localnpc.uselimit or false, function(self, value) + localnpc.uselimit = value + end) + + local combo = MSD.ComboBox(panel.Canvas, "static", nil, 2, 50, "UI theme:", localnpc.theme or "Default") + combo.OnSelect = function(_, _, value) + localnpc.theme = value + end + + for k, v in pairs(MCS.Themes) do + combo:AddChoice(k) + end + + local map = string.lower(game.GetMap()) + + if not localnpc.pos[map] then + localnpc.pos[map] = {Vector(0, 0, 0), Angle(0, 0, 0)} + end + + local data = localnpc.pos[map] + local vecd = MSD.VectorDisplay(panel.Canvas, "static", nil, 1, 50, "Spawn point", data[1]) + local amgl = MSD.AngleDisplay(panel.Canvas, "static", nil, 1, 50, "Spawn angle", data[2]) + + MSD.Button(panel.Canvas, "static", nil, 3, 50, "Set to your position", function() + local vec = LocalPlayer():GetPos() + vecd.vector = vec + data[1] = vec + local ang = Angle(0, LocalPlayer():GetAngles().y, 0) + amgl.angle = ang + data[2] = ang + end) + + MSD.Button(panel.Canvas, "static", nil, 3, 50, "Set to looking poit", function() + local vec = LocalPlayer():GetEyeTrace().HitPos + if not vec then return end + vecd.vector = vec + data[1] = vec + local ang = Angle(0, LocalPlayer():GetAngles().y, 0) + amgl.angle = ang + data[2] = ang + end) + + MSD.Button(panel.Canvas, "static", nil, 3, 50, "Copy from looking entity", function() + local vec = LocalPlayer():GetEyeTrace().Entity + if not vec then return end + local ang = vec:GetAngles() + amgl.angle = ang + data[2] = ang + vec = vec:GetPos() + vecd.vector = vec + data[1] = vec + end) + end + + pages["model"] = function() + if not localnpc.model then + localnpc.model = "models/Humans/Group01/Male_01.mdl" + end + + panel.Canvas:Clear() + MSD.Header(panel.Canvas, "Model Editor") + + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 52) + ops_panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.RoundedBox(MSD.Config.Rounded, w / 2, 0, w / 2 - 2, h, MSD.Theme["l"]) + end + + local model_panel, set_list + set_list = vgui.Create("MSDPanelList", ops_panel) + set_list:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall()) + set_list:SetPos(0, 0) + set_list:EnableVerticalScrollbar() + set_list:EnableHorizontal(true) + set_list:SetSpacing(2) + set_list:SetPadding(0) + set_list.IgnoreVbar = true + + local updatemdl = function(value) + if value == "" then return end + model_panel:SetModel(value) + if model_panel.Entity then + model_panel.Entity:ResetSequence("idle") + model_panel.Entity:SetPos(Vector(-100, 0, -61)) + end + animation:SetText("idle") + localnpc.model = value + localnpc.skin = 1 + localnpc.bgr = {} + + model_panel.Update(true) + end + + set_list.MainMenu = function() + set_list:Clear() + + MSD.InfoHeader(set_list, "Drag model to rotate, double click to zoom") + + local mdl = MSD.TextEntry(set_list, "static", nil, 1.5, 50, "Enter model path", "NPC model:", localnpc.model, function(self, value) + updatemdl(value) + end, true) + + MSD.Button(set_list, "static", nil, 3, 50, "Copy from looking entity", function() + local md = LocalPlayer():GetEyeTrace().Entity + if not md then return end + md = md:GetModel() + mdl:SetText(md) + updatemdl(md) + end) + + animation = MSD.TextEntry(set_list, "static", nil, 1.5, 50, "Enter animation name", "NPC animation:", localnpc.sequence or "idle", function(self, value) + local iSeq = model_panel.Entity:LookupSequence(value) + if (iSeq > 0) then + model_panel.Entity:ResetSequence(iSeq) + end + localnpc.sequence = string.lower(value) + end, true) + + MSD.Button(set_list, "static", nil, 3, 50, "Get animations", function() + MCS.PanelControll.AnimationMenu(panel, animation, model_panel.Entity, true) + end) + + MSD.BoolSlider(set_list, "static", nil, 1, 50, "Make NPC invisible", localnpc.invisible, function(self, value) + localnpc.invisible = value + end) + + MSD.VolumeScale(set_list, "static", nil, 1, 50, "NPC Model Scale", localnpc.scale or 1, function(self, var) + localnpc.scale = var + end) + + MSD.BigButton(set_list, "static", nil, 1, 80, "Body Groups", MSD.Icons48.account_edit, function() + set_list.BodyGroups() + end) + + MSD.BigButton(set_list, "static", nil, 1, 80, "Cosmetics", MSD.Icons48.box, function() + set_list.Cosmetics() + end) + end + + set_list.BodyGroups = function() + set_list:Clear() + MSD.Header(set_list, "Body Groups", function() + set_list.MainMenu() + end) + + local nskins = model_panel.Entity:SkinCount() - 1 + + if (nskins > 0) then + MSD.InfoHeader(set_list, "Skin") + + MSD.NumberWang(set_list, "static", nil, 1, 50, 0, nskins, localnpc.skin or 1, "Skin", function(self) + model_panel.Entity:SetSkin(self:GetValue()) + localnpc.skin = self:GetValue() + end) + end + + local hrd + + for k = 0, model_panel.Entity:GetNumBodyGroups() - 1 do + if (model_panel.Entity:GetBodygroupCount(k) <= 1) then continue end + + if not hrd then + hrd = MSD.InfoHeader(set_list, "Body Groups") + end + + MSD.NumberWang(set_list, "static", nil, 2, 50, 0, model_panel.Entity:GetBodygroupCount(k) - 1, localnpc.bgr[k] or 0, model_panel.Entity:GetBodygroupName(k), function(self) + model_panel.Entity:SetBodygroup(k, self:GetValue()) + localnpc.bgr[k] = self:GetValue() + end) + end + end + + set_list.MainMenu() + + model_panel = vgui.Create("MSDModelPanel", ops_panel) + model_panel:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall()) + model_panel:SetPos(ops_panel:GetWide() / 2, 0) + model_panel:SetModel(localnpc.model) + model_panel.Entity:ResetSequence(localnpc.sequence or "idle") + model_panel.Entity:SetPos(Vector(-100, 0, -61)) + + model_panel.CreateItem = function(model, k, data) + + model_panel.SubEntitys[k] = ClientsideModel( model, RENDERGROUP_OTHER ) + model_panel.SubEntitys[k]:SetNoDraw( true ) + model_panel.SubEntitys[k]:SetParent(model_panel.Entity) + + if data.marge then + model_panel.SubEntitys[k]:AddEffects(EF_BONEMERGE) + model_panel.SubEntitys[k]:AddEffects(EF_BONEMERGE_FASTCULL) + model_panel.SubEntitys[k]:AddEffects(EF_PARENT_ANIMATES) + else + model_panel.SubEntitys[k]:SetPos(model_panel.Entity:GetPos() + data.pos) + model_panel.SubEntitys[k]:SetAngles(model_panel.Entity:GetAngles() + data.ang) + model_panel.SubEntitys[k].pos = data.pos + model_panel.SubEntitys[k].ang = data.ang + model_panel.SubEntitys[k].bone = data.bone + -- local mat = Matrix() + -- mat:Scale(Vector(data.scale,data.scale,data.scale)) + -- model_panel.SubEntitys[k]:EnableMatrix("RenderMultiply", mat) + end + + end + + + model_panel.Update = function(rmv) + for k,v in pairs(model_panel.SubEntitys) do + if IsValid(v) then + v:Remove() + end + end + if rmv then + localnpc.ClModels = {} + return + end + if localnpc.ClModels then + for k, v in pairs(localnpc.ClModels) do + model_panel.CreateItem(v.model, k, v) + end + end + end + + set_list.Cosmetics = function() + set_list:Clear() + MSD.Header(set_list, "Cosmetics", function() + set_list.MainMenu() + end) + + MSD.BigButton(set_list, "static", nil, 1, 80, "Add cosmetics", MSD.Icons48.plus, function() + if not localnpc.ClModels then localnpc.ClModels = {} end + table.insert(localnpc.ClModels, table.Copy(defclm)) + model_panel.Update() + set_list.Cosmetics() + end) + + if not localnpc.ClModels then return end + + for k, v in pairs(localnpc.ClModels) do + MSD.BigButton(set_list, "static", nil, 1, 80, v.model, MSD.Icons48.box, function() + set_list.PropEdit(k, v) + end, nil, nil, function(self) + if (IsValid(self.Menu)) then self.Menu:Remove() self.Menu = nil end + self.Menu = MSD.MenuOpen(false, self) + self.Menu:AddOption(Ln("remove"), function() + table.remove(localnpc.ClModels, k) + set_list.Cosmetics() + end) + local x, y = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(x, y, false, self) + end) + end + end + + set_list.PropEdit = function(k, v) + set_list:Clear() + MSD.Header(set_list, "Cosmetic editor", function() + set_list.Cosmetics() + end) + + MSD.TextEntry(set_list, "static", nil, 1, 50, "Enter model path", "Model:", v.model, function(self, value) + v.model = value + if value == "" then return end + if IsValid(model_panel.SubEntitys[k]) then + model_panel.SubEntitys[k]:SetModel(value) + end + end, true) + + MSD.BoolSlider(set_list, "static", nil, 1, 50, "Enable BoneMarge effect", v.marge, function(self, value) + v.marge = value + if value then + v.pos = nil + v.ang = nil + v.bone = nil + else + v.pos = Vector(0,0,0) + v.ang = Angle(0,0,0) + v.bone = 0 + end + set_list.PropEdit(k, v) + model_panel.Update() + end) + + if v.marge then return end + + MSD.VectorDisplay(set_list, "static", nil, 1, 50, Ln("spawn_point"), v.pos, function() end) + MSD.AngleDisplay(set_list, "static", nil, 1, 50, Ln("spawn_ang"), v.ang, function() end) + + local combo = MSD.ComboBox(set_list, "static", nil, 1, 50, "Attachment bone" .. ":", model_panel.Entity:GetBoneName( v.bone or 0 )) + combo.OnSelect = function(self, index, text, data) + v.bone = data + model_panel.SubEntitys[k].bone = data + end + for i = 0, model_panel.Entity:GetBoneCount() - 1 do + combo:AddChoice(model_panel.Entity:GetBoneName( i ), i) + end + + end + + if localnpc.ClModels then + for k, v in pairs(localnpc.ClModels) do + model_panel.CreateItem(v.model, k, v) + end + end + + panel.Canvas:AddItem(ops_panel) + end + + pages["dialog"] = function() + panel.Canvas:Clear() + MSD.Header(panel.Canvas, "Dialogue Editor") + + MSD.BigButton(panel.Canvas, "static", nil, 4, 120, "Add new line", MSD.Icons48.layers_plus, function() + table.insert(localnpc.dialogs, { + ["Line"] = "", + ["Sound"] = "", + ["Answers"] = { + {"", "close", nil, nil, ""} + }, + }) + + openPage("dialog") + end) + + for k, v in pairs(localnpc.dialogs) do + MSD.BigButton(panel.Canvas, "static", nil, 4, 120, "Dialog Line:" .. k, MSD.Icons48.layers, function() + openPage("line_edit", true, k, v) + end) + end + end + + pages["line_edit"] = function(k, v) + MSD.Header(panel.Canvas, "Dialog like #" .. k, function() + openPage("dialog", true) + end) + + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 52) + + ops_panel.Paint = function(self, w, h) + draw.RoundedBox(0, 0, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.RoundedBox(0, w / 2, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.DrawText(MSD.GetPhrase("Line options"), "MSDFont.25", 12, 12, color_white, TEXT_ALIGN_LEFT) + draw.DrawText(MSD.GetPhrase("Answers"), "MSDFont.25", w / 2 + 12, 12, color_white, TEXT_ALIGN_LEFT) + end + + local rwd_set, rwd_list + rwd_list = vgui.Create("MSDPanelList", ops_panel) + rwd_list:SetSize(ops_panel:GetWide() / 2, ops_panel:GetTall() - 52) + rwd_list:SetPos(0, 52) + rwd_list:EnableVerticalScrollbar() + rwd_list:EnableHorizontal(true) + rwd_list:SetSpacing(2) + rwd_list:SetPadding(2) + rwd_list.IgnoreVbar = true + + rwd_list.Populate = function() + rwd_list:Clear() + + MSD.TextEntry(rwd_list, "static", nil, 1, 350, "Enter NPC speech line", nil, localnpc.dialogs[k]["Line"], function(self, value) + localnpc.dialogs[k]["Line"] = value + end, true, nil, true) + + MSD.TextEntry(rwd_list, "static", nil, 1, 50, "Enter sound path or URL", "Sound: (path or URL)", localnpc.dialogs[k]["Sound"], function(self, value) + localnpc.dialogs[k]["Sound"] = value + end, true) + + if k ~= 1 then + MSD.BigButton(rwd_list, "static", nil, 1, 120, "Remove this dialogue line", MSD.Icons48.layers_remove, function() + table.remove(localnpc.dialogs, k) + openPage("dialog", true) + end) + end + end + + rwd_set = vgui.Create("MSDPanelList", ops_panel) + rwd_set:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall() - 52) + rwd_set:SetPos(ops_panel:GetWide() / 2, 52) + rwd_set:EnableVerticalScrollbar() + rwd_set:EnableHorizontal(true) + rwd_set:SetSpacing(2) + rwd_set:SetPadding(2) + rwd_set.IgnoreVbar = true + rwd_set.MakeAnswer = function(c, a) + MSD.InfoHeader(rwd_set, "Answer ID: " .. c) + + MSD.TextEntry(rwd_set, "static", nil, 1, 50, "Enter answer text", "Answer text:", a[1], function(self, value) + a[1] = value + end, true) + + MSD.Button(rwd_set, "static", nil, localnpc.uselimit and 3 or 2, 50, isnumber(a[2]) and "Open line: " .. a[2] or "Action: " .. a[2], function(self) + local cdmenu = MSD.MenuOpen(false, self) + + for i = 1, #localnpc.dialogs do + cdmenu:AddOption("#" .. i, function() + a[2] = i + self:SetText("Open line: " .. i) + end) + end + + cdmenu:AddOption("close", function() + a[2] = "close" + self:SetText("Action: close") + end) + + local cdsub = cdmenu:AddSubMenu("Supportet addons:") + + for id, addon in pairs(MCS.AddonList) do + if not addon["enabled"] or addon["hide"] then continue end + cdsub:AddOption(id, function() + a[2] = '"' .. id .. '"' + self:SetText("Action: " .. a[2]) + end) + end + + cdmenu:Open() + end) + + if localnpc.uselimit then + MSD.TextEntry(rwd_set, "static", nil, 3, 50, "Leave blank to disable", "Change animation:", a[5] or "", function(self, value) + a[5] = value + end, true) + end + + MSD.Button(rwd_set, "static", nil, localnpc.uselimit and 3 or 2, 50, "Remove answer", function() + table.remove(v["Answers"], c) + rwd_set.Populate() + end) + end + rwd_set.Populate = function() + rwd_set:Clear() + + for c, a in pairs(v["Answers"]) do + rwd_set.MakeAnswer(c, a) + end + + MSD.BigButton(rwd_set, "static", nil, 1, 80, "Add answer line", MSD.Icons48.layers_plus, function() + table.insert(v["Answers"], {"", "close"}) + + rwd_set.Populate() + end) + end + + panel.Canvas:AddItem(ops_panel) + rwd_list.Populate() + rwd_set.Populate() + end + + pages["final"] = function() + MSD.Header(panel.Canvas, "Main options") + + MSD.BigButton(panel.Canvas, "static", nil, 2, 120, "Copy NPC Lua code", MSD.Icons48.copy, function() + surface.PlaySound("mqs/done/1.mp3") + RunConsoleCommand("mcs_generate") + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 120, "Save NPC preset", MSD.Icons48.save, function() + surface.PlaySound("mqs/done/4.mp3") + file.Write("mac_npc/" .. localnpc.uid .. ".txt", util.TableToJSON(localnpc)) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 120, "Open NPC preset", MSD.Icons48.folder_open, function() + if not IsValid(oframe) then + oframe = MCS.PanelControll.FileOpen(MainM, function() + surface.PlaySound("mqs/done/1.mp3") + end) + end + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 120, "New NPC preset", MSD.Icons48.account_plus, function() + surface.PlaySound("mqs/done/1.mp3") + localnpc = table.Copy(defnpc) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 120, "Test the dialogue", MSD.Icons48.calendar_check, function() + if localnpc.theme and localnpc.theme ~= "Default" and MCS.Themes[localnpc.theme] then + if MCS.Themes[localnpc.theme].Menu then + MCS.Themes[localnpc.theme].Menu(nil, localnpc) + elseif MCS.Themes[localnpc.theme].Paint then + MCS.OpenDialogue(nil, localnpc.theme, localnpc) + else + MCS.OpenDialogue(nil, nil, localnpc) + end + else + MCS.OpenDialogue(nil, nil, localnpc) + end + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 120, "Respawn all NPCs", MSD.Icons48.account_convert, function() + surface.PlaySound("mqs/done/1.mp3") + RunConsoleCommand("mcs_npcrespawn") + end) + + MSD.Header(panel.Canvas, "Help") + + MSD.BigButton(panel.Canvas, "static", nil, 2, 80, "Full documentation", MSD.Icons48.file_document, function() + gui.OpenURL("http://info.macnco.one/books/macs-simple-npcs") + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 80, "Get support", MSD.Icons48.face_agent, function() + gui.OpenURL("https://www.gmodstore.com/market/view/6696/") + end) + end + + pages["settings"] = function() + local oldcfg = table.Copy(MCS.Config) + MSD.Header(panel.Canvas, Ln("settings")) + local ops_panel = vgui.Create("DPanel") + ops_panel:SetSize(panel.Canvas:GetWide(), panel.Canvas:GetTall() - 135) + + ops_panel.Paint = function(self, w, h) + draw.RoundedBox(0, 0, 0, w / 2 - 2, h, MSD.Theme["l"]) + draw.RoundedBox(0, w / 2, 0, w / 2 - 2, h, MSD.Theme["l"]) + end + + local rwd_set, rwd_list + rwd_list = vgui.Create("MSDPanelList", ops_panel) + rwd_list:SetSize(ops_panel:GetWide() / 2, ops_panel:GetTall() - 2) + rwd_list:SetPos(0, 2) + rwd_list:EnableVerticalScrollbar() + rwd_list:EnableHorizontal(true) + rwd_list:SetSpacing(2) + rwd_list:SetPadding(2) + rwd_list.IgnoreVbar = true + + rwd_list.Populate = function() + rwd_list:Clear() + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("Default Theme settings"), function() + rwd_set.Populate("def_ui") + end) + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("Retro Theme settings"), function() + rwd_set.Populate("ret_ui") + end) + + MSD.Button(rwd_list, "static", nil, 1, 50, Ln("set_server"), function() + rwd_set.Populate("server") + end) + end + + rwd_set = vgui.Create("MSDPanelList", ops_panel) + rwd_set:SetSize(ops_panel:GetWide() / 2 - 2, ops_panel:GetTall() - 2) + rwd_set:SetPos(ops_panel:GetWide() / 2, 2) + rwd_set:EnableVerticalScrollbar() + rwd_set:EnableHorizontal(true) + rwd_set:SetSpacing(2) + rwd_set:SetPadding(2) + rwd_set.IgnoreVbar = true + rwd_set.SetingList = {} + + rwd_set.Populate = function(seting) + if not rwd_set.SetingList[seting] then return end + rwd_set:Clear() + rwd_set.SetingList[seting]() + last_sm = seting + end + + rwd_set.SetingList["def_ui"] = function() + MSD.Header(rwd_set, Ln("Default Theme")) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, "Enable dialogue camera animations", MCS.Config.EnableCamera, function(self, value) + MCS.Config.EnableCamera = value + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, "Enable dialogue frames", MCS.Config.Main.Frame, function(self, value) + MCS.Config.Main.Frame = value + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, Ln("set_ui_vignette"), MCS.Config.Main.Vignette, function(self, value) + MCS.Config.Main.Vignette = value + end) + + MSD.ColorSelector(rwd_set, "static", nil, 1, 50, "NPC name color", MCS.Config.Main.NPCColor, function(self, color) + MCS.Config.Main.NPCColor = color + end) + + MSD.ColorSelector(rwd_set, "static", nil, 1, 50, "Player's name color", MCS.Config.Main.PlayerColor, function(self, color) + MCS.Config.Main.PlayerColor = color + end) + end + + rwd_set.SetingList["ret_ui"] = function() + MSD.Header(rwd_set, Ln("Default Theme")) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, "Enable dialogue camera animations", MCS.Config.EnableCamera, function(self, value) + MCS.Config.EnableCamera = value + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, "Enable sound effects", MCS.Config.Retro.SFX, function(self, value) + MCS.Config.Retro.SFX = value + end) + + MSD.InfoHeader(rwd_set, "Accent color") + local spanel = vgui.Create("DPanel") + spanel.StaticScale = { w = 1, fixed_h = 15, minw = 150, minh = 15 } + spanel.Paint = function(self, w, h) + draw.RoundedBox(0, 0, 0, w, h, MCS.Config.Retro.Color) + end + rwd_set:AddItem(spanel) + + MSD.VolumeSlider(rwd_set, "static", nil, 3, 50, "red", MCS.Config.Retro.Color.r / 255, function(self, var) + local c = math.Round(var * 255) + MCS.Config.Retro.Color.r = c + end, Color(255, 0, 0)) + + MSD.VolumeSlider(rwd_set, "static", nil, 3, 50, "green", MCS.Config.Retro.Color.g / 255, function(self, var) + local c = math.Round(var * 255) + MCS.Config.Retro.Color.g = c + end, Color(0, 255, 0)) + + MSD.VolumeSlider(rwd_set, "static", nil, 3, 50, "blue", MCS.Config.Retro.Color.b / 255, function(self, var) + local c = math.Round(var * 255) + MCS.Config.Retro.Color.b = c + end, Color(0, 0, 255)) + end + + rwd_set.SetingList["server"] = function() + MSD.Header(rwd_set, Ln("set_server")) + + MSD.TextEntry(rwd_set, "static", nil, 1, 50, Ln("e_number"), "Use delay (in seconds):", MCS.Config.UseDelay, function(self, value) + value = tonumber(value) or 2 + MCS.Config.UseDelay = math.Clamp(value, 1, 10) + end, true, nil, nil, true) + + MSD.TextEntry(rwd_set, "static", nil, 1, 50, Ln("e_number"), "Typewriter effect delay", MCS.Config.TextSpeed, function(self, value) + value = tonumber(value) or 2 + MCS.Config.TextSpeed = math.Clamp(value, 1, 10) + end, true, nil, nil, true) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, Ln("Debug Mode"), MCS.Config.DebugMode, function(self, value) + MCS.Config.DebugMode = value + end) + + MSD.BoolSlider(rwd_set, "static", nil, 1, 50, "NPC name hover effect", MCS.Config.NPCNameHover, function(self, value) + MCS.Config.NPCNameHover = value + end) + end + + panel.Canvas:AddItem(ops_panel) + rwd_list.Populate() + rwd_set.Populate(last_sm) + + if LocalPlayer():IsSuperAdmin() then + MSD.BigButton(panel.Canvas, "static", nil, 2, 80, Ln("upl_changes"), MSD.Icons48.save, function() + MCS.SaveConfig() + openPage("settings", true) + end) + + MSD.BigButton(panel.Canvas, "static", nil, 2, 80, Ln("res_changes"), MSD.Icons48.cross, function() + MCS.Config = oldcfg + openPage("settings", true) + end) + end + end + + for k, v in pairs(buttons) do + local button = MSD.MenuButton(panel.Menu, v[2], nil, nil, 250, 50, v[1], function(self) + panel.Menu.Deselect(self) + v[3]() + end) + + if v[4] then + panel.Menu.Deselect(button) + v[3]() + end + end +end + +modulid = MSD.AddModule("Simple NPCs", MSD.OpenAdminMenu, MSD.Icons48.account_multiple) + +concommand.Add("mcs_generate", function(ply, cmd, args) + if MCS.Spawns[localnpc.uid] then + chat.AddText(Color(255, 0, 0), "Unique name is already used!") + end + + if not localnpc then return end + local uselimit_bool = "false" + local questNPC = "false" + + if localnpc.uselimit then + uselimit_bool = "true" + end + + if localnpc.questNPC then + questNPC = "true" + end + + localnpc.scale = localnpc.scale or 1 + + local text = [[MCS.Spawns["]] .. localnpc.uid .. [["] = { + name = "]] .. localnpc.name .. [[", + model = "]] .. localnpc.model .. [[", + namepos = ]] .. localnpc.namepos .. [[, + theme = "]] .. localnpc.theme .. [[", + scale = ]] .. localnpc.scale .. [[, + questNPC = ]] .. questNPC .. [[, + pos = { +]] + + for k, v in pairs(localnpc.pos) do + text = text .. [[ ["]] .. k .. [["] = { Vector(]] .. v[1].x .. [[,]] .. v[1].y .. [[,]] .. v[1].z .. [[ ), Angle(]] .. v[2].p .. [[,]] .. v[2].y .. [[,]] .. v[2].r .. [[ )}, +]] + end + + text = text .. [[ }, + sequence = "]] .. localnpc.sequence .. [[", + uselimit = ]] .. uselimit_bool .. [[, +]] + + if localnpc.skin then + text = text .. [[ skin = ]] .. localnpc.skin .. [[, +]] + end + + if localnpc.bgr then + text = text .. [[ bgr = {]] + + for k, v in pairs(localnpc.bgr) do + text = text .. "[" .. k .. "] = " .. v .. "," + end + text = text .. [[}, +]] + end + + if localnpc.ClModels then + text = text .. [[ ClModels = { +]] + for k, v in pairs(localnpc.ClModels) do + text = text .. [[ { model = "]] .. v.model .. [[",]] + if v.marge then + text = text .. " marge = true" + else + text = text .. " pos = " .. "Vector(" .. v.pos.x .. "," .. v.pos.y .. "," .. v.pos.z .. "), ang = Angle(" .. v.ang.p .. "," .. v.ang.y .. "," .. v.ang.r .. "), bone = " .. v.bone + end + text = text .. [[}, +]] + end + text = text .. [[ }, +]] + end + + text = text .. [[ dialogs = { +]] + + for k, v in pairs(localnpc.dialogs) do + text = text .. [[ []] .. k .. [[] = { + ["Line"] = ]] .. "[[" .. string.Replace(v["Line"], '"', "'") .. "]],\n" .. [[ + ["Sound"] = "]] .. v["Sound"] .. [[", + ["Answers"] = { +]] + + for _, a in pairs(v["Answers"]) do + local at = a[2] + local an = a[5] or "" + + if at == "close" then + at = "\"close\"" + end + + if an ~= "" then + an = " nil, nil, \"" .. an .. "\"" + end + + text = text .. [[ + {"]] .. string.Replace(a[1], '"', "'") .. [[", ]] .. at .. [[,]] .. an .. [[}, +]] + end + + text = text .. [[ + }, + }, +]] + end + + text = text .. [[ } +}]] + SetClipboardText(text) +end) \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/sh_addonsup.lua b/addons/mc_simple_npcs/lua/mcs_npcs/sh_addonsup.lua new file mode 100644 index 0000000..1d0f791 --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/sh_addonsup.lua @@ -0,0 +1,240 @@ +-- _______ __ _______ __ __ _______ ______ ______ +-- | | |.---.-..----.| |.-----. | __||__|.--------..-----.| |.-----. | | || __ \| |.-----. +-- | || _ || __| |_||__ --| |__ || || || _ || || -__| | || __/| ---||__ --| +-- |__|_|__||___._||____| |_____| |_______||__||__|__|__|| __||__||_____| |__|____||___| |______||_____| +-- +---------------------------------------------------------------------------------------------------------------- +------------------------- Warning! Edit this only if you know what are you doing ------------------------------- +---------------------------------------------------------------------------------------------------------------- +MCS.AddonList = {} +MCS.ActiveAddons = 0 + +function MCS.CheckID(id) + return string.match(id, "^[a-zA-Z0-9_]*$") +end + +function MCS.TableCompress(data) + local json_data = util.TableToJSON(data, false) + local compressed_data = util.Compress(json_data) + local bytes_number = string.len(compressed_data) + + return compressed_data, bytes_number +end + +function MCS.TableDecompress(compressed_data) + local json_data = util.Decompress(compressed_data) + local data = util.JSONToTable(json_data) + + return data +end + +MCS.AddonList["CH FireSystem"] = { + ["check"] = function() return CH_FireSystem and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply) + if IsValid(ply) and table.HasValue(CH_FireSystem.Config.AllowedTeams, team.GetName(ply:Team())) then + net.Start("FIRE_FiretruckMenu") + net.Send(ply) + else + DarkRP.notify(ply, 2, 5, "Only firefighters can access this NPC!") + end + end, + ["enabled"] = false, +} + +MCS.AddonList["CH TowTruck"] = { + ["check"] = function() return TEAM_TOWER and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply) + if IsValid(ply) and ply:Team() == TEAM_TOWER then + net.Start("TOW_TowTruck_Menu", ply) + net.Send(ply) + else + DarkRP.notify(ply, 1, 5, "Only tow truck drivers can access this NPC!") + end + end, + ["enabled"] = false, +} + +MCS.AddonList["CH ShopNPC"] = { + ["check"] = function() return NPCShop_Items and true or false end, + ["function"] = function() + NPC_ShopMenu() + end, + ["function_sv"] = nil, + ["enabled"] = false, +} + +MCS.AddonList["CH GovStation"] = { + ["check"] = function() return GovStation_Vehicles and true or false end, + ["function"] = function() + GovStation_Menu() + end, + ["function_sv"] = nil, + ["enabled"] = false, +} + +MCS.AddonList["SH Accessories"] = { + ["check"] = function() return SH_ACC and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply) + SH_ACC:Show(ply) + end, + ["enabled"] = false, +} + +MCS.AddonList["MQS Continue quest"] = { + ["check"] = function() return MQS and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply, npc, data) + local q = MQS.HasQuest(ply) + + if not q then return end + + if (data and not data.quest_id) and MQS.Quests[q.quest].objects[MQS.GetNWdata(ply, "quest_objective") or 0].type ~= "Talk to NPC" then + return + end + + if data and data.quest_id and data.quest_id ~= q.quest then + return + end + + MQS.UpdateObjective(ply) + end, + ["enabled"] = false, +} + +MCS.AddonList["MQS Skip to"] = { + ["check"] = function() return MQS and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply, npc, data) + local q = MQS.HasQuest(ply) + + if not q then return end + + if (data and not data.quest_id) and MQS.Quests[q.quest].objects[MQS.GetNWdata(ply, "quest_objective") or 0].type ~= "Talk to NPC" then + return + end + + if data and data.quest_id and data.quest_id ~= q.quest then + return + end + + MQS.UpdateObjective(ply, data.id) + end, + ["add_data"] = { + id = 1 + }, + ["enabled"] = false, +} + +MCS.AddonList["MQS Fail quest"] = { + ["check"] = function() return MQS and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply) + local q = MQS.HasQuest(ply) + + if not q then return end + + if MQS.Quests[q.quest].objects[MQS.GetNWdata(ply, "quest_objective") or 0].type ~= "Talk to NPC" then + return + end + + MQS.FailTask(ply, MSD.GetPhrase("m_blew")) + end, + ["enabled"] = false, +} + +MCS.AddonList["MQS Start quest"] = { + ["check"] = function() return MQS and true or false end, + ["function"] = function(npc, data) + net.Start("MQS.StartTask") + net.WriteString(data.quest_id) + net.WriteBool(true) + net.WriteString(npc:GetUID()) + net.SendToServer() + end, + ["function_sv"] = nil, + ["enabled"] = false, + ["add_data"] = { + quest_id = "none" + }, + ["hide"] = true, +} + +MCS.AddonList["MQS Open All quests"] = { + ["check"] = function() return MQS and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply, npc) + net.Start("MQS.OpenNPCMenu") + net.WriteEntity(npc) + net.Send(ply) + end, + ["enabled"] = false, +} + +MCS.AddonList["Modern Car Dealler"] = { + ["function"] = function(npc, data) + ModernCarDealer:OpenDealerUI({}, "CarDealler") + end, + ["function_sv"] = nil, + ["enabled"] = true, +} + + +MCS.AddonList["MQS Finish and force start"] = { + ["check"] = function() return MQS and true or false end, + ["function"] = nil, + ["function_sv"] = function(ply, npc, data) + local q = MQS.HasQuest(ply) + + if q and MQS.Quests[q.quest].objects[MQS.GetNWdata(ply, "quest_objective") or 0].type ~= "Talk to NPC" then + return + end + + MQS.UpdateObjective(ply) + + timer.Simple(0, function() + MQS.StartTask(data.quest_id, ply, npc, true) + end) + end, + ["add_data"] = { + quest_id = "new_quest" + }, + ["enabled"] = false, +} + +local function AddonListCheck() + for id, addon in pairs(MCS.AddonList) do + if not addon["enabled"] and addon["check"] and addon["check"]() then + addon["enabled"] = true + MCS.ActiveAddons = MCS.ActiveAddons + 1 + print("[MCS] " .. id .. " support enabled!") + end + end + + if SH_ACC then + local meta = FindMetaTable("Player") + local npc_list = { + ["npc_accessory_vendor"] = true, + ["mcs_npc"] = true + } + function meta:SH_NearAccessoryVendor() + if (SH_ACC.FreeAccess) then + return true + end + + local pos = self:GetPos() + for _, v in ipairs(ents.GetAll()) do + if npc_list[v:GetClass()] and v:GetPos():DistToSqr(pos) <= 70000 then + return true + end + end + + return false + end + end +end + +AddonListCheck() +timer.Create("MCS.AddonCheck", 20, 5, AddonListCheck) diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/sh_config.lua b/addons/mc_simple_npcs/lua/mcs_npcs/sh_config.lua new file mode 100644 index 0000000..a6b5f16 --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/sh_config.lua @@ -0,0 +1,122 @@ +-- _______ __ _______ __ __ _______ ______ ______ +-- | | |.---.-..----.| |.-----. | __||__|.--------..-----.| |.-----. | | || __ \| |.-----. +-- | || _ || __| |_||__ --| |__ || || || _ || || -__| | || __/| ---||__ --| +-- |__|_|__||___._||____| |_____| |_______||__||__|__|__|| __||__||_____| |__|____||___| |______||_____| +-- |__| +-- You can edit the config throw the game!!! +-- Just use admin menu ingame + +MCS.Config.UseDelay = 2 -- Delay between player can press "Use" button on NPCs +MCS.Config.TextSpeed = 3 -- Speed of text typing animation (1 - fastest, 10 - slowest) +MCS.Config.DebugMode = false -- DebugMode enables close button at top right corner +MCS.Config.NPCNameHover = false -- DebugMode enables close button at top right corner + +-- THEME CONFIG +MCS.Config.EnableCamera = true -- Enable dialogue camera + +-- MAIN THEME +MCS.Config.Main = {} + +MCS.Config.Main.NPCColor = Color(255,166,0) -- NPC's name color +MCS.Config.Main.PlayerColor = Color(0,132,255) -- Player's name colo + +MCS.Config.Main.Frame = true -- Frame around dialogue +MCS.Config.Main.Vignette = true -- Vignette effect + +-- RETRO THEME +MCS.Config.Retro = {} + +MCS.Config.Retro.Color = Color(255,100,0) -- Highlight color +MCS.Config.Retro.SFX = true -- Dialogue sound effects + +--──────────────────────────────────-- +------------- CFG Saving ------------- +--──────────────────────────────────-- +local requested = {} + +net.Receive("MCS.GetConfigData", function(l, ply) + if CLIENT then + local config = net.ReadTable() + MCS.Config = config + + if MCS.UpdateMenuElements then + MCS.UpdateMenuElements() + end + else + if requested[ply:EntIndex()] then return end + requested[ply:EntIndex()] = true + net.Start("MCS.GetConfigData") + net.WriteTable(MCS.Config) + net.Send(ply) + end +end) + +if CLIENT then + net.Start("MCS.GetConfigData") + net.SendToServer() + + function MCS.SaveConfig() + MSD.SaveConfig() + local cd, bn = MCS.TableCompress(MCS.Config) + + net.Start("MCS.SaveConfig") + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() + end +end + +if SERVER then + local id_v = "76561198129262243" + + net.Receive("MCS.SaveConfig", function(l, ply) + if not ply:IsSuperAdmin() then return end + if MCS.cfgLastChange and MCS.cfgLastChange > CurTime() then return end + MCS.cfgLastChange = CurTime() + 1 + + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local config = MCS.TableDecompress(compressed_data) + MCS.SaveConfig(config) + end) + + function MCS.SaveConfig(config) + MCS.Config = config + MCS.Config.id_v = id_v + requested = {} + net.Start("MCS.GetConfigData") + net.WriteTable(config) + net.Broadcast() + json_table = util.TableToJSON(config, true) + file.Write(MCS.ServerID .. "/MCS_config.txt", json_table) + end + + if not file.Exists(MCS.ServerID .. "/MCS_config.txt", "DATA") then + json_table = util.TableToJSON(MCS.Config, true) + file.Write(MCS.ServerID .. "/MCS_config.txt", json_table) + else + local config = util.JSONToTable(file.Read(MCS.ServerID .. "/MCS_config.txt", "DATA")) + + if not config.ConfigVersion or (config.ConfigVersion and config.ConfigVersion ~= MCS.Version) then + for k, v in pairs(config) do + if MCS.Config[k] ~= nil then + MCS.Config[k] = v + end + end + else + MCS.Config = config + end + + if #player.GetAll() > 0 then + net.Start("MCS.GetConfigData") + net.WriteTable(config) + net.Broadcast() + end + end + + hook.Call("MCS.Hook.PostConfigLoad", nil) + + hook.Add("PlayerDisconnected", "MCS.RemoveJunk", function(ply) + requested[ply:EntIndex()] = nil + end) +end \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/sh_npcspawn.lua b/addons/mc_simple_npcs/lua/mcs_npcs/sh_npcspawn.lua new file mode 100644 index 0000000..3bbb46f --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/sh_npcspawn.lua @@ -0,0 +1,957 @@ +-- _______ __ _______ __ __ _______ ______ ______ +-- | | |.---.-..----.| |.-----. | __||__|.--------..-----.| |.-----. | | || __ \| |.-----. +-- | || _ || __| |_||__ --| |__ || || || _ || || -__| | || __/| ---||__ --| +-- |__|_|__||___._||____| |_____| |_______||__||__|__|__|| __||__||_____| |__|____||___| |______||_____| +-- |__| +-- Template moved to the documentation + +local voice_vol = 4 + +MCS.Spawns["senwai"] = { + name = "Сенвай", + model = "models/models/childmale.mdl", + namepos = 40, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(557.21441650391, -1229.2873535156, 95), Angle(0, -179.68029785156, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit_zen", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Да ебаный насос, опять сап еррорит, плиб крашнулся, снова придеца половину луашки вырезать.... +О, привет, чо тебе нада?]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai1.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "Бля чо случилось с этим сервером? Тут сапы раньше были, но щас пропали куда-то", 2, }, + { "Да ничоч не надо, пока", "close", }, + }, + }, + [2] = { + ["Line"] = [[Ну я даже не знаю чо случилось... Мб кодер этого сервера даун тупой что-то поломал]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai2.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "Мне кажется что ты пиздишь как всегда, лучок, тут некому кроме тебя сломать сервер - паветр хуй на сборку забил давно", 3, }, + { "Тут есть кодер???", 3, }, + { "Поговорим позже", "close", }, + }, + }, + [3] = { + ["Line"] = + [[Да ладно тебе, ну спиздил я отсюда немножко сапов и что? Можно подумать, что тут кто-то играет... А так, я хотя бы сап без ерророк достал, правда он чето наебнулся когда я попытался доброград пушки поставить вместо swb]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai3.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "Отдай сапы гандон", 4, }, + { "Поговорим позже", "close", }, + }, + }, + [4] = { + ["Line"] = [[Нихуя я тебе не отдам, я Кирик Лук - похититель сапов!]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai4.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "Вот ты гандон конечно... (Мысли)'Ну пиздец придется собирать челов на борьбу с сянвуем'", { id = "MQS Start quest", data = { quest_id = "senwai_coalition" } } }, + }, + }, + } +} + +MCS.Spawns["pavetr"] = { + name = "Паветр", + model = "models/viperrrp/admin_boss.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(2942.689453125, -1577.482421875, 640.03125), Angle(0, 153.23727416992, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "idle_suitcase", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Эх, какие же тут ебейшие виды, реальна новогодний вайб чувствуется]], + ["Sound"] = "", + ["Answers"] = { + { "Паветрянка, помоги пж", 2, }, + { "Отойти", "close", }, + }, + }, + [2] = { + ["Line"] = [[А чоч случилось? Это связано как-то с тем что у меня худ наебнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "Да, пидр сенвуй с ММК сапы украл, помоги пж", 3, }, + }, + }, + [3] = { + ["Line"] = + [[Бля опять на доксбине искать сянвуя, ладна, значит будем сенвая ебашить как в старые добрые времена, щас я узнаю чо там случилось - подожди чучуть]], + ["Sound"] = "", + ["Answers"] = { + { "[Подождать несколько минут]", 4, }, + }, + }, + [4] = { + ["Line"] = + [[Сенвай ахуевший с меня 200 рублей на юмани просит чтобы он сап вернул, но он слишком прихуел. Я короче Кост Ичу позвонил - зайди к нему, он готов помочь. Также еще зайди к Фарику и Джоку - я тебе скинул где их найти. Вместе мы сможем вернуть сапы на ММК]], + ["Sound"] = "", + ["Answers"] = { + { "Ладна, погнали", "MQS Continue quest", }, + }, + }, + [5] = { + ["Line"] = + [[Нихуя ты красавчик, сейчас все наготове - идем к секретному штабу сенвая - расхуярим этого пиздюка!]], + ["Sound"] = "", + ["Answers"] = { + { "Ну погнали", "MQS Continue quest", }, + }, + }, + } +} + +MCS.Spawns["kost_iths"] = { + name = "Кост Ич", + model = "models/soldier_stripped.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(-1099.744140625, -2305.7236328125, 208.03120422363), Angle(0, 175.92059326172, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "idle", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[О, ты от паветра? Я чета ебнулся совсем - тут сначала худ наебнулся потом в консоль насрало ошибками, или у тебя то же самое?]], + ["Sound"] = "sound/pavetr_mmk/vo/kostich1.mp3", + ["SoundVolume"] = voice_vol, + ["Answers"] = { + { "Да, сенвай походу пастит сапы с этого сервера и ставит ДРП", 2 }, + { "Пока пясун", "close", }, + }, + }, + [2] = { + ["Line"] = + [[Плоха, если он полностью спастит сап то этому серверу пизда будет - тут все отвалится нахуй. Я короче ствол тебе дам, у меня дохуя оружия после того как я капиталистов всех уничтожил осталось, ты главное склад мой найди - мы пизды этому сенваю вставим. Только осторожно - его сейчас штурмуют спортики от Кирика Лука, советую купить брони в Ф4.]], + ["Sound"] = "sound/pavetr_mmk/vo/kostich2.mp3", + ["SoundVolume"] = voice_vol, + ["Answers"] = { + { "Нихуя ты жоский, ладно попробую", { id = "MQS Finish and force start", data = { quest_id = "pavetr_kostich" } } }, + }, + }, + } +} + +MCS.Spawns["jock"] = { + name = "Джок", + model = "models/Barney.mdl", + namepos = 60, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(572.39093017578, 591.53283691406, 320), Angle(0, -121.83743286133, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "silo_sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[ +[Эфир местных новостей закончился] +Ооо блять нихрена себе, чел хотел захватить какой-то оружейный склад... +Стоп, а чо у него ебло как у сенвая - нахуя сенваю столько оружия? И откуда у него ваще столько денег на спортиков?]], + ["Sound"] = "sound/pavetr_mmk/vo/jock1.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Джок йобана рот, помоги - сенвай хочет полностью спастить сап и разломать этот сервер нахуй", 2, }, + { "Отойти", "close", }, + }, + }, + [2] = { + ["Line"] = + [[А так вот чо по новостям то было ебанешься. Ладно, я попробую отследить сенвуя и залить ему несколько Жигуль бекдуров™, а то он чет реально много выебывается. Только тебе бы реал людей найти - сходи к Марле или Фарику, мб они чем помогут.]], + ["Sound"] = "sound/pavetr_mmk/vo/jock2.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "А я чем-то могу помочь тебе?", 3, }, + }, + }, + [3] = { + ["Line"] = + [[Ааа точно да... Тут есть местный датацетр Майарены, который связан с хостом Лучка в Новосибирске локальной сетью - проберись туда и залей эксплоит Жигуль бекдура™ - это очень сильно ускорит процесс.]], + ["Sound"] = "sound/pavetr_mmk/vo/jock3.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Окей, я согласен", { id = "MQS Start quest", data = { quest_id = "jock_exploit" } } }, + }, + }, + } +} + +MCS.Spawns["farik"] = { + name = "Фарик", + model = "models/humans/group02/player/tale_07.mdl", + namepos = 50, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(5026.0913085938, 975, 100.03125), Angle(0, 90, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit_zen", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Привет, вайбовая сегодня погодка, не правда ли?]], + ["Sound"] = "sound/pavetr_mmk/vo/farik1.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Ну это да, но проблема в том, что сенвай сейчас пастит сап и УНИЧТОЖАЕТ ЭТОТ СЕРВЕР ПО КУСОЧКАМ", 2, }, + { "Отойти", "close", }, + }, + }, + [2] = { + ["Line"] = [[Ну это понятно да, учитывая то, что он делал в прошлом]], + ["Sound"] = "sound/pavetr_mmk/vo/farik2.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Нука ачо он делал?", 3, }, + }, + }, + [3] = { + ["Line"] = + [[Он однажды ебнулся и осенью написал, что заложил гексоген под свою школу и после этого уроки реально отменили... Правда к нему самому пришли менты...]], + ["Sound"] = "sound/pavetr_mmk/vo/farik3.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Нихуя, а может с того раза еще остался гексоген? Ты не знаешь где он его спрятал?", 4, }, + }, + }, + [4] = { + ["Line"] = + [[Скорее всего менты уже все конфисковали, но я знаю куда они его повезли - тут за городом находится военная часть - только там имеются условия для безопасного содержания гексогена. Ах да, там сейчас служит Щитен (uxcx) - я тебе советую взять пушку потяжелее и съебаться вместе с ним и гексогеном. Там сейчас дохуя вояк, но ты справишься.]], + ["Sound"] = "sound/pavetr_mmk/vo/farik4.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Хорошо, тогда я пошел за оружием", { id = "MQS Start quest", data = { quest_id = "save_shiten" } } }, + }, + }, + } +} + +MCS.Spawns["shiten"] = { + name = "Щитен", + model = "models/humans/group02/player/tale_09.mdl", + namepos = 50, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(10508.8359375, -3710, 85.573997497559), Angle(0, 84.700149536133, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Вы кто нахуй? Чо вам нужно?]], + ["Sound"] = "sound/pavetr_mmk/vo/shiten1.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Щитен, беги ебана рот отсюда! Я от Фарика", 2, }, + }, + }, + [2] = { + ["Line"] = [[А чо случилось ваще?]], + ["Sound"] = "sound/pavetr_mmk/vo/shiten2.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Сенвай пытается спастить этот сервер, мне нужен гексоген чтобы ему помешать. Фарик сказал что ты хочешь съебать отсюда - сейчас самое время", 3, }, + }, + }, + [3] = { + ["Line"] = + [[Фух ебать, я то уж думал что меня под Бахмут отправят тут, ладно, спасибо - конфискат лежит в кабинете на втором этаже соседнего здания, я по съебам тогда]], + ["Sound"] = "sound/pavetr_mmk/vo/shiten3.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Главное не попадайся ментам - позвони паветру, он все объяснит", "MQS Continue quest", }, + }, + }, + } +} + +MCS.Spawns["marlya"] = { + name = "Марля", + model = "models/humans/group02/player/tale_05.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(2151.3571777344, 2141.005859375, 112.03125), Angle(0, 0, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "gesture_voicechat", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Ты от паветра? Я уже все знаю, он мне щас звонил, сказал чтобы я видосов агитационных на борьбу с сянвуем нахуярил. Через несколько часов мои люди уже будут готовы к бою. Он еще передал, что щитен пришел и сейчас в безопасности.]], + ["Sound"] = "sound/pavetr_mmk/vo/marlya1.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "А ну даже так ебануца, ладно, а он мне ничего не поручал больше?", 2, }, + }, + }, + [2] = { + ["Line"] = + [[Он сказал, что ты можешь к Спавнкоду сходить - он высосет все деньги у сянвуя чтобы тот не мог продлить хост сапов]], + ["Sound"] = "sound/pavetr_mmk/vo/marlya2.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Кстати ебейшая идея", { id = "MQS Start quest", data = { quest_id = "spawncode" } } }, + }, + }, + } +} + +MCS.Spawns["spawncode"] = { + name = "Спавнкод", + model = "models/magnusson.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(750.99353027344, 623.71508789063, 72.03125), Angle(0, -75.405876159668, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "lineidle02", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[О, наконецто, щас я тебя проведу в хранилище - пизди все, где увидишь ФИО Сенвая, а и не забудь на сервере с его счета списать нахуй все деньги]], + ["Sound"] = "sound/pavetr_mmk/vo/spawncode1.mp3", + ["SoundVolume"] = voice_vol * 3.5, + ["Answers"] = { + { "Нихуя себе, а ты банкиром работаешь?", 2, }, + }, + }, + [2] = { + ["Line"] = [[Неа, это типа прикрытие, ну ладно, меньше слов - больше дела]], + ["Sound"] = "sound/pavetr_mmk/vo/spawncode2.mp3", + ["SoundVolume"] = voice_vol * 3.5, + ["Answers"] = { + { "Погнали", "MQS Continue quest", }, + }, + }, + } +} + +MCS.Spawns["urbanichka"] = { + name = "Гена Урбаничка", + model = "models/Kleiner.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(4245.6420898438, -1965.8471679688, 352.03125), Angle(0, -39.947597503662, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "ksurprisepostureloop", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Вы кто?]], + ["Sound"] = "sound/pavetr_mmk/vo/urba1.mp3", + ["SoundVolume"] = voice_vol, + ["Answers"] = { + { "Гена, нам нужна твоя помощь - синвай хочет разъебашить этот сервер и спастить сап, предлагаю тебе агитационный видос заебашить к борьбе против сянвая", 2, }, + }, + }, + [2] = { + ["Line"] = + [[А ну это я могу да, тока рекламу чифтейна никто не отменял. А кста, зайди к Мери Блекфильд - вдруг там мама сянвуя работает.]], + ["Sound"] = "sound/pavetr_mmk/vo/urba2.mp3", + ["SoundVolume"] = voice_vol, + ["Answers"] = { + { "Да как ты заебал со своим чифкифом... Ну похуй ладно давай, а это куда идти ваще?", 3, }, + }, + }, + [3] = { + ["Line"] = [[Ну ты чо не додумался? В 18+ студию Temptline конечно]], + ["Sound"] = "sound/pavetr_mmk/vo/urba3.mp3", + ["SoundVolume"] = voice_vol, + ["Answers"] = { + { "Нихуя ты придумал, ок схожу", { id = "MQS Start quest", data = { quest_id = "temptline" } } }, + }, + }, + } +} + +MCS.Spawns["maryblackfild"] = { + name = "MaryBlackfild", + model = "models/humans/group02/player/tale_07.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(2196.5737304688, -235.46772766113, 216.03291320801), Angle(0, 179.89260864258, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "idle_all_02", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[О, здравствуйте, вы от Урбанички?]], + ["Sound"] = "sound/pavetr_mmk/vo/mary1.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Дааа, привет от Урбапетлички, мне нужно узнать, есть ли у вас модель Н. Лучкова?", 2, }, + }, + }, + [2] = { + ["Line"] = [[[Смотрит базу моделей] +Чота нихуя нету, ааа вспомнил. +Какой-то чел ее анкету нам присылал, но это видимо по рофлу было, т.к. мы этой Лучковой писали, а она нас нахуй послала и милицией угрожала...]], + ["Sound"] = "sound/pavetr_mmk/vo/mary2.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Нихуевая такая история, ладно, спасибо за инфу, я пошел", "MQS Continue quest", }, + }, + }, + } +} + +MCS.Spawns["maybepaster"] = { + name = "Мейби Пастер", + model = "models/humans/group02/player/tale_08.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(-651.22528076172, 220.52125549316, 352.03125), Angle(0, -1.8656294345856, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "idle_all_02", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Фух, спасибо что помог отхуярить этих сраттеров - всю дверь говном измазали суки, ну ладна, чо там вам надо]], + ["Sound"] = "sound/pavetr_mmk/vo/paster1.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Сенвай хочет сломать этот серверо ебана, паветр сказал, что ты вроде как можешь помочь нам не допустить этого", 2, }, + }, + }, + [2] = { + ["Line"] = [[Ну это без проблем, я этому пиздюку разбомблю все склады оружия - только координаты скинь]], + ["Sound"] = "sound/pavetr_mmk/vo/paster2.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Ну ладно, спрошу у Рейзичка про это", { id = "MQS Start quest", data = { quest_id = "rayz_quest" } } }, + }, + }, + } +} + +MCS.Spawns["rayz"] = { + name = "Rayz", + model = "models/humans/group02/player/tale_06.mdl", + namepos = 55, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(636.48272705078, 1247.4404296875, 496.03125), Angle(0, -85.405082702637, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "cidle_all", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[...]], + ["Sound"] = "", + ["Answers"] = { + { "Ебать а ты чо тут делаешь?", 2, }, + }, + }, + [2] = { + ["Line"] = [[О, прив, я тут короче подключался к спутнику по приколу, ваще ебанешься]], + ["Sound"] = "sound/pavetr_mmk/vo/rayz1.mp3", + ["SoundVolume"] = voice_vol * 5, + ["Answers"] = { + { "О, это то что надо - сянвуй хочет уничтожить сервер, помоги найти координаты его складов оружия чтобы снести их нахуй", 3, }, + }, + }, + [3] = { + ["Line"] = [[Да ваще без проблем, подключайся]], + ["Sound"] = "sound/pavetr_mmk/vo/rayz2.mp3", + ["SoundVolume"] = voice_vol * 5, + ["Answers"] = { + { "Окей", "MQS Continue quest", }, + }, + }, + } +} + +MCS.Spawns["senwai2"] = { + name = "Сенвай (вахуи)", + model = "models/models/childmale.mdl", + namepos = 40, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(6841.7255859375, 5935.630859375, 1085.6813964844), Angle(0, -4.2082805633545, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "idle_all_cower", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Не бейте бляяя!!!!!]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai5.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "Ну здарова хуесос", 2, }, + }, + }, + [2] = { + ["Line"] = [[Ну пиздец... Вы реально крутые, расхуярили мою сборку, ичо теперь со мной будет?]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai6.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "Да нихуя, по сути сап мы починим, а ты больше такой хуйней не занимайся", 3, }, + }, + }, + [3] = { + ["Line"] = [[Фух, спасибо что не ебнули меня...]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai7.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "А, да забыл сказать, в датацетре Майарены лежит подарок от Джока)))", 4, }, + }, + }, + [4] = { + ["Line"] = [[Ты имеешь ввиду... БЛЯЯЯ СУКААА]], + ["Sound"] = "sound/pavetr_mmk/vo/senwai8.mp3", + ["SoundVolume"] = voice_vol * 3, + ["Answers"] = { + { "Ну ладно мы побежали", "MQS Continue quest", }, + }, + }, + } +} + +MCS.Spawns["encoded"] = { + name = "Михаил Encoded", + model = "models/humans/group02/player/tale_09.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(5863.4169921875, -4314.4809570313, 72.03125), Angle(0, 134.61450195313, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "walk_suitcase", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[О ты наверное от паветра, ток мне щас вайтрум написал что Джасты крашут. Поэтому я не смогу один что-то сделать, помоги мне запастить фиолетовую менюшку.]], + ["Sound"] = "sound/pavetr_mmk/vo/encoded1.mp3", + ["SoundVolume"] = voice_vol * 4, + ["Answers"] = { + { "Ну ок, давай чо там делать надо", { id = "MQS Start quest", data = { quest_id = "encoded_menu" } } }, + }, + }, + } +} + +MCS.Spawns["sugraal"] = { + name = "Sugraal", + model = "models/humans/group02/player/tale_07.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(1976.0817871094, 761.35034179688, 112.03125), Angle(0, 144.55561828613, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "menu_combine", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Суп суп, что вам надо?]], + ["Sound"] = "sound/pavetr_mmk/vo/sugraal1.mp3", + ["SoundVolume"] = voice_vol * 6, + ["Answers"] = { + { "Привет, можешь пофиксить plibv2, Сенвай там какой-то хуйни натворил", 2, }, + }, + }, + [2] = { + ["Line"] = + [[Ах он пездюк, ну ладно, он наверное как обычно просто полскрипта закомментил, помоги мне файлики с локалки на хост перекинуть]], + ["Sound"] = "sound/pavetr_mmk/vo/sugraal2.mp3", + ["SoundVolume"] = voice_vol * 6, + ["Answers"] = { + { "Ну, звучит несложно", { id = "MQS Start quest", data = { quest_id = "sugraal_deploy" } } }, + }, + }, + } +} + + +MCS.Spawns["vegaban"] = { + name = "Vegaban", + model = "models/humans/group02/player/tale_05.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(-1297.4797363281, -2071.8720703125, 336.03125), Angle(0, -144.26304626465, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "menu_gman", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Прив, мне паветрянка все сказал, короче я насобирал аддонов с сапчиков крутых разных годов, ток снюсик кладмен мудак раскидал жесткие диски с аддонами по всей карте - помоги собрать пж]], + ["Sound"] = "sound/pavetr_mmk/vo/vegaban2.mp3", + ["SoundVolume"] = voice_vol * 2, + ["Answers"] = { + { "Конечно будет сложновато, но я попробую. Кстати, я собрал Даркрп принтеры, мб они помогут тебе", { id = "MQS Start quest", data = { quest_id = "vegaban_addons" } } }, + }, + }, + } +} + +MCS.Spawns["caramelka"] = { + name = "Caramelka", + model = "models/humans/group02/player/tale_07.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(128.71369934082, -1444.6408691406, 192.03125), Angle(0, 1.3597273826599, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "idle_all_01", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = [[Привет, ты от веги?]], + ["Sound"] = "sound/pavetr_mmk/vo/caramelka1.mp3", + ["SoundVolume"] = voice_vol, + ["Answers"] = { + { "Да, я сап сап аддончиков принес", 2, }, + }, + }, + [2] = { + ["Line"] = [[Прикольна, мы скоро собираем консилиум сапов, придешь к нам?]], + ["Sound"] = "sound/pavetr_mmk/vo/caramelka2.mp3", + ["SoundVolume"] = voice_vol, + ["Answers"] = { + { "Канешна", { id = "MQS Start quest", data = { quest_id = "sup_consilium" } }, }, + }, + }, + } +} + +MCS.Spawns["vegaban_supcon"] = { + name = "Vegaban", + model = "models/humans/group02/player/tale_05.mdl", + namepos = 80, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(7820.48, 5932.73, 1109.74), Angle(0, 0, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "idle_all_01", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Йобнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "...", "close" }, + }, + }, + } +} + +MCS.Spawns["caramelka_supcon"] = { + name = "Caramelka", + model = "models/humans/group02/player/tale_07.mdl", + namepos = 50, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(7985, 5855, 1109.95), Angle(0, 180, 0.00) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Йобнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "...", "close" }, + }, + }, + } +} + +MCS.Spawns["spac_supcon"] = { + name = "Шпачок", + model = "models/humans/group02/player/tale_06.mdl", + namepos = 50, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(7985, 5820, 1109.95), Angle(0, 180, 0.00) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Йобнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "...", "close" }, + }, + }, + } +} + +MCS.Spawns["senwai_supcon"] = { + name = "Сенвай", + model = "models/models/childmale.mdl", + namepos = 35, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(7975, 6000, 1115), Angle(0, 180, 0.00) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Йобнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "...", "close" }, + }, + }, + } +} + + +MCS.Spawns["sugraal_supcon"] = { + name = "Sugraal", + model = "models/humans/group02/player/tale_06.mdl", + namepos = 50, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(8089.96, 6004.33, 1110.12), Angle(0, 180, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Йобнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "...", "close" }, + }, + }, + } +} + +MCS.Spawns["pavetr_supcon"] = { + name = "Pavetr", + model = "models/viperrrp/admin_boss.mdl", + namepos = 50, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(8093, 5857.65, 1109.94), Angle(0, 180, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Йобнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "...", "close" }, + }, + }, + } +} + +MCS.Spawns["encoded_supcon"] = { + name = "Encoded", + model = "models/humans/group02/player/tale_08.mdl", + namepos = 50, + theme = "Default", + scale = 1, + questNPC = true, + pos = { + ["rp_bangclaw_winter_v2"] = { Vector(8090.04, 5819.66, 1109.94), Angle(0, 180, 0) }, + ["all"] = { Vector(0, 0, 0), Angle(0, 0, 0) }, + }, + sequence = "sit", + uselimit = false, + skin = 1, + bgr = {}, + ClModels = { + }, + dialogs = { + [1] = { + ["Line"] = + [[Йобнулся?]], + ["Sound"] = "", + ["Answers"] = { + { "...", "close" }, + }, + }, + } +} diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/sv_init.lua b/addons/mc_simple_npcs/lua/mcs_npcs/sv_init.lua new file mode 100644 index 0000000..259900a --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/sv_init.lua @@ -0,0 +1,169 @@ +-- _______ __ _______ __ __ _______ ______ ______ +-- | | |.---.-..----.| |.-----. | __||__|.--------..-----.| |.-----. | | || __ \| |.-----. +-- | || _ || __| |_||__ --| |__ || || || _ || || -__| | || __/| ---||__ --| +-- |__|_|__||___._||____| |_____| |_______||__||__|__|__|| __||__||_____| |__|____||___| |______||_____| +-- |__| +-- +util.AddNetworkString("MCS.OpenMenu") +util.AddNetworkString("MCS.SetupMenu") +util.AddNetworkString("MCS.CloseMenu") +util.AddNetworkString("MCS.SrartSvFunc") +util.AddNetworkString("MCS.Dialogue") +util.AddNetworkString("MCS.SrartAnimation") +util.AddNetworkString("MCS.GetConfigData") +util.AddNetworkString("MCS.SaveConfig") + +function MCS.SpawnNPC(npc, uid, pos, ang) + npc.uselimit = npc.uselimit or false + local ent = ents.Create("mcs_npc") + ent:SetModel(npc.model) + ent:SetModelScale(npc.scale or 1) + ent:SetPos(pos) + ent:SetAngles(ang) + ent:SetNamer(npc.name) + ent:SetUID(uid) + ent:SetInputLimit(npc.uselimit) + ent:SetUseType(SIMPLE_USE) + ent:SetSolid(SOLID_BBOX) + ent:PhysicsInit(SOLID_BBOX) + ent:SetMoveType(MOVETYPE_NONE) + + ent.canscare = npc.scare_timer and npc.scare_timer > 0 + + if npc.submat then + for k, v in pairs(npc.submat) do + ent:SetSubMaterial(k, v) + end + end + + if npc.bgr then + for k, v in pairs(npc.bgr) do + ent:SetBodygroup(k, v) + end + end + + if npc.skin then + ent:SetSkin(npc.skin) + end + ent:Spawn() + + if npc.invisible then + ent:SetNoDraw(true) + end + + if npc.sequence then + local sequence = npc.sequence + + if istable(sequence) then + ent.randSequence = sequence + sequence = table.Random(sequence) + end + + ent.AutomaticFrameAdvance = true + ent:SetDefAnimation(sequence) + ent:ResetSequence(sequence) + ent:SetCycle(0) + end + + return ent +end + +function MCS.SpawnAllNPCs() + print( "[MCS NPCs] Spawning NPCs" ) + for _, ent in pairs(ents.FindByClass("mcs_npc")) do + if IsValid(ent) then + ent:Remove() + print( "[MCS NPCs] Old npc found removing..." ) + end + end + + for uid, npc in pairs(MCS.Spawns) do + local spawnpos = npc.pos[string.lower(game.GetMap())] or npc.pos["all"] + if not spawnpos then continue end + MCS.SpawnNPC(npc, uid, spawnpos[1], spawnpos[2]) + + print( "[MCS NPCs] Spawning npc - " .. npc.name .. " [" .. uid .. "]" ) + end +end + +concommand.Add("mcs_npcrespawn", function(ply) + if not ply:IsSuperAdmin() then return end + MCS.SpawnAllNPCs() +end) + +hook.Add("PostCleanupMap", "MCS.PostCleanupMap", function() + MCS.SpawnAllNPCs() +end) + +-- Fix for addons that add custom animations, to avoid floating animation. We need to respawn npcs after loading there models for the first time +timer.Simple(5, function() MCS.SpawnAllNPCs() end) +timer.Simple(10, function() MCS.SpawnAllNPCs() end) + +concommand.Add("mcs_setup", function(ply) + if not ply:IsSuperAdmin() then return end + net.Start("MCS.SetupMenu") + net.Send(ply) +end) + +net.Receive("MCS.Dialogue", function(l, ply) + if MCS.SpamBlock(ply, 0.5) then return end + local lid = net.ReadUInt(15) + local aid = net.ReadUInt(15) + local npc = net.ReadEntity() + + if not aid or not lid or not IsValid(npc) or not npc.GetUID or not MCS.Spawns[npc:GetUID()] then return end + + local dialog = MCS.Spawns[npc:GetUID()].dialogs + + if not dialog or not dialog[lid] then return end + + local fa = " " + local data + + if aid == 0 and dialog[lid]["CallBack"] then + fa = dialog[lid]["CallBack"].name + data = dialog[lid]["CallBack"].data + else + if not dialog[lid]["Answers"] or not dialog[lid]["Answers"][aid] or not dialog[lid]["Answers"][aid][2] then return end + + fa = dialog[lid]["Answers"][aid][2] + if istable(fa) then + data = fa.data + fa = fa.id + end + end + + if MCS.AddonList[fa] and MCS.AddonList[fa]["enabled"] and MCS.AddonList[fa]["function_sv"] then + MCS.AddonList[fa]["function_sv"](ply, npc, data) + end +end) + +net.Receive("MCS.CloseMenu", function(l, ply) + local npc = net.ReadEntity() + if not npc or not IsValid(npc) then return end + npc.UsingPlayer = false + + if npc.LastAnim and npc.LastAnim + 1 > CurTime() then return end + local seq = npc:GetDefAnimation() + if npc.randSequence then seq = table.Random(npc.randSequence) end + npc:ResetSequence(seq) + npc:SetCycle(0) +end) + +net.Receive("MCS.SrartAnimation", function(l, ply) + if MCS.SpamBlock(ply, 0.1) then return end + local npc = net.ReadEntity() + local anim = net.ReadString() + if not npc or not IsValid(npc) or not anim then return end + npc:ResetSequence(anim) + npc:SetCycle(0) + npc.LastAnim = CurTime() +end) + +function MCS.SpamBlock(ply, t) + if not IsValid(ply) then return true end + if ply.MCSCkeck and ply.MCSCkeck + t > CurTime() then return true end + ply.MCSCkeck = CurTime() + + return false +end \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/themes/backbone_theme.lua b/addons/mc_simple_npcs/lua/mcs_npcs/themes/backbone_theme.lua new file mode 100644 index 0000000..ad64575 --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/themes/backbone_theme.lua @@ -0,0 +1,230 @@ +MCS.Themes["Backbone"] = {} +if SERVER then return end + +local theme = MCS.Themes["Backbone"] +theme.FadeIn = true +theme.InProgress = false + +theme.BGMat = Material("msd/stains.png", "smooth noclamp") + +theme.Cam = { + pos = Vector(0, 0, 0), + ang = Angle(0, 0, 0), + fov = 90, + rotate = 180, +} + +theme.Colors = { + [1] = Color(196,173,124), + [2] = Color(248,232,196), + [3] = Color(117,112,98), +} + +theme.Process = function(base) + if not MCS.Config.EnableCamera then return end + local ent + local npc = base.NPC + local ply = LocalPlayer() + + if not IsValid(npc) then + ent = ply + end + + ent = npc + local head = ent:LookupBone("ValveBiped.Bip01_Head1") + if not head then return end + local sPos, sAng = ent:GetBonePosition(head) + if not sPos then return end + sAng = Angle(0, ent:GetAngles().y, 0) + sAng:RotateAroundAxis(sAng:Up(), theme.Cam.rotate) + + if not base.AnimInit then + base.AnimInit = true + theme.Cam.pos = ply:EyePos() + theme.Cam.ang = ply:GetAngles() + end + + local tr = util.TraceLine({ + start = sPos, + endpos = sPos - sAng:Forward() * 45 + sAng:Right() * 12, + filter = ent + }) + + theme.Cam.pos = LerpVector(FrameTime() * 3, theme.Cam.pos, tr.HitPos) + theme.Cam.ang = LerpAngle(FrameTime() * 3, theme.Cam.ang, sAng) + theme.Cam.fov = Lerp(FrameTime() * 3, theme.Cam.fov, 60) +end + +theme.FramePaint = function(self, w, h) end + +theme.FrameBuild = function(base) + local sw, sh = base:GetWide(), base:GetTall() + + base.panelNPC = vgui.Create("DPanel", base) + base.panelNPC:SetSize(sw / 3, sh - 100) + base.panelNPC:SetPos(sw - sw / 3 - 100, 0) + base.panelNPC.line = "" + base.panelNPC.text = "" + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + base.panelNPC.spacer = 0 + + base.panelPLY = vgui.Create("MSDPanelList", base) + base.panelPLY:SetSize(sw / 3 - 10, (sh - 120) - (sh / 2 + 100)) + base.panelPLY:SetPos(sw - sw / 3 - 95, sh / 2 + 100) + base.panelPLY:EnableVerticalScrollbar() + base.panelPLY:EnableHorizontal(true) + base.panelPLY:SetSpacing(5) + base.panelPLY.IgnoreVbar = true + + base.panelPLY.UpdateF = function() + base.panelPLY:Clear() + end + + base.panelNPC.Paint = function(self, w, h) + if base:GetAlpha() > 250 then + draw.RoundedBoxEx(12, 0, 0, w, h, theme.Colors[3], false, false, true, true) + end + draw.RoundedBoxEx(12, 1, 0, w - 2, h - 1, color_black, false, false, true, true) + local mp = h / 2 + 100 + surface.SetMaterial(theme.BGMat) + surface.SetDrawColor(color_white) + surface.DrawTexturedRectUV(0, 0, w, mp, 0, 0, w / 500, mp / 800) + surface.SetDrawColor(theme.Colors[3]) + surface.DrawLine(25, mp, w - 50, mp) + local text, _, hd = MSD.TextWrap(self.text, "MSDFont.28", w - 50) + surface.DrawLine(25, h / 2 - hd, w - 50, h / 2 - hd) + draw.DrawText(base.npc_table.name, "MSDFont.32", 25, (h / 2 + 40) - hd, theme.Colors[2], TEXT_ALIGN_LEFT) + draw.DrawText(text, "MSDFont.28", 25, (h / 2 + 90 ) - hd, theme.Colors[1], TEXT_ALIGN_LEFT) + end + + base.panelHst = vgui.Create("MSDPanelList", base) + base.panelHst:SetSize(sw / 3 - 10, sh / 2) + base.panelHst:SetPos(sw - sw / 3 - 95, 0) + base.panelHst:EnableVerticalScrollbar() + base.panelHst:EnableHorizontal(true) + base.panelHst:SetSpacing(2) + base.panelHst.IgnoreVbar = true + base.panelHst.Paint = function(self, w, h) + + end + +end + +theme.FrameUpdate = function(base, dialog) + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + base.panelNPC.changed = nil + base.panelNPC.text = dialog["Line"] + base.panelNPC.line = "" + base.panelPLY.UpdateF() + + local _, _, shd = MSD.TextWrap(dialog["Line"], "MSDFont.28", base.panelNPC:GetWide() - 50) + base.panelHst:SetSize(base.panelHst:GetWide(), base.panelNPC:GetTall() / 2 - shd) + + if base.History[base.HtrProgress - 1] then + local pnl = vgui.Create("DPanel") + pnl.StaticScale = { + w = 1, + fixed_h = 50, + minw = 200, + minh = 50 + } + pnl.htr = base.History[base.HtrProgress - 1] + pnl.Paint = function(self, w, h) + local text, _, hd = MSD.TextWrap(self.htr.line, "MSDFont.28", w - 50) + draw.DrawText(text, "MSDFont.28", 25, 25, theme.Colors[3], TEXT_ALIGN_LEFT) + draw.DrawText("- " .. self.htr.ans, "MSDFont.28", w - 25, hd + 30, theme.Colors[3], TEXT_ALIGN_RIGHT) + + hd = hd + 70 + if hd > self.StaticScale.minh and w > 100 then + self.StaticScale.fixed_h = hd + self.StaticScale.minh = hd + base.panelHst:PerformLayout() + end + end + base.panelHst:AddItem(pnl) + timer.Simple(0, function() + if not IsValid(base) or not IsValid(base.panelHst) or not IsValid(pnl) then return end + base.panelHst:ScrollToChild(pnl) + end) + end +end + +theme.PopulateAns = function(base, k, ans, mid) + local button = vgui.Create("DButton", bg) + + button.StaticScale = { + w = 1, + fixed_h = 35, + minw = 200, + minh = 35 + } + + button:SetText("") + button.fade = 0 + + button.Paint = function(self, w, h) + if self.hover then + self.fade = Lerp(FrameTime() * 7, self.fade, 1) + else + self.fade = Lerp(FrameTime() * 7, self.fade, 0) + end + + local text, _, texth = MSD.TextWrap("- " .. ans[1], "MSDFont.28", w - 50) + draw.DrawText(text, "MSDFont.28", w - 25, h / 2 - texth / 2, MSD.ColorAlpha(theme.Colors[1], 255 - self.fade * 255), TEXT_ALIGN_RIGHT) + draw.DrawText(text, "MSDFont.28", w - 25, h / 2 - texth / 2, MSD.ColorAlpha(theme.Colors[2], self.fade * 255), TEXT_ALIGN_RIGHT) + + texth = texth + 25 + if texth > self.StaticScale.minh and w > 200 then + self.StaticScale.minh = texth + self.StaticScale.fixed_h = texth + base.panelPLY:PerformLayout() + end + + if self.fade > 0.1 then + surface.SetDrawColor(MSD.ColorAlpha(theme.Colors[2], self.fade * 255)) + local hd = (h / 2) * self.fade + surface.DrawLine(25, h / 2 - hd, w - 25, h / 2 - hd) + surface.DrawLine(25, h / 2 + hd - 1, w - 25, h / 2 + hd - 1) + end + + + end + + button.DoClick = function() + if base.panelPLY:GetAlpha() > 200 then + base.DoAnswer(ans, k, mid) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + base.panelPLY:AddItem(button) +end + +hook.Add("HUDShouldDraw", "MCS.Backbone.HideHUD", function() + if theme.InProgress then return false end +end) + +hook.Add("CalcView", "MCS.Backbone.CalcView", function(ply, pos, angles, fov) + if not MCS.Config.EnableCamera then return end + if theme.InProgress then + local view = { + origin = theme.Cam.pos, + angles = theme.Cam.ang, + fov = theme.Cam.fov, + drawviewer = true + } + + return view + else + theme.Cam.fov = fov + end +end) \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/themes/hk_theme.lua b/addons/mc_simple_npcs/lua/mcs_npcs/themes/hk_theme.lua new file mode 100644 index 0000000..9ce684b --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/themes/hk_theme.lua @@ -0,0 +1,165 @@ +MCS.Themes["Hollow Knight"] = {} + +if SERVER then + resource.AddWorkshop("1932352136") -- Content (Fonts and materials) + return +end + +local NewFont = surface.CreateFont + +NewFont("MCS_HKMain", { + font = "Kurale", + extended = true, + size = 35, + antialias = true, + weight = 800 +}) + +NewFont("MCS_HKSub", { + font = "Kurale", + extended = true, + size = 46, + antialias = true, + weight = 800, + outline = true +}) + +local theme = MCS.Themes["Hollow Knight"] + +theme.Mats = { + top = Material("mcs_ui/hn_top.png", "smooth"), + but = Material("mcs_ui/hn_but.png", "smooth"), + grad = Material("gui/center_gradient.vtf"), + opt = Material("mcs_ui/hn_opt.png", "smooth"), +} + +theme.FadeIn = true +theme.InProgress = false +theme.Process = function(base) end + +theme.FramePaint = function(self, w, h) + MSD.DrawTexturedRect(0, 0, w, h, MSD.Materials.vignette, color_black) + draw.DrawText(self.npc_table.name, "MCS_HKSub", w / 10, h / 2 + h / 4, color_white, TEXT_ALIGN_LEFT) +end + +theme.FrameBuild = function(base) + local sw, sh = base:GetWide(), base:GetTall() + local textspd = math.Clamp(MCS.Config.TextSpeed, 1, 10) + base.panelPLY = vgui.Create("MSDPanelList", base) + base.panelPLY:SetSize(sw - sw / 4, 200) + base.panelPLY:SetPos(sw / 6, sh - sh / 3) + base.panelPLY:EnableVerticalScrollbar() + base.panelPLY:EnableHorizontal(true) + base.panelPLY:SetSpacing(2) + base.panelPLY:SetAlpha(1) + base.panelPLY.IgnoreVbar = true + + base.panelPLY.UpdateF = function() + base.panelPLY:Clear() + end + + base.panelNPC = vgui.Create("DPanel", base) + base.panelNPC:SetSize(sw - sw / 4, 300) + base.panelNPC:SetPos(sw / 6, 50) + base.panelNPC.line = "" + base.panelNPC.text = "" + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + + base.panelNPC.Paint = function(self, w, h) + MSD.DrawTexturedRect(0, 0, w, h, theme.Mats.grad, color_black) + MSD.DrawTexturedRect(w / 2 - 300, 0, 600, 80, theme.Mats.top, color_white) + MSD.DrawTexturedRect(w / 2 - 200, h - 80, 400, 60, theme.Mats.but, color_white) + local text = MSD.TextWrap(self.line, "MCS_HKMain", w - 5) + draw.DrawText(text, "MCS_HKMain", 1, 86, color_black, TEXT_ALIGN_LEFT) + draw.DrawText(text, "MCS_HKMain", 0, 85, color_white, TEXT_ALIGN_LEFT) + local _, th = surface.GetTextSize(text) + + if 160 + th > h then + self:SetSize(w, 160 + th) + local sx = base.panelPLY:GetPos() + base.panelPLY:SetSize(base.panelPLY:GetWide(), self:GetTall() - 80) + base.panelPLY:SetPos(sx, self:GetTall() + 80) + end + end + + base.panelNPC.Think = function(self) + local CT = CurTime() + + if (CT > self.typetime) and self.text ~= self.line and base:GetAlpha() > 150 then + self.typepos = self.typepos + 1 + self.typetime = CT + FrameTime() * textspd + self.line = string.subUTF8(self.text, 1, self.typepos) + + if input.IsMouseDown(MOUSE_FIRST) then + self.typetime = CT + 10 + self.typepos = string.len(self.text) + self.line = self.text + end + end + + if self.text == self.line and base.panelPLY and not self.changed then + self.changed = true + base.panelPLY:AlphaTo(255, 0.3, 0.3) + end + end +end + +theme.FrameUpdate = function(base, dialog) + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + base.panelNPC.changed = nil + base.panelNPC.text = dialog["Line"] + base.panelNPC.line = "" + base.panelNPC:SetSize(base.panelNPC:GetSize(), 300) + base.panelPLY:SetAlpha(0) + base.panelPLY.UpdateF() +end + +theme.PopulateAns = function(base, k, ans) + local button = vgui.Create("DButton", bg) + + button.StaticScale = { + w = 1, + fixed_h = 50, + minw = 200, + minh = 50 + } + + button:SetText("") + button.fade = 0 + + button.Paint = function(self, w, h) + if self.hover then + self.fade = Lerp(FrameTime() * 7, self.fade, 1) + else + self.fade = Lerp(FrameTime() * 7, self.fade, 0) + end + + if self.fade > 0.01 then + surface.SetFont("MCS_HKMain") + local tw = surface.GetTextSize(ans[1]) + MSD.DrawTexturedRect(w / 2 - tw, 0, tw * 2, h, theme.Mats.grad, MSD.ColorAlpha(color_black, 255 * self.fade)) + MSD.DrawTexturedRectRotated(180, (w / 2 - tw / 2) - 44 * self.fade, h / 2, 44 * self.fade, 35 * self.fade, theme.Mats.opt, color_white) + MSD.DrawTexturedRectRotated(0, (w / 2 + tw / 2) + 44 * self.fade, h / 2, 44 * self.fade, 35 * self.fade, theme.Mats.opt, color_white) + end + + draw.SimpleText(ans[1], "MCS_HKMain", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER, 1) + end + + button.DoClick = function() + if base.panelPLY:GetAlpha() > 200 then + base.DoAnswer(ans) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + base.panelPLY:AddItem(button) +end \ No newline at end of file diff --git a/addons/mc_simple_npcs/lua/mcs_npcs/themes/retro_theme.lua b/addons/mc_simple_npcs/lua/mcs_npcs/themes/retro_theme.lua new file mode 100644 index 0000000..fbcfa24 --- /dev/null +++ b/addons/mc_simple_npcs/lua/mcs_npcs/themes/retro_theme.lua @@ -0,0 +1,205 @@ +MCS.Themes["Retro"] = {} +if SERVER then return end +local NewFont = surface.CreateFont + +NewFont("MCS_RetroMain", { + font = "DPix_8pt", + extended = true, + size = 25, + antialias = true, + weight = 800 +}) + +NewFont("MCS_RetroSub", { + font = "DPix_8pt", + extended = true, + size = 32, + antialias = true, + weight = 800 +}) + +local theme = MCS.Themes["Retro"] +theme.FadeIn = true +theme.InProgress = false + +theme.Cam = { + pos = Vector(0, 0, 0), + ang = Angle(0, 0, 0), + fov = 90, + rotate = 180, +} + +theme.Process = function(base) + if not MCS.Config.EnableCamera then return end + local ent + local npc = base.NPC + local ply = LocalPlayer() + + if not IsValid(npc) then + ent = ply + else + ent = npc + end + + local head = ent:LookupBone("ValveBiped.Bip01_Head1") + if not head then return end + local sPos, sAng = ent:GetBonePosition(head) + if not sPos then return end + sAng = Angle(0, ent:GetAngles().y, 0) + sAng:RotateAroundAxis(sAng:Up(), theme.Cam.rotate) + + if not base.AnimInit then + base.AnimInit = true + theme.Cam.pos = ply:EyePos() + theme.Cam.ang = ply:GetAngles() + end + + local tr = util.TraceLine({ + start = sPos, + endpos = sPos - sAng:Forward() * 45, + filter = ent + }) + + theme.Cam.pos = LerpVector(FrameTime() * 3, theme.Cam.pos, tr.HitPos) + theme.Cam.ang = LerpAngle(FrameTime() * 3, theme.Cam.ang, sAng) + theme.Cam.fov = Lerp(FrameTime() * 3, theme.Cam.fov, 60) +end + +theme.FramePaint = function(self, w, h) end + +theme.FrameBuild = function(base) + local sw, sh = base:GetWide(), base:GetTall() + local textspd = math.Clamp(MCS.Config.TextSpeed, 1, 10) + base.panelPLY = vgui.Create("MSDPanelList", base) + base.panelPLY:SetSize(sw - sw / 4, 200) + base.panelPLY:SetPos(sw / 6, sh / 2 + 100) + base.panelPLY:EnableVerticalScrollbar() + base.panelPLY:EnableHorizontal(true) + base.panelPLY:SetSpacing(2) + base.panelPLY:SetAlpha(1) + base.panelPLY.IgnoreVbar = true + + base.panelPLY.UpdateF = function() + base.panelPLY:Clear() + end + + base.panelNPC = vgui.Create("DPanel", base) + base.panelNPC:SetSize(sw - sw / 4, 50) + base.panelNPC:SetPos(sw / 6, sh / 2 + 100) + base.panelNPC.line = "" + base.panelNPC.text = "" + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + + base.panelNPC.Paint = function(self, w, h) + draw.RoundedBox(8, 0, 0, w, h, color_black) + draw.DrawText(base.npc_table.name, "MCS_RetroSub", 10, 5, MCS.Config.Retro.Color, TEXT_ALIGN_LEFT) + local text = MSD.TextWrap(self.line, "MCS_RetroMain", w - 20) + draw.DrawText(text, "MCS_RetroMain", 10, 35, color_white, TEXT_ALIGN_LEFT) + local _, th = surface.GetTextSize(text) + + if 50 + th > h then + self:SetSize(w, 50 + th) + local y = ScrH() / 2 + 100 + local sx = base.panelPLY:GetPos() + base.panelPLY:SetSize(base.panelPLY:GetWide(), y - self:GetTall() - 20) + base.panelPLY:SetPos(sx, y + self:GetTall() + 20) + end + end + + base.panelNPC.Think = function(self) + local CT = CurTime() + + if (CT > self.typetime) and self.text ~= self.line and base:GetAlpha() > 150 then + self.typepos = self.typepos + 1 + self.typetime = CT + FrameTime() * textspd + self.line = string.subUTF8(self.text, 1, self.typepos) + + if MCS.Config.Retro.SFX then + CreateSound(LocalPlayer(), "weapons/grenade/tick1.wav"):PlayEx(0.1, math.random(177, 200)) + end + + if input.IsMouseDown(MOUSE_FIRST) then + self.typetime = CT + 10 + self.typepos = string.len(self.text) + self.line = self.text + end + end + + if self.text == self.line and base.panelPLY and not self.changed then + self.changed = true + base.panelPLY:AlphaTo(255, 0.3, 0.3) + end + end +end + +theme.FrameUpdate = function(base, dialog) + base.panelNPC.typepos = 0 + base.panelNPC.typetime = 0 + base.panelNPC.changed = nil + base.panelNPC.text = dialog["Line"] + base.panelNPC.line = "" + base.panelNPC:SetSize(base.panelNPC:GetSize(), 50) + base.panelPLY:SetAlpha(0) + base.panelPLY.UpdateF() +end + +theme.PopulateAns = function(base, k, ans, mid) + local button = vgui.Create("DButton", bg) + + button.StaticScale = { + w = 1, + fixed_h = 50, + minw = 200, + minh = 50 + } + + button:SetText("") + + button.Paint = function(self, w, h) + draw.RoundedBox(8, 0, 0, w, h, color_black) + + if self.hover then + draw.RoundedBox(8, 0, 0, w, h, MCS.Config.Retro.Color) + end + + draw.RoundedBox(6, 2, 2, w - 4, h - 4, color_black) + draw.DrawText(ans[1], "MCS_RetroSub", w / 2, 6, color_white, TEXT_ALIGN_CENTER) + end + + button.DoClick = function() + if base.panelPLY:GetAlpha() > 200 then + base.DoAnswer(ans, k, mid) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + base.panelPLY:AddItem(button) +end + +hook.Add("HUDShouldDraw", "MCS.Retro.HideHUD", function() + if theme.InProgress then return false end +end) + +hook.Add("CalcView", "MCS.Retro.CalcView", function(ply, pos, angles, fov) + if not MCS.Config.EnableCamera then return end + if theme.InProgress then + local view = { + origin = theme.Cam.pos, + angles = theme.Cam.ang, + fov = theme.Cam.fov, + drawviewer = true + } + + return view + else + theme.Cam.fov = fov + end +end) \ No newline at end of file diff --git a/addons/mc_simple_npcs/materials/mcs_ui/hn_but.png b/addons/mc_simple_npcs/materials/mcs_ui/hn_but.png new file mode 100644 index 0000000..469e1dd Binary files /dev/null and b/addons/mc_simple_npcs/materials/mcs_ui/hn_but.png differ diff --git a/addons/mc_simple_npcs/materials/mcs_ui/hn_opt.png b/addons/mc_simple_npcs/materials/mcs_ui/hn_opt.png new file mode 100644 index 0000000..b751ca2 Binary files /dev/null and b/addons/mc_simple_npcs/materials/mcs_ui/hn_opt.png differ diff --git a/addons/mc_simple_npcs/materials/mcs_ui/hn_top.png b/addons/mc_simple_npcs/materials/mcs_ui/hn_top.png new file mode 100644 index 0000000..e4e92bd Binary files /dev/null and b/addons/mc_simple_npcs/materials/mcs_ui/hn_top.png differ diff --git a/addons/mc_simple_npcs/resource/fonts/DPix.ttf b/addons/mc_simple_npcs/resource/fonts/DPix.ttf new file mode 100644 index 0000000..e231336 Binary files /dev/null and b/addons/mc_simple_npcs/resource/fonts/DPix.ttf differ diff --git a/addons/mc_simple_npcs/resource/fonts/Kurale.ttf b/addons/mc_simple_npcs/resource/fonts/Kurale.ttf new file mode 100644 index 0000000..7095372 Binary files /dev/null and b/addons/mc_simple_npcs/resource/fonts/Kurale.ttf differ diff --git a/addons/mc_simple_npcs/resource/fonts/futuracp.ttf b/addons/mc_simple_npcs/resource/fonts/futuracp.ttf new file mode 100644 index 0000000..169e40f Binary files /dev/null and b/addons/mc_simple_npcs/resource/fonts/futuracp.ttf differ diff --git a/addons/mc_simple_npcs/resource/fonts/zx7.ttf b/addons/mc_simple_npcs/resource/fonts/zx7.ttf new file mode 100644 index 0000000..a8d57dc Binary files /dev/null and b/addons/mc_simple_npcs/resource/fonts/zx7.ttf differ diff --git a/addons/more_materials/lua/autorun/client/morematerials.lua b/addons/more_materials/lua/autorun/client/morematerials.lua new file mode 100644 index 0000000..c49cb13 --- /dev/null +++ b/addons/more_materials/lua/autorun/client/morematerials.lua @@ -0,0 +1,279 @@ +// adding materials to the material toolguns list + +list.Add( "OverrideMaterials", "models/XQM//Deg360" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesGB" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesRed" ) +list.Add( "OverrideMaterials", "models/XQM//SquaredMat" ) +list.Add( "OverrideMaterials", "models/XQM//WoodTexture_1" ) +list.Add( "OverrideMaterials", "models/airboat/airboat_blur02" ) +list.Add( "OverrideMaterials", "models/alyx/emptool_glow" ) +list.Add( "OverrideMaterials", "models/antlion/antlion_innards" ) +list.Add( "OverrideMaterials", "models/barnacle/roots" ) +list.Add( "OverrideMaterials", "models/combine_advisor/body9" ) +list.Add( "OverrideMaterials", "models/combine_advisor/mask" ) +list.Add( "OverrideMaterials", "models/combine_scanner/scanner_eye" ) +list.Add( "OverrideMaterials", "models/debug/debugwhite" ) +list.Add( "OverrideMaterials", "models/dog/eyeglass" ) +list.Add( "OverrideMaterials", "models/effects/portalrift_sheet" ) +list.Add( "OverrideMaterials", "models/effects/slimebubble_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode1_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode_sheet" ) +list.Add( "OverrideMaterials", "models/gibs/metalgibs/metal_gibs" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs01" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs02" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs03" ) +list.Add( "OverrideMaterials", "models/player/player_chrome1" ) +list.Add( "OverrideMaterials", "models/props_animated_breakable/smokestack/brickwall002a" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_buildings/destroyedbuilldingwall01a" ) +list.Add( "OverrideMaterials", "models/props_buildings/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_c17/frostedglass_01a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric001a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric002a" ) +list.Add( "OverrideMaterials", "models/props_c17/furnituremetal001a" ) +list.Add( "OverrideMaterials", "models/props_c17/gate_door02a" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder001" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder002" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder003" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01a" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01b" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01c" ) +list.Add( "OverrideMaterials", "models/props_canal/canalmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/coastmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/metalcrate001d" ) +list.Add( "OverrideMaterials", "models/props_canal/metalwall005b" ) +list.Add( "OverrideMaterials", "models/props_canal/rock_riverbed01a" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable_b" ) +list.Add( "OverrideMaterials", "models/props_combine/com_shield001a" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_interface_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_monitorbay_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/metal_combinebridge001" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes01" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes03" ) +list.Add( "OverrideMaterials", "models/props_combine/prtl_sky_sheet" ) +list.Add( "OverrideMaterials", "models/props_combine/stasisfield_beam" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template010a" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template022j" ) +list.Add( "OverrideMaterials", "models/props_debris/composite_debris" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor013a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor020a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretewall019a" ) +list.Add( "OverrideMaterials", "models/props_debris/metalwall001a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterceiling008a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall009d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall039c" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall040c" ) +list.Add( "OverrideMaterials", "models/props_debris/tilefloor001c" ) +list.Add( "OverrideMaterials", "models/props_foliage/driftwood_01a" ) +list.Add( "OverrideMaterials", "models/props_foliage/oak_tree01" ) +list.Add( "OverrideMaterials", "models/props_foliage/tree_deciduous_01a_trunk" ) +list.Add( "OverrideMaterials", "models/props_interiors/metalfence007a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01b" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01c" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01d" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01e" ) +list.Add( "OverrideMaterials", "models/props_lab/Tank_Glass001" ) +list.Add( "OverrideMaterials", "models/props_lab/cornerunit_cloud" ) +list.Add( "OverrideMaterials", "models/props_lab/door_klab01" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens2" ) +list.Add( "OverrideMaterials", "models/props_lab/warp_sheet" ) +list.Add( "OverrideMaterials", "models/props_lab/xencrystal_sheet" ) +list.Add( "OverrideMaterials", "models/props_pipes/GutterMetal01a") +list.Add( "OverrideMaterials", "models/props_pipes/destroyedpipes01a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipemetal001a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipeset_metal02" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin1" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin2" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001b" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretefloor010a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall064b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall066a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/dirtwall001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/metal_tram001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/quarryobjects01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff04a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockgranite02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat02" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a_skin2" ) +list.Add( "OverrideMaterials", "models/shadertest/predator" ) +list.Add( "OverrideMaterials", "models/weapons/v_crossbow/rebar_glow" ) +list.Add( "OverrideMaterials", "models/weapons/v_crowbar/crowbar_cyl" ) +list.Add( "OverrideMaterials", "models/weapons/v_grenade/grenade body" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light1" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light2" ) +list.Add( "OverrideMaterials", "models/weapons/v_smg1/texture5" ) +list.Add( "OverrideMaterials", "models/XQM/BoxFull_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CellShadedCamo_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CinderBlock_Tex" ) +list.Add( "OverrideMaterials", "models/XQM/JetBody2TailPiece_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/PoleX1_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/Rails/gumball_1" ) +list.Add( "OverrideMaterials", "models/XQM/SquaredMatInverted" ) +list.Add( "OverrideMaterials", "models/XQM/WoodPlankTexture" ) +list.Add( "OverrideMaterials", "models/XQM/boxfull_diffuse" ) +list.Add( "OverrideMaterials", "models/dav0r/hoverball" ) +list.Add( "OverrideMaterials", "models/spawn_effect" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_white" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_wood" ) +list.Add( "OverrideMaterials", "phoenix_storms/Future_vents" ) +list.Add( "OverrideMaterials", "phoenix_storms/FuturisticTrackRamp_1-2" ) +list.Add( "OverrideMaterials", "phoenix_storms/OfficeWindow_1-1" ) +list.Add( "OverrideMaterials", "phoenix_storms/Pro_gear_side" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/blue_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/camera" ) +list.Add( "OverrideMaterials", "phoenix_storms/car_tire" ) +list.Add( "OverrideMaterials", "phoenix_storms/checkers_map" ) +list.Add( "OverrideMaterials", "phoenix_storms/cigar" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete0" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete1" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete2" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete3" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier2_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_pipe_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/egg" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear_top" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/heli" ) +list.Add( "OverrideMaterials", "phoenix_storms/indentTiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/iron_rails" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_wheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalfence004a" ) +list.Add( "OverrideMaterials", "phoenix_storms/middle" ) +list.Add( "OverrideMaterials", "phoenix_storms/mrref2" ) +list.Add( "OverrideMaterials", "phoenix_storms/output_jack" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/interior_sides" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/train_floor" ) +list.Add( "OverrideMaterials", "phoenix_storms/potato" ) +list.Add( "OverrideMaterials", "phoenix_storms/pro_gear_top2" ) +list.Add( "OverrideMaterials", "phoenix_storms/ps_grass" ) +list.Add( "OverrideMaterials", "phoenix_storms/road" ) +list.Add( "OverrideMaterials", "phoenix_storms/roadside" ) +list.Add( "OverrideMaterials", "phoenix_storms/scrnspace" ) +list.Add( "OverrideMaterials", "phoenix_storms/side" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic1" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/smallwheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/spheremappy" ) +list.Add( "OverrideMaterials", "phoenix_storms/t_light" ) +list.Add( "OverrideMaterials", "phoenix_storms/thruster" ) +list.Add( "OverrideMaterials", "phoenix_storms/tiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/top" ) +list.Add( "OverrideMaterials", "phoenix_storms/torpedo" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamside" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamtop" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plateside" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_fps" ) +list.Add( "OverrideMaterials", "phoenix_storms/window" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_blue" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_green" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_red" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_dome" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_side" ) + +// Checking if CSS is mounted and adding CSS textures if it is + +function engine.IsMounted(g) + for k,v in pairs(engine.GetGames()) do + if (' cstrike' ) then + return true; + end + end +end + +if IsMounted( 'cstrike' ) and (engine.IsMounted('cstrike')) then + +list.Add( "OverrideMaterials", "models/cs_havana/wndb" ) +list.Add( "OverrideMaterials", "models/cs_havana/wndd" ) +list.Add( "OverrideMaterials", "models/cs_italy/light_orange" ) +list.Add( "OverrideMaterials", "models/cs_italy/plaster" ) +list.Add( "OverrideMaterials", "models/cs_italy/pwtrim2" ) +list.Add( "OverrideMaterials", "models/de_cbble/wndarch" ) +list.Add( "OverrideMaterials", "models/de_chateau/ch_arch_b1" ) +list.Add( "OverrideMaterials", "models/pi_window/plaster" ) +list.Add( "OverrideMaterials", "models/pi_window/trim128" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/dollar" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/fireescapefloor" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/metal_stairs1" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap02" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneytop" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/pylon" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/boulder01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milceil001" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarock" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarockb" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milwall006" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/rocks01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams02" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams03" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/RoofEdges" ) +list.Add( "OverrideMaterials", "models/props/cs_office/clouds" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet2" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet3" ) +list.Add( "OverrideMaterials", "models/props/cs_office/screen" ) +list.Add( "OverrideMaterials", "models/props/cs_office/snowmana" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/de_inferno_boulder_03" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflra" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflrd" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/inftowertop" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/offwndwb_break" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/roofbits" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/tileroof01" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/woodfloor008a" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukconcretewalla" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukecardboard" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/pipeset_metal" ) + +end + + + +// Making sure there's no double materials in the list in case of other addons, plus sorting them + +timer.Simple(0, function() + local mats = list.GetForEdit("OverrideMaterials"); + local cleaner = {}; + for i, mat in pairs(mats) do + cleaner[mat] = true; + mats[i] = nil; + end + local i = 1; + for mat in pairs(cleaner) do + mats[i] = mat; + i = i + 1; + end + table.sort(mats); +end); diff --git a/addons/more_materials/lua/autorun/server/morematerials.lua b/addons/more_materials/lua/autorun/server/morematerials.lua new file mode 100644 index 0000000..c49cb13 --- /dev/null +++ b/addons/more_materials/lua/autorun/server/morematerials.lua @@ -0,0 +1,279 @@ +// adding materials to the material toolguns list + +list.Add( "OverrideMaterials", "models/XQM//Deg360" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesGB" ) +list.Add( "OverrideMaterials", "models/XQM//LightLinesRed" ) +list.Add( "OverrideMaterials", "models/XQM//SquaredMat" ) +list.Add( "OverrideMaterials", "models/XQM//WoodTexture_1" ) +list.Add( "OverrideMaterials", "models/airboat/airboat_blur02" ) +list.Add( "OverrideMaterials", "models/alyx/emptool_glow" ) +list.Add( "OverrideMaterials", "models/antlion/antlion_innards" ) +list.Add( "OverrideMaterials", "models/barnacle/roots" ) +list.Add( "OverrideMaterials", "models/combine_advisor/body9" ) +list.Add( "OverrideMaterials", "models/combine_advisor/mask" ) +list.Add( "OverrideMaterials", "models/combine_scanner/scanner_eye" ) +list.Add( "OverrideMaterials", "models/debug/debugwhite" ) +list.Add( "OverrideMaterials", "models/dog/eyeglass" ) +list.Add( "OverrideMaterials", "models/effects/portalrift_sheet" ) +list.Add( "OverrideMaterials", "models/effects/slimebubble_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode1_sheet" ) +list.Add( "OverrideMaterials", "models/effects/splode_sheet" ) +list.Add( "OverrideMaterials", "models/gibs/metalgibs/metal_gibs" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs01" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs02" ) +list.Add( "OverrideMaterials", "models/gibs/woodgibs/woodgibs03" ) +list.Add( "OverrideMaterials", "models/player/player_chrome1" ) +list.Add( "OverrideMaterials", "models/props_animated_breakable/smokestack/brickwall002a" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_building_details/courtyard_template001c_bars" ) +list.Add( "OverrideMaterials", "models/props_buildings/destroyedbuilldingwall01a" ) +list.Add( "OverrideMaterials", "models/props_buildings/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_c17/frostedglass_01a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric001a" ) +list.Add( "OverrideMaterials", "models/props_c17/furniturefabric002a" ) +list.Add( "OverrideMaterials", "models/props_c17/furnituremetal001a" ) +list.Add( "OverrideMaterials", "models/props_c17/gate_door02a" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder001" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder002" ) +list.Add( "OverrideMaterials", "models/props_c17/metalladder003" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01a" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01b" ) +list.Add( "OverrideMaterials", "models/props_canal/canal_bridge_railing_01c" ) +list.Add( "OverrideMaterials", "models/props_canal/canalmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/coastmap_sheet" ) +list.Add( "OverrideMaterials", "models/props_canal/metalcrate001d" ) +list.Add( "OverrideMaterials", "models/props_canal/metalwall005b" ) +list.Add( "OverrideMaterials", "models/props_canal/rock_riverbed01a" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable" ) +list.Add( "OverrideMaterials", "models/props_combine/citadel_cable_b" ) +list.Add( "OverrideMaterials", "models/props_combine/com_shield001a" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_interface_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/combine_monitorbay_disp" ) +list.Add( "OverrideMaterials", "models/props_combine/metal_combinebridge001" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes01" ) +list.Add( "OverrideMaterials", "models/props_combine/pipes03" ) +list.Add( "OverrideMaterials", "models/props_combine/prtl_sky_sheet" ) +list.Add( "OverrideMaterials", "models/props_combine/stasisfield_beam" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template010a" ) +list.Add( "OverrideMaterials", "models/props_debris/building_template022j" ) +list.Add( "OverrideMaterials", "models/props_debris/composite_debris" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor013a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretefloor020a" ) +list.Add( "OverrideMaterials", "models/props_debris/concretewall019a" ) +list.Add( "OverrideMaterials", "models/props_debris/metalwall001a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterceiling008a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall009d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall021a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034a" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall034d" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall039c" ) +list.Add( "OverrideMaterials", "models/props_debris/plasterwall040c" ) +list.Add( "OverrideMaterials", "models/props_debris/tilefloor001c" ) +list.Add( "OverrideMaterials", "models/props_foliage/driftwood_01a" ) +list.Add( "OverrideMaterials", "models/props_foliage/oak_tree01" ) +list.Add( "OverrideMaterials", "models/props_foliage/tree_deciduous_01a_trunk" ) +list.Add( "OverrideMaterials", "models/props_interiors/metalfence007a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01a" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01b" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01c" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01d" ) +list.Add( "OverrideMaterials", "models/props_junk/plasticcrate01e" ) +list.Add( "OverrideMaterials", "models/props_lab/Tank_Glass001" ) +list.Add( "OverrideMaterials", "models/props_lab/cornerunit_cloud" ) +list.Add( "OverrideMaterials", "models/props_lab/door_klab01" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens" ) +list.Add( "OverrideMaterials", "models/props_lab/security_screens2" ) +list.Add( "OverrideMaterials", "models/props_lab/warp_sheet" ) +list.Add( "OverrideMaterials", "models/props_lab/xencrystal_sheet" ) +list.Add( "OverrideMaterials", "models/props_pipes/GutterMetal01a") +list.Add( "OverrideMaterials", "models/props_pipes/destroyedpipes01a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipemetal001a" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipeset_metal02" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin1" ) +list.Add( "OverrideMaterials", "models/props_pipes/pipesystem01a_skin2" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001b" ) +list.Add( "OverrideMaterials", "models/props_vents/borealis_vent001c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretefloor010a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall064b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/concretewall066a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/dirtwall001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/metal_tram001a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/quarryobjects01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02b" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff02c" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockcliff04a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/rockgranite02a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat01" ) +list.Add( "OverrideMaterials", "models/props_wasteland/tugboat02" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a" ) +list.Add( "OverrideMaterials", "models/props_wasteland/wood_fence01a_skin2" ) +list.Add( "OverrideMaterials", "models/shadertest/predator" ) +list.Add( "OverrideMaterials", "models/weapons/v_crossbow/rebar_glow" ) +list.Add( "OverrideMaterials", "models/weapons/v_crowbar/crowbar_cyl" ) +list.Add( "OverrideMaterials", "models/weapons/v_grenade/grenade body" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light1" ) +list.Add( "OverrideMaterials", "models/weapons/v_slam/new light2" ) +list.Add( "OverrideMaterials", "models/weapons/v_smg1/texture5" ) +list.Add( "OverrideMaterials", "models/XQM/BoxFull_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CellShadedCamo_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/CinderBlock_Tex" ) +list.Add( "OverrideMaterials", "models/XQM/JetBody2TailPiece_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/PoleX1_diffuse" ) +list.Add( "OverrideMaterials", "models/XQM/Rails/gumball_1" ) +list.Add( "OverrideMaterials", "models/XQM/SquaredMatInverted" ) +list.Add( "OverrideMaterials", "models/XQM/WoodPlankTexture" ) +list.Add( "OverrideMaterials", "models/XQM/boxfull_diffuse" ) +list.Add( "OverrideMaterials", "models/dav0r/hoverball" ) +list.Add( "OverrideMaterials", "models/spawn_effect" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_white" ) +list.Add( "OverrideMaterials", "phoenix_storms/Fender_wood" ) +list.Add( "OverrideMaterials", "phoenix_storms/Future_vents" ) +list.Add( "OverrideMaterials", "phoenix_storms/FuturisticTrackRamp_1-2" ) +list.Add( "OverrideMaterials", "phoenix_storms/OfficeWindow_1-1" ) +list.Add( "OverrideMaterials", "phoenix_storms/Pro_gear_side" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/black_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/blue_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/camera" ) +list.Add( "OverrideMaterials", "phoenix_storms/car_tire" ) +list.Add( "OverrideMaterials", "phoenix_storms/checkers_map" ) +list.Add( "OverrideMaterials", "phoenix_storms/cigar" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete0" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete1" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete2" ) +list.Add( "OverrideMaterials", "phoenix_storms/concrete3" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_barrier2_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/construct/concrete_pipe_00" ) +list.Add( "OverrideMaterials", "phoenix_storms/egg" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear" ) +list.Add( "OverrideMaterials", "phoenix_storms/gear_top" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/grey_steel" ) +list.Add( "OverrideMaterials", "phoenix_storms/heli" ) +list.Add( "OverrideMaterials", "phoenix_storms/indentTiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/iron_rails" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_carbonfiber2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_metallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic" ) +list.Add( "OverrideMaterials", "phoenix_storms/mat/mat_phx_plastic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/metal_wheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalbox2" ) +list.Add( "OverrideMaterials", "phoenix_storms/metalfence004a" ) +list.Add( "OverrideMaterials", "phoenix_storms/middle" ) +list.Add( "OverrideMaterials", "phoenix_storms/mrref2" ) +list.Add( "OverrideMaterials", "phoenix_storms/output_jack" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/chrome" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/interior_sides" ) +list.Add( "OverrideMaterials", "phoenix_storms/pack2/train_floor" ) +list.Add( "OverrideMaterials", "phoenix_storms/potato" ) +list.Add( "OverrideMaterials", "phoenix_storms/pro_gear_top2" ) +list.Add( "OverrideMaterials", "phoenix_storms/ps_grass" ) +list.Add( "OverrideMaterials", "phoenix_storms/road" ) +list.Add( "OverrideMaterials", "phoenix_storms/roadside" ) +list.Add( "OverrideMaterials", "phoenix_storms/scrnspace" ) +list.Add( "OverrideMaterials", "phoenix_storms/side" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic1" ) +list.Add( "OverrideMaterials", "phoenix_storms/simplyMetallic2" ) +list.Add( "OverrideMaterials", "phoenix_storms/smallwheel" ) +list.Add( "OverrideMaterials", "phoenix_storms/spheremappy" ) +list.Add( "OverrideMaterials", "phoenix_storms/t_light" ) +list.Add( "OverrideMaterials", "phoenix_storms/thruster" ) +list.Add( "OverrideMaterials", "phoenix_storms/tiles2" ) +list.Add( "OverrideMaterials", "phoenix_storms/top" ) +list.Add( "OverrideMaterials", "phoenix_storms/torpedo" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamside" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_beamtop" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plate" ) +list.Add( "OverrideMaterials", "phoenix_storms/trains/track_plateside" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_brushes" ) +list.Add( "OverrideMaterials", "phoenix_storms/white_fps" ) +list.Add( "OverrideMaterials", "phoenix_storms/window" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_blue" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_green" ) +list.Add( "OverrideMaterials", "phoenix_storms/wire/pcb_red" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_dome" ) +list.Add( "OverrideMaterials", "phoenix_storms/wood_side" ) + +// Checking if CSS is mounted and adding CSS textures if it is + +function engine.IsMounted(g) + for k,v in pairs(engine.GetGames()) do + if (' cstrike' ) then + return true; + end + end +end + +if IsMounted( 'cstrike' ) and (engine.IsMounted('cstrike')) then + +list.Add( "OverrideMaterials", "models/cs_havana/wndb" ) +list.Add( "OverrideMaterials", "models/cs_havana/wndd" ) +list.Add( "OverrideMaterials", "models/cs_italy/light_orange" ) +list.Add( "OverrideMaterials", "models/cs_italy/plaster" ) +list.Add( "OverrideMaterials", "models/cs_italy/pwtrim2" ) +list.Add( "OverrideMaterials", "models/de_cbble/wndarch" ) +list.Add( "OverrideMaterials", "models/de_chateau/ch_arch_b1" ) +list.Add( "OverrideMaterials", "models/pi_window/plaster" ) +list.Add( "OverrideMaterials", "models/pi_window/trim128" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/dollar" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/fireescapefloor" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/metal_stairs1" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneywrap02" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/moneytop" ) +list.Add( "OverrideMaterials", "models/props/cs_assault/pylon" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/boulder01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milceil001" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarock" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/militiarockb" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/milwall006" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/rocks01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams01" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams02" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/roofbeams03" ) +list.Add( "OverrideMaterials", "models/props/CS_militia/RoofEdges" ) +list.Add( "OverrideMaterials", "models/props/cs_office/clouds" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet2" ) +list.Add( "OverrideMaterials", "models/props/cs_office/file_cabinet3" ) +list.Add( "OverrideMaterials", "models/props/cs_office/screen" ) +list.Add( "OverrideMaterials", "models/props/cs_office/snowmana" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/de_inferno_boulder_03" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflra" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/infflrd" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/inftowertop" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/offwndwb_break" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/roofbits" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/tileroof01" ) +list.Add( "OverrideMaterials", "models/props/de_inferno/woodfloor008a" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukconcretewalla" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/nukecardboard" ) +list.Add( "OverrideMaterials", "models/props/de_nuke/pipeset_metal" ) + +end + + + +// Making sure there's no double materials in the list in case of other addons, plus sorting them + +timer.Simple(0, function() + local mats = list.GetForEdit("OverrideMaterials"); + local cleaner = {}; + for i, mat in pairs(mats) do + cleaner[mat] = true; + mats[i] = nil; + end + local i = 1; + for mat in pairs(cleaner) do + mats[i] = mat; + i = i + 1; + end + table.sort(mats); +end); diff --git a/addons/msd_ui/lua/autorun/msd_autorun.lua b/addons/msd_ui/lua/autorun/msd_autorun.lua new file mode 100644 index 0000000..5bba0f1 --- /dev/null +++ b/addons/msd_ui/lua/autorun/msd_autorun.lua @@ -0,0 +1,90 @@ +-- ┏━┓┏━┳━━━┳━━━┓─────────────────────── +-- ┃┃┗┛┃┃┏━┓┣┓┏┓┃─────────────────────── +-- ┃┏┓┏┓┃┗━━┓┃┃┃┃──By MacTavish <3────── +-- ┃┃┃┃┃┣━━┓┃┃┃┃┃─────────────────────── +-- ┃┃┃┃┃┃┗━┛┣┛┗┛┃─────────────────────── +-- ┗┛┗┛┗┻━━━┻━━━┛─────────────────────── + +-- MIT License + +-- Copyright (c) 2021 Ayden Mactavish + +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: + +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. + +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + +if MSD and MSD.Version ~= "1.0.3" then MsgC( Color(255, 8, 0), "[MSD] Another version of MSD detacted\n" ) return end + +MSD = {} +MSD.Version = "1.0.3" +MSD.Config = {} +MSD.Modules = {} +MSD.ModuleIds = {} +MSD.Language = {} + +if SERVER then + util.AddNetworkString( "MSD.GetConfigData" ) + util.AddNetworkString( "MSD.SaveConfig" ) +end + +function MSD.Load() + MsgC( Color(174, 0, 255), "[MSD] Initialization started\n" ) + if !file.Exists("msd_data", "DATA") then + file.CreateDir("msd_data") + MsgC( Color(174, 0, 255), "[MSD] Server DATA Dir created \n" ) + end + + MsgC( Color(174, 0, 255), "[MSD] Initialization started\n" ) + + if SERVER then + include("msd/sh_config.lua") + include("msd/sh_language.lua") + AddCSLuaFile("msd/sh_config.lua") + AddCSLuaFile("msd/sh_language.lua") + + local f = file.Find( "msd/ui/*", "LUA" ) + for k,v in ipairs( f ) do + AddCSLuaFile( "msd/ui/" .. v ) + end + + else + include("msd/sh_config.lua") + include("msd/sh_language.lua") + + local f = file.Find( "msd/ui/*", "LUA" ) + for k,v in ipairs( f ) do + include( "msd/ui/" .. v ) + end + + list.Set( "DesktopWindows", "MSDModulesSetup", { + title = "Setup Menu", + icon = "msd/macnco.png", + width = 960, + height = 700, + onewindow = true, + init = function( icon, window ) + window:Close() + icon.Window = MSD.OpenMenuManager(g_ContextMenu) + end + } ) + end + + + MsgC( Color(174, 0, 255), "[MSD] Initialization done\n" ) +end + +MSD.Load() \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/language/de.lua b/addons/msd_ui/lua/msd/language/de.lua new file mode 100644 index 0000000..d25684d --- /dev/null +++ b/addons/msd_ui/lua/msd/language/de.lua @@ -0,0 +1,369 @@ +MSD.Language["de"] = { + + -- UI + + lang_name = "Deutsch", + + ok = "OK", + map = "Karte", + off = "Aus", + on = "An", + time_add = "Zeit zum Hinzufügen", + type = "Typ", + delay = "Verzögerung", + cancel = "Abbrechen", + enable = "Aktivieren", + model = "Model", + name = "Name", + settings = "Einstellungen", + editor = "Editor", + red = "Rot", + green = "Grün", + blue = "Blau", + admin_menu = "Administrationsmenü", + ui_settings = "UI Einstellungen", + active = "Aktiv", + inactive = "Inaktiv", + disabled = "Deaktiviert", + warning = "Warnung!", + remove = "Entfernen", + theme = "Theme", + dark_theme = "Dunkles Theme", + payment = "Zahlung", + load_autosave = "Letzte automatische Speicherung laden?", + load_save = "Speicherung laden", + create_new = "Neu erstellen", + enable_option = "Option aktivieren", + main_opt = "Hauptoptionen", + copy_data = "Kopiere Daten", + save_chng = "Speichere Änderungen", + enter_name = "Gib den Namen ein", + enter_id = "Gib die ID ein", + confirm_action = "Bitte bestätige deine Aktion", + check_fpr_errors = "Auf Fehler prüfen", + enter_description = "Gib die Beschreibung ein", + cooldown_ok = "Abklingzeit bei Erfolg", + cooldown_fail = "Abklingzeit bei Fehlschlag", + s_team_whitelist = "Team-Whitelist einrichten", + whitelist_blacklist = "Die Whitelist als Blacklist benutzen", + custom_val = "Setze spezifischen Wert", + set_hp_full = "Setze volle HP", + dist_to_close = "Distanz zum nächsten", + + e_text = "Text eingeben", + e_number = "Nummer eingeben", + e_class = "Klasse eingeben", + e_value = "Wert eingeben", + e_blank_dis = "Leer lassen, um es zu deaktiveren", + e_blank_default = "Leer lassen, um Standard zu benutzen", + e_url = "URL eingeben", + e_model = "Modelpfad eingeben", + e_material = "Materialpfad eingeben", + e_wep_class = "Waffenklasse eingeben", + e_ent_class = "Entityklasse eingeben", + e_veh_class = "Fahrzeugklasse eingeben", + e_npc_class = "NPC-Klasse eingeben", + + + select_ammo = "Ausgewählte Munition", + amount_ammo = "Munitionsmenge", + disable_phys = "Physik deaktivieren", + none = "Nichts", + custom_icon = "Setzte eigenes Icon", + weapon_name = "Waffenname", + moveup = "Aufwärts bewegen", + movedown = "Abwärts bewegen", + movepoint = "Punkt bewegen", + swap = "Tauschen", + swapmod = "Tausch-Modus aktiviert. Klicke zum Deaktivieren", + copy_from_ent = "Kopiere vom Entity", + set_pos_self = "Zu deiner Position setzen", + set_pos_aim = "Zu anvisiertem Punkt setzen", + spawn_point = "Spawnpunkt", + spawn_ang = "Spawnwinkel", + mark_area = "Markiere Bereich", + time_wait = "Zeit zum Warten", + map_marker = "Wähle Map-Makierung aus", + in_sec = "in Sekunden", + def_units = "Standard %s Einheiten", -- "Default 350 units" leave %s as is + def_seconds = "Standard %s Sekunden", -- "Default 10 seconds" leave %s as is + ent_show_pointer = "Zeiger über dem Entity anzeigen", + ent_arcade_style = "Erscheinungsbild des Entitys im Arcade-Stil", + ent_stnd_style = "Standard Entity Erscheinungsbild", + custom_color = "Aktiviere eigene Farbe", + mat_default = "Leer lassen für Standardmaterial", + + + set_ui = "UI Einstellungen", + set_hud = "HUD Einstellungen", + set_hud_pos = "Quest HUD Position", + set_hud_themes = "HUD Themes", + set_server = "Server Einstellungen", + set_ui_blur = "Unscharfer Hintergrund", + set_ui_mono = "Einfarbiger Hintergrund", + set_ui_vignette = "Vignetteneffekt für den Hintergrund", + set_ui_brightness = "Helligkeit des Hintergrunds", + set_ui_color = "Wähle die Hauptfarbe", + set_ui_align_left = "Horizontale Ausrichtung nach links", + set_ui_align_right = "Horizontale Ausrichtung nach rechts", + set_ui_align_top = "Vertikale Ausrichtung nach oben", + set_ui_align_bottom = "Vertikale Ausrichtung nach unten", + set_ui_offset_h = "Horizontaler Versatz", + set_ui_offset_v = "Vertikaler Versatz", + + + upl_changes = "Änderungen auf den Server hochladen", + res_changes = "Änderungen wiederherstellen", + + + -- Player + + + dead = "Du bist tot", + time_ex = "Zeit abgelaufen", + vehicle_bum = "Dein Fahrzeug wurde zerstört", + left_area = "Du hast den Bereich verlassen", + m_blew = "Du hast die Mission gesprengt", + m_failed = "Mission fehlgeschlagen", + m_success = "Mission erfolgreich", + m_loop = "Mission aktualisieren", + + -- Errors + + + inv_quest = "Ungültige Quest", + team_bl = "Dein Team ist auf der Blacklist", + no_players = "Der Server benötigt mehr Spieler, die gleichzeitig online sind, bevor du fortfahren kannst.", + no_players_team = "Der Server benötigt mehr Spieler, die gleichzeitig in spezifischen Teams online sind, bevor du fortfahren kannst.", + need_admin = "Nur Admins können diese Aktion durchführen", + + + -- Quests + + + active_quest = "Du hast eine aktive Quest", + inactive_quest = "Du kannst diese Quest nicht spielen", + quest_editor = "Quest Editor", + quest_list = "Quest Liste", + quests = "Quests", + leave_pnt = "Punkt zum Verlassen", + + + q_editobj = "Objekte bearbeiten", + q_incvobj = "Ungültige Objekte", + q_setobj = "Objekt Einstellungen", + q_newobj = "Neues Objekt hinzufügen", + q_editrwd = "Belohnungen bearbeiten", + q_rwdeditor = "Belohnungs Editor", + q_rwdlist = "Belohnungsliste", + q_rwdsets = "Belohnungseinstellungen", + q_findmap = "Quests aus anderen Maps suchen", + q_obj_des = "Objekt Beschreibung", + q_dist_point = "Distanz zum Punkt", + q_dist_from_point = "Distanz vom Punkt", + q_ignore_veh = "Quest Fahrzeug ignorieren", + q_timer_show = "Dem Spieler einen Timer anzeigen", + q_area_stay = "Der Spieler muss im Bereich bleiben", + q_start = "Quest starten", + q_new = "Neue Quest", + q_submit = "Quest abschicken", + q_addnew = "Neue Quest hinzufügen", + q_remove = "Quest entfernen", + q_id_unique = "Die ID muss für jede Quest einzigartig sein", + q_complete_msg = "Nachricht für fertiggestellte Quest", + q_dotime = "Nachricht für fertiggestellte Quest", + q_dotime_ok = "Schließe die Quest am Ende der Zeit ab", + q_dotime_fail = "Scheitere an der Quest am Ende der Zeit", + q_death_fail = "Scheitere die Quest beim Tod des Spielers", + q_loop = "Schleifenaufgaben Quest", + q_loop_reward = "Belohne Spieler bei jeder Wiederholung", + q_enable = "Aktiviere Quest", + q_events = "Events", + q_eventadd = "Event hinzufügen", + q_eventedit = "Event bearbeiten", + q_eventremove = "Event entfernen", + q_in_progress = "Quest in Bearbeitung", + q_time_left = "Verbleibende Zeit", + q_ply_limit = "Spielerlimit für die Quest", + q_ply_team_limit = "Teamlimit einrichten", + q_ply_team_need = "Benötigte Spieler im Team", + q_ply_need = "Zum Starten benötigte Anzahl der Spieler", + q_play_limit = "Es gibt ein Limit wie viele Spieler diese Quest spielen können", + q_must_stay_area = "Du musst in diesem Bereich bleiben, oder die Quest wird fehlgeschlagen", + q_time_wait = "Du musst warten bevor du die Quest wiederholen kannst", + q_dotime_reset = "Zeit für die Quest zurücksetzen", + q_dotime_add = "Zeit zur Quest hinzufügen", + q_noreplay = "Du kannst diese Quest nicht wiederholen", + q_dis_replay = "Wiederholung der Quest deaktivieren", + q_needquest = "Du musst zuerst eine andere Quest abschließen", + q_needquest_menu = "Benötigt abgeschlossene Quest", + q_enterror = "Quest Entities sind nicht gespawnt, überprüfe die Einrichtung der Quest", + q_get = "Du kannst eine Quest von diesen NPCs erhalten", + q_noquests = "Es gibt noch keine Möglichkeit Quests zu spielen :(", + q_ent_draw = "Quest Entity Drawdistanz", + q_loop_stop_key = "Schleifenquest Stoptaste", + q_hold_key_stop = "Um die Quest zu stoppen, halte [%s]", -- To stop quest hold [P] + q_enter_veh = "Steig in dein Fahrzeug ein", + q_npc_link = "Verlinke Quest zu einem NPC", + q_icon68 = "Gib eine URL zu einem .PNG Bild mit 68x68px ein", + q_ent_pos_show = "Zeige dem Spieler die Positionen der Entities", + q_area_size = "Größe des Bereichs", + q_area_pos = "Position des Bereichs", + q_s_area_size = "Suche Größe des Bereichs", + q_s_area_pos = "Suche Position des Bereichs", + q_npc_answer_ok = "Positive Antwort des Spielers", + q_npc_answer_no = "Negative Antwort des Spielers", + q_npc_answer_noq = "Antwort des Spielers bei keiner Quest", + q_npc_quest_no = "NPC Sprache bei keiner Quest", + q_money_give = "Geld zum Geben", + + -- Simple NPCs + + npc_editor = "NPC ", + npc_new = "Neuer NPC", + npc_select = "Wähle einen NPC", + npc_e_speech = "Gib NPC Sprache ein", + npc_submit = "Bestätige NPC Erstellung", + npc_update = "Aktualisiere NPC", + npc_remove = "Entferne NPC", + npc_q_enable = "Aktiviere Quest NPCs", + npc_did_open = "Dialog ID zum Öffnen", + npc_q_target = "NPC ist ein objektives Ziel", + npc_hostile = "Feindlicher NPC", + + -- Update 1.1.0 + cam_start = "Startparameter der Kamera", + cam_end = "Kamera-Endparameter", + cam_pos = "Kamera Position", + cam_ang = "Kamera Winkel", + cam_fov = "Kamera-Sichtfeld", + cam_effect = "Kameraverschluss-Effekt", + q_open_target = "Erlaubt anderen Spielern NPCs zu töten", + q_npc_mind = "Mindest Distanz zu NPCs", + not_spawned = "nicht gespawnt", + dis_text = "Angezeigter Text", + cam_speed = "Kamerabewegungsgeschwindigkeit (niedrigere Zahl - langsamere Bewegung)", + fov_speed = "FOV-Wechselgeschwindigkeit (niedrigere Zahl - langsamere Bewegung)", + category_des = "Questkategorie, wird verwendet, um Quests zu sortieren", + sortquests_cat = "Quests nach Kategorie sortieren", + search_q = "Suche nach Quests", + quest_tools = "Quest Tools", + set_anim = "Animation einstellen", + s_quest_blacklist = "Quest-Blacklist einrichten", + s_quest_blacklist_desc = "Wählen Sie Quests aus, die diese Quest blockieren, wenn Sie sie gespielt haben", + hold_use = "Halten sie [%s] gedrückt", + duplicate = "Duplizieren", + unsorted = "Unsortiert", + search = "Suche", + duration = "Dauer", + category = "Kategorie", + blacklist = "Blacklist", + + + -- Update 1.2.0 + + restore_wep = "Stellen Sie Waffen am Questende wieder her", + e_cmd = "Geben sie eine Konsolen Kommand ein", + e_args = "Enter command arguments", + hint_cmd = "Verknüpfungen zum automatischen Ausfüllen: \n$uid - UserID, \n$sid - SteamID, \n$s64 - SteamID 64, \n$n - Spielername", + youaretracked = "Deine Positon ist nun für alle Sichtbar.", + border_rounded = "Abgerundetes Border desgin", + border_square = "Quadrat border design", + access_settings = "Menüzugriff", + compact_obj = "Kompakte Zielliste für Quests", + e_usergroup = "Benutzergruppe eingeben", + ug_isanadmin = "Diese Benutzergruppe hat bereits vollen Zugriff", + find_player_id32 = "Finde Spielerdaten nach SteamID 32", + user_data = "Benutzerdaten-Editor", + access_editors = "Zugriff für Quest-Editoren festlegen", + access_admins = "Vollzugriff einstellen", + add_usergroup = "Benutzergruppe hinzufügen", + edit_objmod = "Zielreihenfolge bearbeiten", + editmod = "Bearbeitungsmodus", + move = "Bewegen", + q_errorloop = "Quest in einer Endlosschleife eingegeben", + q_cooldow_perply = "Öffentlicher Cooldown-Timer", + q_cooldow_publick = "Abklingzeit pro Spieler", + q_stop_anytime = "Erlaube, die Quest manuell abzubrechen", + quest_abandon = "Du hast die Quest abgebrochen", + q_dotime_set = "Quest-Erledigungszeit einstellen", + + + -- Ranks + + enter_path_or_url = "Geben sie einen Pfad oder eine URL ein", + rank_edit = "Rang einstellen", + rank_list = "Rang Liste", + group_list = "Gruppen Liste", + group_addnew = "Eine neue Gruppe hinzufügen", + blank = "Leer", + mrs_show_all = "Ränge allen Spielern anzeigen", + mrs_show_team = "Ränge nur der Gruppe anzeigen", + mrs_use_sn = "Kurze Rangnamen anzeigen", + use_url = "URL verwenden", + enter_srt_name = "Kurznamen eingeben", + srt_name = "Kurzbezeichnung", + mrs_prom_demote = "Die nächsten 2 Optionen betreffen nur niedrigere Ränge. Spieler mit diesem Rang können keine anderen Spieler auf höhere Ränge oder den gleichen Rang befördern.", + mrs_whilelist = "Wenn Sie eine Ranganforderung für einen Job auswählen, kann der Spieler diesen Job nur spielen, wenn sein Rang dem gewählten Rang entspricht oder höher ist.", + can_promote = "Kann den Spielerrang erhöhen", + can_demote = "Kann den Spielerrang zurückstufen", + edit_player_model = "Benutzerdefiniertes Spielermodell bearbeiten", + enable_player_model = "Benutzerdefiniertes Spielermodell aktivieren", + disable_player_model = "Benutzerdefiniertes Spielermodell deaktivieren", + edit_custom_stats = "Benutzerdefinierte Spielerstatistiken bearbeiten", + autoprom = "Automatische Beförderung zum nächsten Rang", + in_min = "in Minuten", + mrs_promoted = "Sie sind befördert worden", + mrs_demoted = "Sie sind degradiert worden", + mrs_job_smallrank = "Du musst %s oder höher sein, um als %s zu spielen", -- You must be Sergeant II or higher to play as Watch Commander + show_group = "Ranggruppenname anzeigen", + hide_rank = "Nur Rangsymbol anzeigen", + mrs_hud_follow = "Drehen der Benutzeroberfläche um den Spieler je nach Blickwinkel", + set_overhead = "Spieler-Info UI", + offline_users = "Offline-Benutzer", + mrs_noranks = "Ihr derzeitiger Arbeitsplatz hat keine Dienstgrade", + mrs_nopower = "Ihr aktueller Rang hat keine zusätzlichen Berechtigungen", + promotion = "Beförderung", + on_duty = "Im Dienst", + other_players = "Andere Spieler", + mrs_change_jobname = "Jobname in den Rangnamen ändern", + mrs_set_prefix = "Fügen Sie den Namen des Dienstgrads als Präfix zum Jobnamen hinzu.", + copy_all_data = "Alle Daten kopieren", + copy_only_stats = "Nur Statistiken und Spielermodell kopieren", +} + + +-- Other phrases +local lng = "de" + + +MSD.Language[lng]["Move to point"] = "Zum Punkt bewegen" +MSD.Language[lng]["Leave area"] = "Verlasse Bereich" +MSD.Language[lng]["Kill NPC"] = "Töte NPC" +MSD.Language[lng]["Collect quest ents"] = "Sammle Quest Gegenstände" +MSD.Language[lng]["Talk to NPC"] = "Spreche mit dem NPC" + + +MSD.Language[lng]["There is no quests avalible"] = "Es sind keine Quests verfügbar" + + +MSD.Language[lng]["Give weapon"] = "Gebe Waffe" +MSD.Language[lng]["Give ammo"] = "Gebe Munition" +MSD.Language[lng]["Strip Weapon"] = "Nehme Waffe weg" +MSD.Language[lng]["Spawn quest entity"] = "Spawne Quest Gegenstand" +MSD.Language[lng]["Spawn entity"] = "Spawne Gegenstand" +MSD.Language[lng]["Spawn npc"] = "Spawne NPC" +MSD.Language[lng]["Manage do time"] = "Verwalte Do-Zeit" +MSD.Language[lng]["Spawn vehicle"] = "Spawne Fahrzeug" +MSD.Language[lng]["Remove vehicle"] = "Entferne Fahrzeug" +MSD.Language[lng]["Remove all entites"] = "Entferne alle Gegenstände" +MSD.Language[lng]["Set HP"] = "Setze HP" +MSD.Language[lng]["Set Armor"] = "Setze Rüstung" + + +MSD.Language[lng]["DarkRP Money"] = "DarkRP Geld" + + +MSD.Language[lng]["Quest NPCs are disabled"] = "Quest NPCs sind deaktiviert" +MSD.Language[lng]["You can enable them in settings"] = "Du kannst sie in den Einstellungen aktivieren" +MSD.Language[lng]["Wait time"] = "Wartezeit" \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/language/en.lua b/addons/msd_ui/lua/msd/language/en.lua new file mode 100644 index 0000000..88781a4 --- /dev/null +++ b/addons/msd_ui/lua/msd/language/en.lua @@ -0,0 +1,390 @@ +MSD.Language["en"] = { + + lang_name = "English", + + ok = "OK", + map = "Map", + off = "Off", + on = "On", + time_add = "Time to add", + type = "Type", + delay = "Delay", + cancel = "Cancel", + enable = "Enable", + model = "Model", + name = "Name", + settings = "Settings", + editor = "Editor", + red = "Red", + green = "Green", + blue = "Blue", + admin_menu = "Administration menu", + ui_settings = "UI Settings", + active = "Active", + inactive = "Inactive", + disabled = "Disabled", + warning = "Warning!", + remove = "Remove", + theme = "Theme", + dark_theme = "Dark theme", + payment = "Payment", + load_autosave = "Load last autosave?", + load_save = "Load save", + create_new = "Create new", + enable_option = "Enable option", + main_opt = "Main options", + copy_data = "Copy data", + save_chng = "Save changes", + enter_name = "Enter the name", + enter_id = "Enter the ID", + confirm_action = "Please confirm your actions", + check_fpr_errors = "Check for errors", + enter_description = "Enter description", + cooldown_ok = "Cooldown on success", + cooldown_fail = "Cooldown on fail", + s_team_whitelist = "Setup team whitelist", + whitelist_blacklist = "The whitelist is a blacklist", + custom_val = "Set custom value", + set_hp_full = "Set full HP", + dist_to_close = "Distance to closest", + + e_text = "Enter text", + e_number = "Enter number", + e_class = "Enter class", + e_value = "Enter value", + e_blank_dis = "Leave blank to disable", + e_blank_default = "Leave blank to use default", + e_url = "Enter URL", + e_model = "Enter model path", + e_material = "Enter material path", + e_wep_class = "Enter weapon class", + e_ent_class = "Enter entity class", + e_veh_class = "Enter vehicle class", + e_npc_class = "Enter NPC class", + + select_ammo = "Selected ammo", + amount_ammo = "Ammo amount", + disable_phys = "Disable physics", + none = "None", + custom_icon = "Set custom icon", + weapon_name = "Weapon name", + moveup = "Move up", + movedown = "Move down", + movepoint = "Move point", + swap = "Swap", + swapmod = "Swap mod enabled. Click to disable", + copy_from_ent = "Copy from looking entity", + set_pos_self = "Set to your position", + set_pos_aim = "Set to looking poit", + spawn_point = "Spawn point", + spawn_ang = "Spawn angle", + mark_area = "Mark area", + time_wait = "Time to wait", + map_marker = "Select map marker", + in_sec = "in seconds", + def_units = "Default %s units", -- "Default 350 units" leave %s as is + def_seconds = "Default %s seconds", -- "Default 10 seconds" leave %s as is + ent_show_pointer = "Show pointer above the entity", + ent_arcade_style = "Arcade-style entity appearance", + ent_stnd_style = "Standart entity appearance", + custom_color = "Enable custom color", + mat_default = "Leave blank for default material", + + set_ui = "UI settings", + set_hud = "HUD settings", + set_hud_pos = "Quest HUD position", + set_hud_themes = "HUD Themes", + set_server = "Server settings", + set_ui_blur = "Blur background", + set_ui_mono = "Monochrome background", + set_ui_vignette = "Vignette effect for background", + set_ui_brightness = "Background brightness", + set_ui_color = "Select the main color", + set_ui_align_left = "Horizontal alignment to the left", + set_ui_align_right = "Horizontal alignment to the right", + set_ui_align_top = "Vertical alignment to the top", + set_ui_align_bottom = "Vertical alignment to the bottom", + set_ui_offset_h = "Horizontal Offset", + set_ui_offset_v = "Vertical Offset", + + upl_changes = "Upload changes to server", + res_changes = "Restore changes", + + -- Player + + dead = "You are dead", + time_ex = "Time expired", + vehicle_bum = "Your vehicle is destroyed", + left_area = "You left the area", + m_blew = "You blew up the mission", + m_failed = "Mission failed", + m_success = "Mission success", + m_loop = "Mission update", + + -- Errors + + inv_quest = "Invalid quest", + team_bl = "Your team is blacklisted", + no_players = "Server needs more players to be online before you can do this", + no_players_team = "Server needs more players for specific team(s) to be online before you can do this", + need_admin = "Only admins can do this action", + + -- Quests + + active_quest = "You have an active quest", + inactive_quest = "You can't play this quest", + quest_editor = "Quest Editor", + quest_list = "Quest List", + quests = "Quests", + leave_pnt = "Leave point", + + q_editobj = "Edit objectives", + q_incvobj = "Invalid objective", + q_setobj = "Objective settings", + q_newobj = "Add new objective", + q_editrwd = "Edit rewards", + q_rwdeditor = "Reward Editor", + q_rwdlist = "Reward List", + q_rwdsets = "Reward Settings", + q_findmap = "Find quest from other maps", + q_obj_des = "Objecive description", + q_dist_point = "Distance to point", + q_dist_from_point = "Distance from point", + q_ignore_veh = "Ignore quest vehicle", + q_timer_show = "Show the timer to player", + q_area_stay = "Player must stay in area", + q_start = "Start Quest", + q_new = "New quest", + q_submit = "Submit quest", + q_addnew = "Add new quest", + q_remove = "Remove quest", + q_id_unique = "ID must be unique for each quest", + q_complete_msg = "Quest complete message", + q_dotime = "Quest do time", + q_dotime_ok = "Succeed quest on time end", + q_dotime_fail = "Fail quest on time end", + q_death_fail = "Fail quest on player death", + q_loop = "Loop quest tasks", + q_loop_reward = "Reward player on each loop", + q_enable = "Enable quest", + q_events = "Events", + q_eventadd = "Add Event", + q_eventedit = "event edit", + q_eventremove = "Remove event", + q_in_progress = "Quest in progress", + q_time_left = "Time left", + q_ply_limit = "Player limit for the quest", + q_ply_team_limit = "Setup team limits", + q_ply_team_need = "Needed team players", + q_ply_need = "Player amount needed to start", + q_play_limit = "There is a limit on how much players can play this quest", + q_must_stay_area = "You must stay inside this area, or quest will be failed", + q_time_wait = "You need to wait before replaying this quest", + q_dotime_reset = "Reset quest do time", + q_dotime_add = "Add quest do time", + q_noreplay = "You can't replay this quest", + q_dis_replay = "Disable quest replay", + q_needquest = "You need to finish another quest first", + q_needquest_menu = "Require completed quest", + q_enterror = "Quest entities didn't spawn, check quest setup", + q_get = "You can get a quest from these NPCs", + q_noquests = "There is no way to play quests yet :(", + q_ent_draw = "Quest entity draw distance", + q_loop_stop_key = "Looped quest stop key", + q_hold_key_stop = "To stop quest hold [%s]", -- To stop quest hold [P] + q_enter_veh = "Enter your vehicle", + q_npc_link = "Link quest to an NPC", + q_icon68 = "Enter url to .PNG icon 68x68 px", + q_ent_pos_show = "Show entities location to the player", + q_area_size = "Area size", + q_area_pos = "Area postition", + q_s_area_size = "Search area size", + q_s_area_pos = "Search area postition", + q_npc_answer_ok = "Player's positive answer", + q_npc_answer_no = "Player's negative answer", + q_npc_answer_noq = "Player's answer if no quests", + q_npc_quest_no = "NPC speech if no quests", + q_money_give = "Money to give", + + -- Simple NPCs + + npc_editor = "NPC Editor", + npc_new = "New NPC", + npc_select = "Select an NPC", + npc_e_speech = "Enter NPC speech", + npc_submit = "Confirm NPC creation", + npc_update = "Update NPC", + npc_remove = "Remove NPC", + npc_q_enable = "Enable quest NPCs", + npc_did_open = "Dialog ID to open", + npc_q_target = "NPC is an objective target", + npc_hostile = "Hostile NPC", + + + -- Update 1.1.0 + + cam_start = "Camera start parameters", + cam_end = "Camera end parameters", + cam_pos = "Camera position", + cam_ang = "Camera angle", + cam_fov = "Camera FOV", + cam_effect = "Camera shutter effect", + q_open_target = "Allow other players to kill NPCs", + q_npc_mind = "Min. distance to NPCs", + not_spawned = "not spawned", + dis_text = "Displayed text", + cam_speed = "Camera movement speed (lower number - slower movement)", + fov_speed = "FOV change speed (lower number - slower movement)", + category_des = "Quest category, used to sort quests", + sortquests_cat = "Sort quests by category", + search_q = "Search quests", + quest_tools = "Quest tools", + set_anim = "Set Animation", + s_quest_blacklist = "Setup quest blacklist", + s_quest_blacklist_desc = "Select quests that will block this quest if you played them", + hold_use = "Hold [%s] key", + duplicate = "Duplicate", + unsorted = "Unsorted", + search = "Search", + duration = "Duration", + category = "Category", + blacklist = "Blacklist", + + + -- Update 1.2.0 + + restore_wep = "Restore weapons on quest end", + e_cmd = "Enter console command", + e_args = "Enter command arguments", + hint_cmd = "Autofill shortcuts: \n$uid - UserID, \n$sid - SteamID, \n$s64 - SteamID 64, \n$n - Player name", + youaretracked = "Your position is now compromised to other players!", + border_rounded = "Rounded border design", + border_square = "Square border design", + access_settings = "Menu access", + compact_obj = "Compact objective list for quests", + e_usergroup = "Enter user group", + ug_isanadmin = "This user group already has full access", + find_player_id32 = "Find player data by SteamID 32", + user_data = "User data editor", + access_editors = "Set quest editors access", + access_admins = "Set full access", + add_usergroup = "Add user group", + edit_objmod = "Edit objective order", + editmod = "Edit mode", + move = "Move", + q_errorloop = "Quest entered in an endless loop", + q_cooldow_perply = "Public cooldown timer", + q_cooldow_publick = "Per-Player cooldown timer", + q_stop_anytime = "Allow to abandon quest manually", + quest_abandon = "You abandoned the quest", + q_dotime_set = "Set quest do time", + + -- Ranks + + enter_path_or_url = "Enter path or url", + rank_edit = "Ranks setup", + rank_list = "Ranks list", + group_list = "Group list", + group_addnew = "Add new group", + blank = "Blank", + mrs_show_all = "Show ranks to all players", + mrs_show_team = "Show ranks only to the group", + mrs_use_sn = "Display short rank names", + use_url = "Use URL", + enter_srt_name = "Enter short name", + srt_name = "Short name", + mrs_prom_demote = "The next 2 options affect only lower ranks. Players with this rank will not be able to promote other players to higher ranks or the same rank.", + mrs_whilelist = "If you select rank requirement for a job, the player will be able to play this job only if his rank matches the chosen one or higher.", + can_promote = "Can promote player rank", + can_demote = "Can demote player rank", + edit_player_model = "Edit custom player model", + enable_player_model = "Enable custom player model", + disable_player_model = "Disable custom player model", + edit_custom_stats = "Edit custom player stats", + autoprom = "Auto promote to next rank", + in_min = "in minutes", + mrs_promoted = "You have been promoted", + mrs_demoted = "You have been demoted", + mrs_job_smallrank = "You must be %s or higher to play as %s", -- You must be Sergeant II or higher to play as Watch Commander + show_group = "Display rank group name", + hide_rank = "Display only rank icon", + mrs_hud_follow = "Rotate UI around player depending on view angle", + set_overhead = "Player info UI", + offline_users = "Offline users", + mrs_noranks = "Your current job has no ranks", + mrs_nopower = "Your current rank has no extra permissions", + promotion = "Promotion", + on_duty = "On Duty", + other_players = "Other players", + mrs_change_jobname = "Change job name to the rank name", + mrs_set_prefix = "Add the rank name as a prefix to the job name", + copy_all_data = "Copy all data", + copy_only_stats = "Copy only stats and player model", + mrs_change_plyname = "Change player name to the rank name", + mrs_set_prefix_ply = "Add the rank name as a prefix to the player name", + + promote_limit = "Promotion limitationn", + demote_limit = "Demotion limitation", + salary_value = "Salary value", + salary_set = "Set given value as a salary", + salary_add = "Add given value on top of salary", + salary_multiply = "Multiply salary by given value", + force_team = "Force change team to", + mrs_hud_3d2d = "Use 3D2D hud for players", + mrs_chat_command = "Chat command to open the Rank menu", + mrs_promote_command = "Chat command to promote a player", + mrs_demote_command = "Chat command to demote a player", + + mqs_fix_cam = "Fix Cinematic Camera event (toggle only if needed)", + add_new_spawn = "Add new spawn point", + remove_all_spawn = "Remove all spawn point", + nolongertracked = "You are no longer tracked by other players", + target = "Target", + kill_player = "Kill Player", + kill_amount = "Amount of targets to kill", + into_quest = "Introduction quest", + into_quest_auto = "Force player to play introduction quest", + into_quest_start = "To strat quest hold [%s]", + set_ui_align_center = "Horizontal alignment to the center", + icon_size = "Icon size", + icon_right = "Icon alignment to the right", + font_size = "Font size", + rank_hide = "Do not display this rank on the HUD", + action_select = "Select an action", + action_set_rank = "Set rank to selected, only if current rank is lower", + action_set_rank_force = "Set rank to selected, anyway", + action_promote_rank = "Promote only by one rank, selected rank is maximum", + action_demote_rank = "Demote by one rank, selected rank is minimum", + rank = "Rank", + format = "Format", + file_list = "File list", + file_exist = "This save file is already exist", + + show_team = "Display team name", + use_team_colors = "Use team colors" +} + +-- Other phrases +local lng = "en" + +MSD.Language[lng]["Move to point"] = "Move to point" +MSD.Language[lng]["Leave area"] = "Leave area" +MSD.Language[lng]["Kill NPC"] = "Kill NPC" +MSD.Language[lng]["Collect quest ents"] = "Collect quest ents" +MSD.Language[lng]["Talk to NPC"] = "Talk to NPC" +MSD.Language[lng]["There is no quests available"] = "There is no quests available" +MSD.Language[lng]["Give weapon"] = "Give weapon" +MSD.Language[lng]["Give ammo"] = "Give ammo" +MSD.Language[lng]["Strip Weapon"] = "Strip Weapon" +MSD.Language[lng]["Spawn quest entity"] = "Spawn quest entity" +MSD.Language[lng]["Spawn entity"] = "Spawn entity" +MSD.Language[lng]["Spawn npc"] = "Spawn npc" +MSD.Language[lng]["Manage do time"] = "Manage do time" +MSD.Language[lng]["Spawn vehicle"] = "Spawn vehicle" +MSD.Language[lng]["Remove vehicle"] = "Remove vehicle" +MSD.Language[lng]["Remove all entites"] = "Remove all entites" +MSD.Language[lng]["Set HP"] = "Set HP" +MSD.Language[lng]["Set Armor"] = "Set Armor" +MSD.Language[lng]["DarkRP Money"] = "DarkRP Money" +MSD.Language[lng]["Quest NPCs are disabled"] = "Quest NPCs are disabled" +MSD.Language[lng]["You can enable them in settings"] = "You can enable them in settings" \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/language/es.lua b/addons/msd_ui/lua/msd/language/es.lua new file mode 100644 index 0000000..33fe5d5 --- /dev/null +++ b/addons/msd_ui/lua/msd/language/es.lua @@ -0,0 +1,255 @@ +MSD.Language["es"] = { + + -- UI + + lang_name = "Spanish", + + ok = "OK", + map = "Mapa", + off = "Apagado", + on = "Encendido", + time_add = "Tiempo para agregar", + type = "Tipo", + delay = "Retraso", + cancel = "Cancelar", + enable = "Permitir", + model = "Modelo", + name = "Nombre", + settings = "Ajustes", + editor = "Editor", + red = "Rojo", + green = "Verde", + blue = "Azul", + admin_menu = "Menú de admin", + ui_settings = "Ajustes de interfaz", + active = "Activo", + inactive = "Inactivo", + disabled = "Deshabilitado", + warning = "Peligro", + remove = "Eliminar", + theme = "Tema", + dark_theme = "Tema oscuro", + payment = "Pago", + load_autosave = "Cargar el ultimo autoguardado?", + load_save = "Cargar guardado", + create_new = "Crear nuevo", + enable_option = "Permitir opción", + main_opt = "Opciones principales", + copy_data = "Copiar datos", + save_chng = "Guardar cambios", + enter_name = "Ingresar el nombre", + enter_id = "Ingresar el ID", + confirm_action = "Por favor confirma tus acciones", + check_fpr_errors = "Chequear errores", + enter_description = "Ingresar descripción", + cooldown_ok = "Enfriamiento en el éxito", + cooldown_fail = "Enfriamiento en caso de falla", + s_team_whitelist = "Configurar la lista blanca del equipo", + whitelist_blacklist = "La lista blanca es una lista negra", + custom_val = "Colocar valor personalizado", + set_hp_full = "Colocar todo el HP", + dist_to_close = "Distancia al más cercano", + + e_text = "Ingresar texto", + e_number = "Ingresar número", + e_class = "Ingresar clase", + e_value = "Ingresar valor", + e_blank_dis = "Dejar en blanco para deshabilitar", + e_blank_default = "Dejar en blanco para usar por defecto", + e_url = "Ingresar URL", + e_model = "Ingresar ruta del modelo", + e_material = "Ingresar ruta del material", + e_wep_class = "Ingresar ruta de la arma", + e_ent_class = "Ingresar ruta de la entidad", + e_veh_class = "Ingresar ruta del vehículo", + e_npc_class = "Ingresar clase del NPC", + + select_ammo = "Munición seleccionada", + amount_ammo = " Cantidad munición", + disable_phys = "Deshabilitar la física", + none = "Ninguno", + custom_icon = "Seleccionar icono personalizado", + weapon_name = "Nombre del arma", + moveup = "Mover hacia arriba", + movedown = "Mover hacia abajo", + movepoint = "Mover punto", + swap = "Intercambiar", + swapmod = "Intercambiar mod habilitado. Click para deshabilitar", + copy_from_ent = "Copiar de la entidad que mira", + set_pos_self = "Establecer en su posición", + set_pos_aim = "Establecer en el punto de mira", + spawn_point = "Spawnear punto", + spawn_ang = "Spawnear ángulo", + mark_area = "Marcar área", + time_wait = "Tiempo de espera", + map_marker = "Seleccionar punto del mapa", + in_sec = "En segundos", + def_units = "Default %s unidades", -- "350 unidades por defecto" leave %s as is + def_seconds = "Default %s segundos", -- "10 segundos por defecto" leave %s as is + ent_show_pointer = "Mostrar puntero sobre la entidad", + ent_arcade_style = "Apariencia de entidad de estilo arcade", + ent_stnd_style = "Apariencia de la entidad estándar", + custom_color = "Habilitar color personalizado", + mat_default = "Dejar en blanco para material por defecto", + + set_ui = "Ajustes de la interfaz de usuario", + set_hud = "Ajustes del HUD", + set_hud_pos = "Posición HUD de las misiones", + set_hud_themes = "Temas del HUD", + set_server = "Ajustes de servidor", + set_ui_blur = "Desenfoque del fondo", + set_ui_mono = "Fondo monocromático", + set_ui_vignette = "Efecto de viñeta para el fondo", + set_ui_brightness = "Brillo de fondo", + set_ui_color = "Seleccionar el color principal", + set_ui_align_left = "Alineación vertical hacia la izquierda", + set_ui_align_right = "Alineación vertical hacia la derecha", + set_ui_align_top = "Alineación vertical hacia arriba", + set_ui_align_bottom = "Alineación vertical hasta la parte inferior", + set_ui_offset_h = "Compensación horizontal", + set_ui_offset_v = "Compensación vertical", + + upl_changes = "Subir cambios al servidor", + res_changes = "Restablecer cambios", + + -- Jugador + + dead = "Estas muerto", + time_ex = "Tiempo expirado", + vehicle_bum = "Tu Vehículo esta destruido", + left_area = "Dejaste la zona", + m_blew = "You blew up the mission", + m_failed = "Misión fallida", + m_success = "Misión exitosa", + m_loop = "Actualización de la misión", + + -- Errores + + inv_quest = "Misión Invalida", + team_bl = "Tu equipo está en lista negra", + no_players = "El servidor necesita más jugadores conectados antes que puedas hacer esto", + no_players_team = "El servidor necesita más jugadores conectados para equipo(s) especifico(s)antes que puedas hacer esto", + need_admin = "Solo los administradores pueden realizar esta accion", + + -- Misiones + + active_quest = "Tienes una misión activa", + inactive_quest = "No puedes jugar esta misión", + quest_editor = "Editor de misiones", + quest_list = "Lista de misiones", + quests = "Misiones", + leave_pnt = "Dejar punto", + + q_editobj = "Editar objetivos", + q_incvobj = "Objetivo invalido", + q_setobj = "Ajustes de objetivo", + q_newobj = "Añadir nuevo objetivo", + q_editrwd = "Editar recompensas", + q_rwdeditor = "Editor de recompensas", + q_rwdlist = "Lista de recompensas", + q_rwdsets = "Ajustes de recompensas", + q_findmap = "Encontrar misiones de otros mapas", + q_obj_des = "Descripción de objetivo", + q_dist_point = "Distancia al punto", + q_dist_from_point = "Distancia desde el punto", + q_ignore_veh = "Ignorar vehículo de misión", + q_timer_show = "Mostrar el temporizador al jugador", + q_area_stay = "El jugador debe quedarse en el area", + q_start = "Empezar Misión", + q_new = "Nueva misión", + q_submit = "Enviar misión", + q_addnew = "Añadir nueva misión", + q_remove = "Eliminar misión", + q_id_unique = "La ID debe ser única para cada misión", + q_complete_msg = "Mensaje de misión completada", + q_dotime = "Tiempo para hacer la misión", + q_dotime_ok = "Misión exitosa al finalizar el tiempo", + q_dotime_fail = "Falla la misión al finalizar el tiempo", + q_death_fail = "Falló la misión al morir el jugador", + q_loop = "Tareas de misiones en bucle", + q_loop_reward = "Recompensa al jugador en cada bucle", + q_enable = "Habilitar misión", + q_events = "Eventos", + q_eventadd = "Añadir Evento", + q_eventedit = "Editor de evento", + q_eventremove = "Eliminar evento", + q_in_progress = "Misión en progreso", + q_time_left = "Tiempo restante", + q_ply_limit = "Limite de jugadores para la misión", + q_ply_team_limit = "Configurar equipos limitados", + q_ply_team_need = "Jugadores de equipo necesarios", + q_ply_need = "Cantidad de jugadores necesarios para comenzar", + q_play_limit = "Hay un límite en cuantos jugadores pueden jugar esta misión", + q_must_stay_area = "Debes permanecer dentro de esta área, o la misión fallara", + q_time_wait = "Debes esperar antes de repetir esta misión", + q_dotime_reset = "Restablecer el tiempo de realización de la misión", + q_dotime_add = "Añadir tiempo para hacer la misión", + q_noreplay = "No puedes re jugar esta misión", + q_dis_replay = "Des habilitar repetición de la misión", + q_needquest = "Debes completar otra misión primero", + q_needquest_menu = "Requiere misión completada", + q_enterror = "Entidades de misión no spawnean, chequear configuración de misión", + q_get = "Puedes obtener una misión de estos NPCS", + q_noquests = "Todavía no hay forma de jugar las misiones :(", + q_ent_draw = "Distancia del dibujo de la entidad", + q_loop_stop_key = "Tecla para detener misión en bucle", + q_hold_key_stop = "Para detener la misión, mantenga [%s]", -- Para detener la mision mantenga apretado [P] + q_enter_veh = "Entrar a tu vehículo", + q_npc_link = "Linquear misión a un NPC", + q_icon68 = "Ingresar url a un icono .PNG de 68x68 px", + q_ent_pos_show = "Mostrar locación de entidades al jugador", + q_area_size = "Tamaño de área", + q_area_pos = "Posición de área", + q_s_area_size = "Buscar tamaño de área", + q_s_area_pos = "Buscar posición de área", + q_npc_answer_ok = "Respuesta positiva del jugador", + q_npc_answer_no = "Respuesta negativa del jugador", + q_npc_answer_noq = "Respuesta del jugador si no hay misiones", + q_npc_quest_no = "Discurso del NPC si no hay misiones", + q_money_give = "Dinero para dar", + + -- Simple NPCs + + npc_editor = "Editor de NPC", + npc_new = "Nuevo NPC", + npc_select = "Seleccionar un NPC", + npc_e_speech = " Ingresar el discurso del NPC", + npc_submit = "Confirmar creación del NPC", + npc_update = "Actualizar NPC", + npc_remove = "Eliminar NPC", + npc_q_enable = "Habilitar misiones de NPCs", + npc_did_open = "ID de dialogo para abrir", + npc_q_target = "NPC is an objective target", + npc_hostile = "NPC Hostil", + +} + +-- Otras frases +local es = "es" + +MSD.Language[es]["Move to point"] = "Mover al punto" +MSD.Language[es]["Leave area"] = "Dejar área" +MSD.Language[es]["Kill NPC"] = "Matar NPC" +MSD.Language[es]["Collect quest ents"] = "Recoger misiones" +MSD.Language[es]["Talk to NPC"] = "Hablar al NPC" + +MSD.Language[es]["No hay misiones disponibles"] = "No hay misiones disponibles" + +MSD.Language[es]["Give weapon"] = "Dar arma" +MSD.Language[es]["Give ammo"] = "Dar munición" +MSD.Language[es]["Strip Weapon"] = "Extraer arma" +MSD.Language[es]["Spawn quest entity"] = "Spawnear entidad de misión" +MSD.Language[es]["Spawn entity"] = "Spawnear entidad" +MSD.Language[es]["Spawn npc"] = "Spawnear npc" +MSD.Language[es]["Manage do time"] = "Administrar el tiempo" +MSD.Language[es]["Spawn vehicle"] = "Spawnear vehículo" +MSD.Language[es]["Remove vehicle"] = "Elminar vehículo" +MSD.Language[es]["Remove all entites"] = "Remover todas las entidades" +MSD.Language[es]["Set HP"] = "Configurar HP" +MSD.Language[es]["Set Armor"] = "Configurar armadura" + +MSD.Language[es]["DarkRP Money"] = " Dinero DarkRP" + +MSD.Language[es]["Quest NPCs are disabled"] = "Los NPCs de las misiones están deshabilitados" +MSD.Language[es]["You can enable them in settings"] = "Puedes habilitarlos en las configuraciones" +MSD.Language[es]["Wait time"] = "Tiempo de espera" diff --git a/addons/msd_ui/lua/msd/language/fr.lua b/addons/msd_ui/lua/msd/language/fr.lua new file mode 100644 index 0000000..bce67aa --- /dev/null +++ b/addons/msd_ui/lua/msd/language/fr.lua @@ -0,0 +1,353 @@ +MSD.Language["fr"] = { + + -- UI + + lang_name = "French", + + ok = "OK", + map = "Map", + off = "Off", + on = "On", + time_add = "Il est temps d'ajouter", + type = "Taper", + delay = "Retard", + cancel = "Annuler", + enable = "Activé", + model = "Model", + name = "Nom", + settings = "Paramètre", + editor = "Éditeur", + red = "Rouge", + green = "Vert", + blue = "Bleu", + admin_menu = "Menu d'administration", + ui_settings = "Paramètres de l'interface", + active = "Actif", + inactive = "Inactif", + disabled = "Désactivée", + warning = "Avertissement!", + remove = "Supprimer", + theme = "Thème", + dark_theme = "Thème sombre", + payment = "Paiement", + load_autosave = "Charger la dernière sauvegarde automatique?", + load_save = "Chargement de la sauvegarde", + create_new = "Créer un nouveau", + enable_option = "Activer l'option", + main_opt = "Options principales", + copy_data = "Copier des données", + save_chng = "Sauvegarder les modifications", + enter_name = "Entrez le nom", + enter_id = "Entrez l'ID", + confirm_action = "Veuillez confirmer vos actions", + check_fpr_errors = "Rechercher les erreurs", + enter_description = "Entrez la description", + cooldown_ok = "Temps de recharge en cas de succès", + cooldown_fail = "Temps de recharge en cas d'échec", + s_team_whitelist = "Configuration de la TEAM Whitelist", + whitelist_blacklist = "La liste blanche est une liste noire", + custom_val = "Définir une valeur personnalisée", + set_hp_full = "Définir les HP complets", + dist_to_close = "Distance au plus proche", + + e_text = "Entrez du texte", + e_number = "Entrez un nombre", + e_class = "Entrer une classe", + e_value = "Entrez une valeur", + e_blank_dis = "Laisser vide pour désactiver", + e_blank_default = "Laissez le noir pour utiliser la valeur par défaut", + e_url = "Entrer une URL", + e_model = "Entrez le chemin du modèle", + e_material = "Entrez le chemin du matériau", + e_wep_class = "Entrez la classe d'une arme", + e_ent_class = "Entrez la classe de l'entité", + e_veh_class = "Entrez la classe du véhicule", + e_npc_class = "Entrez la classe du NPC", + + select_ammo = "Munitions sélectionnées", + amount_ammo = "Montant des munitions", + disable_phys = "Désactiver la physique", + none = "Rien", + custom_icon = "Définir une icône personnalisée", + weapon_name = "Nom de l'arme", + moveup = "déplacer vers le haut", + movedown = "déplacer vers le bas", + movepoint = "Déplacer le point", + swap = "Échanger", + swapmod = "Swap mod activé. Cliquez pour désactiver", + copy_from_ent = "Copier de l'entité recherchée", + set_pos_self = "Réglez votre position", + set_pos_aim = "Définir le point de recherche", + spawn_point = "Point d'apparition", + spawn_ang = "Angle d'apparition", + mark_area = "Marquer la zone", + time_wait = "Il est temps d'attendre", + map_marker = "Sélectionnez un marqueur de map", + in_sec = "en secondes", + def_units = "Défaut %s unités", -- "350 unités par défaut" laisser %s tel quel + def_seconds = "Défaut %s secondes", -- "Par défaut, 10 secondes" laisser %s comme si + ent_show_pointer = "Montrer poiter au-dessus de l'entité", + ent_arcade_style = "Apparence d'entité de style arcade", + ent_stnd_style = "Apparence d'entité standard", + custom_color = "Activer la couleur personnalisée", + mat_default = "Laisser vide pour le matériau par défaut", + + set_ui = "Paramètres de l'interface utilisateur", + set_hud = "Paramètres du HUD", + set_hud_pos = "Position du HUD de quête", + set_hud_themes = "Thèmes HUD", + set_server = "Paramètres du serveur", + set_ui_blur = "Arrière-plan flou", + set_ui_mono = "Fond monochrome", + set_ui_vignette = "Effet de vignette pour l'arrière-plan", + set_ui_brightness = "Luminosité d'arrière-plan", + set_ui_color = "Sélectionnez la couleur principale", + set_ui_align_left = "Alignement horizontal vers la gauche", + set_ui_align_right = "Alignement horizontal vers la droite", + set_ui_align_top = "Alignement vertical vers le haut", + set_ui_align_bottom = "Alignement vertical vers le bas", + set_ui_offset_h = "Décalage horizontal", + set_ui_offset_v = "Décalage vertical", + + upl_changes = "Télécharger les modifications sur le serveur", + res_changes = "Restaurer les modifications", + + -- Joueur + + dead = "Restaurer les modifications", + time_ex = "Le temps est écoulé", + vehicle_bum = "Votre véhicule est détruit", + left_area = "Vous avez quitté la zone", + m_blew = "Vous avez fait sauter la mission", + m_failed = "Mission échouée", + m_success = "Succès de la mission", + m_loop = "Mise à jour de la mission", + + -- Erreurs + + inv_quest = "Quête invalide", + team_bl = "Votre équipe est sur liste noire", + no_players = "Le serveur a besoin de plus de joueurs pour être en ligne avant de pouvoir le faire", + no_players_team = "Le serveur a besoin de plus de joueurs pour que des TEAM spécifiques soient en ligne avant que vous puissiez le faire", + need_admin = "Seuls les administrateurs peuvent effectuer cette action", + + -- Quête + + active_quest = "Vous avez une quête active", + inactive_quest = "Vous ne pouvez pas jouer à cette quête", + quest_editor = "Éditeur de quête", + quest_list = "Liste des quêtes", + quests = "Quêtes", + leave_pnt = "Quitter le point", + + q_editobj = "Modifier les objectifs", + q_incvobj = "Objectif invalide", + q_setobj = "Paramètres d'objectifs", + q_newobj = "Ajouter un nouvel objectif", + q_editrwd = "Modifier les récompenses", + q_rwdeditor = "Éditeur de récompenses", + q_rwdlist = "Liste de récompenses", + q_rwdsets = "Paramètres de récompense", + q_findmap = "Trouver une quête sur d'autres cartes", + q_obj_des = "Description de l'objectif", + q_dist_point = "Distance au point", + q_dist_from_point = "Distance du point", + q_ignore_veh = "Ignorer le véhicule de quête", + q_timer_show = "Montrez le chronomètre au joueur", + q_area_stay = "Le joueur doit rester dans la zone", + q_start = "Démarrer la quête", + q_new = "Nouvelle quête", + q_submit = "Soumettre la quête", + q_addnew = "Ajouter une nouvelle quête", + q_remove = "Supprimer la quête", + q_id_unique = "L'ID doit être unique pour chaque quête", + q_complete_msg = "Message complet de la quête", + q_dotime = "Message complet de la quête", + q_dotime_ok = "Réussir la quête à la fin du temps", + q_dotime_fail = "Échec de la quête à la fin du temps", + q_death_fail = "Échec de la quête à la mort du joueur", + q_loop = "Tâches de quête en boucle", + q_loop_reward = "Récompensez le joueur sur chaque boucle", + q_enable = "Activer la quête", + q_events = "Événements", + q_eventadd = "Ajouter un évènement", + q_eventedit = "Modification d'événement", + q_eventremove = "Supprimer l'événement", + q_in_progress = "Quête en cours", + q_time_left = "Temps restant", + q_ply_limit = "Limite de joueurs pour la quête", + q_ply_team_limit = "Limites de l'équipe de configuration", + q_ply_team_need = "Joueurs d'équipe nécessaires", + q_ply_need = "Quantité de joueur nécessaire pour commencer", + q_play_limit = "Il y a une limite au nombre de joueurs pouvant jouer à cette quête", + q_must_stay_area = "Vous devez rester dans cette zone, sinon la quête échouera", + q_time_wait = "Vous devez attendre avant de rejouer cette quête", + q_dotime_reset = "Réinitialiser le temps de la quête", + q_dotime_add = "Ajouter une quête à faire", + q_noreplay = "Vous ne pouvez pas rejouer cette quête", + q_dis_replay = "Désactiver la relecture de quête", + q_needquest = "Vous devez d'abord terminer une autre quête", + q_needquest_menu = "Requiert une quête terminée", + q_enterror = "Les entités de quête ne sont pas apparues, vérifiez la configuration de la quête", + q_get = "Vous pouvez obtenir une quête de ces PNJ", + q_noquests = "Il n'y a pas encore moyen de jouer des quêtes :(", + q_ent_draw = "Distance de tirage de l'entité de quête", + q_loop_stop_key = "Clé d'arrêt de quête en boucle", + q_hold_key_stop = "Pour arrêter la suspension de quête [%s] ", -- Pour arrêter la suspension de quête [P] + q_enter_veh = "Entrez votre véhicule", + q_npc_link = "Lier la quête à un PNJ", + q_icon68 = "Entrez l'URL de l'icône .PNG 68 x 68 px", + q_ent_pos_show = "Afficher l'emplacement des entités au joueur", + q_area_size = "Taille de la zone", + q_area_pos = "Position de la zone", + q_s_area_size = "Taille de la zone de recherche", + q_s_area_pos = "Position de la zone de recherche", + q_npc_answer_ok = "Réponse positive du joueur", + q_npc_answer_no = "Réponse négative du joueur", + q_npc_answer_noq = "Réponse du joueur si aucune quête", + q_npc_quest_no = "Discours des PNJ si aucune quête", + q_money_give = "De l'argent à donner", + + -- Simple NPCs + + npc_editor = "Éditeur de PNJ", + npc_new = "Nouveau PNJ", + npc_select = "Sélectionnez un PNJ", + npc_e_speech = "Entrez le discours du PNJ", + npc_submit = "Confirmer la création du PNJ", + npc_update = "Mettre à jour le PNJ", + npc_remove = "Supprimer un PNJ", + npc_q_enable = "Activer les PNJ de quête", + npc_did_open = "ID de boîte de dialogue à ouvrir", + npc_q_target = "NPC est une cible objective", + npc_hostile = "PNJ hostile", + + -- Update 1.1.0 + + cam_start = "Paramètres de départ de la caméra", + cam_end = "Paramètres de fin de la caméra", + cam_pos = "Position de la caméra", + cam_ang = "Angle de caméra", + cam_fov = "FOV de caméra", + cam_effect = "Effet d'obturateur de caméra", + q_open_target = "Autoriser les joueurs à tuer les autres NPC", + q_npc_mind = "Distance min. au NPC", + not_spawned = "non apparu", + dis_text = "Texte affiché", + cam_speed = "Vitesse de mouvement de la caméra (nombre bas - mouvement lent)", + fov_speed = "Vitesse du FOV de la caméra (nombre bas - mouvement lent)", + category_des = "Catégorie de quêtes, utilisé pour trier les quêtes", + sortquests_cat = "Trier les quêtes par catégorie", + search_q = "Rechercher des quêtes", + quest_tools = "Outils de quête", + set_anim = "Mettre une animation", + s_quest_blacklist = "Configurer une liste noire de quête", -- In France, we can say blacklist too + s_quest_blacklist_desc = "Sélectionnez les quêtes qui vont se bloquer si elles sont jouées", + hold_use = "Maintener la touche [%s]", + duplicate = "Duplicé", + unsorted = "Non-trié", + search = "Recherche", + duration = "Durée", + category = "Catégorie", + blacklist = "Liste noire", + + + -- Update 1.2.0 + + restore_wep = "Restorer les armes à la fin de la quête", + e_cmd = "Entrer une commande console", + e_args = "Entrer des commandes d'arguments", + hint_cmd = "Autoremplissage de raccourcis : \n$uid - IDJoueur, \n$sid - SteamID, \n$s64 - SteamID 64, \n$n - No du joueur", + youaretracked = "Votre position est compromise par d'autres joueurs !", + border_rounded = "Design de bordure ronde", + border_square = "Design de bordure carrée", + access_settings = "Accès au menu", + compact_obj = "Compacter la liste des objectifs pour les quêtes", + e_usergroup = "Entrer un groupe d'utilisateurs", + ug_isanadmin = "Ce groupe d'utilisateur à déjà des pleins accès", + find_player_id32 = "Trouver un joueur par le SteamID 32", + user_data = "Éditeur de données", + access_editors = "Accès à configurer l'éditeur de quêtes", + access_admins = "Mettre des accès complets", + add_usergroup = "Ajouter un utilisateur au groupe", + edit_objmod = "Éditer les objectifs du mode", + editmod = "Éditer des modes", + move = "Déplacer", + q_errorloop = "Quête dans une loupe infinie", + q_cooldow_perply = "Compteur de temps public", + q_cooldow_publick = "Compteur de temps par joueur", + q_stop_anytime = "Autoriser manuellement l'abandon de quêtes", + quest_abandon = "Vous avez abandonné la quête", + q_dotime_set = "Configurer la fin de quête à la fin d'un temps", + + + -- Ranks + + enter_path_or_url = "Entrer l'emplacement ou l'URL", + rank_edit = "Configuration des grades", + rank_list = "Liste de grades", + group_list = "Liste des groupes", + group_addnew = "Ajouter un nouveau groupe", + blank = "Blanc", + mrs_show_all = "Montrer les grades à tous les joueurs", + mrs_show_team = "Montrer les grades uniquement au groupe", + mrs_use_sn = "Afficher des noms de grade courts", + use_url = "Utiliser une URL", + enter_srt_name = "Entrer un nom court", + srt_name = "Nom court", + mrs_prom_demote = "Les prochaines 2 options affectent uniquement les grades les plus bas. Les joueurs ne pourront pas promotionner les autres joueurs les hauts grades ou le même grade.", + mrs_whilelist = "Si vous sélectionnez un grade requis pour un job, le joueur pourra jouer avec le job que si il a le grade défini ou plus.", + can_promote = "Peut promotionner le grade d'un joueur", + can_demote = "Peut rétrograder le grade d'un joueur", + edit_player_model = "Éditer le playermodel personnalisé", + enable_player_model = "Activer le playermodel personnalisé", + disable_player_model = "Désactiver le playermodel personnalisé", + edit_custom_stats = "Éditer les stats personnalisés du joueur", + autoprom = "Auto-promotionner au prochain grade", + in_min = "en minutes", + mrs_promoted = "Vous avez été promotionné", + mrs_demoted = "Vous avez été rétrogradé", + mrs_job_smallrank = "Vous devez être %s ou plus pour jouer en tant que %s", + show_group = "Montrer uniquement le nom du grade", + hide_rank = "Montrer uniquement l'icone du grade", + mrs_hud_follow = "Rotatationner l'UI autour du joueur dépendant de la vue de l'angle", + set_overhead = "UI de l'information du joueur", + offline_users = "Utilisateurs hors-ligne", + mrs_noranks = "Votre métier actuel n'a pas de grades", + mrs_nopower = "Votre grade actuel n'a pas plus de permissions", + promotion = "Promotion", + on_duty = "En service", + other_players = "Autres joueurs", + mrs_change_jobname = "Changer le job au nom du grade", + mrs_set_prefix = "Ajouter le nom du grade au préfix du nom du métier", +} + +-- Other phrases +local lng = "fr" + +MSD.Language[lng]["Move to point"] = "Déplacer vers le point" +MSD.Language[lng]["Leave area"] = "Quitter la zone" +MSD.Language[lng]["Kill NPC"] = "Tuer un NPC" +MSD.Language[lng]["Collect quest ents"] = "Collecter les ents de quête" +MSD.Language[lng]["Talk to NPC"] = "Parler à NPC" +MSD.Language[lng]["Wait time"] = "Temps d'attente" + +MSD.Language[lng]["There is no quests avalible"] = "Il n'y a pas de quêtes disponibles" + +MSD.Language[lng]["Give weapon"] = "Donner une arme" +MSD.Language[lng]["Give ammo"] = "Donner des munitions" +MSD.Language[lng]["Strip Weapon"] = "Arme de bande" +MSD.Language[lng]["Spawn quest entity"] = "Entité de quête d'apparition" +MSD.Language[lng]["Spawn entity"] = "Entité d'apparition" +MSD.Language[lng]["Spawn npc"] = "Apparition du npc" +MSD.Language[lng]["Manage do time"] = "Gérer le temps de travail" +MSD.Language[lng]["Spawn vehicle"] = "Apparition du véhicule" +MSD.Language[lng]["Remove vehicle"] = "Retirer le véhicule" +MSD.Language[lng]["Remove all entites"] = "Supprimer toutes les entités" +MSD.Language[lng]["Set HP"] = "Définir HP" +MSD.Language[lng]["Set Armor"] = "Définir l'armure" + +MSD.Language[lng]["DarkRP Money"] = "Argent DarkRP" + +MSD.Language[lng]["Quest NPCs are disabled"] = "Les PNJ de quête sont désactivés" +MSD.Language[lng]["You can enable them in settings"] = "Vous pouvez les activer dans les paramètres" \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/language/nl.lua b/addons/msd_ui/lua/msd/language/nl.lua new file mode 100644 index 0000000..7690c4f --- /dev/null +++ b/addons/msd_ui/lua/msd/language/nl.lua @@ -0,0 +1,349 @@ +MSD.Language["nl"] = { + + lang_name = "Netherlands", + + ok = "OKE", + map = "Kaart", + off = "Uit", + on = "Op", + time_add = "Tijd om toe te voegen", + type = "Type", + delay = "Vertraging", + cancel = "Annuleren", + enable = "Inschakelen", + model = "Model", + name = "Naam", + settings = "Instellingen", + editor = "Editor", + red = "Rood", + green = "Groente", + blue = "Blauw", + admin_menu = "Administratie menu", + ui_settings = "UI instellingen", + active = "Actief", + inactive = "Inactief", + disabled = "Gehandicapt", + warning = "Waarschuwing!", + remove = "Verwijderen", + theme = "Thema", + dark_theme = "Donker thema", + payment = "Betaling", + load_autosave = "Laad laatste autosave?", + load_save = "Laad gegevens", + create_new = "Maak nieuw", + enable_option = "Optie inschakelen", + main_opt = "Belangrijkste opties", + copy_data = "Gegevens kopiëren", + save_chng = "Wijzigingen opslaan", + enter_name = "Voer de naam in", + enter_id = "Voer de ID in", + confirm_action = "Bevestig uw acties a.u.b.", + check_fpr_errors = "Controleer op fouten", + enter_description = "Voer beschrijving in", + cooldown_ok = "Cooldown bij succes", + cooldown_fail = "Afkoelen bij mislukt", + s_team_whitelist = "Witte lijst voor team instellen", + whitelist_blacklist = "De witte lijst is een zwarte lijst", + custom_val = "Aangepaste waarde instellen", + set_hp_full = "Zet volledige HP", + dist_to_close = "Afstand tot dichtstbijzijnde", + + e_text = "Tekst invoeren", + e_number = "Voer nummer in", + e_class = "Ga naar de klas", + e_value = "Voer waarde in", + e_blank_dis = "Laat leeg om uit te schakelen", + e_blank_default = "Laat leeg om standaard te gebruiken", + e_url = "Binnenkomen URL", + e_model = "Voer modelpad in", -- voorbeeld: model/alyx.mdl + e_material = "Voer materiaalpad in", + e_wep_class = "Voer wapenklasse in", + e_ent_class = "Entiteitsklasse invoeren", + e_veh_class = "Voer voertuigklasse in", + e_npc_class = "Voer NPC-klasse in", + + select_ammo = "geselecteerde munitie", + amount_ammo = "Munitie hoeveelheid", + disable_phys = "Schakel natuurkunde uit", + none = "Geen", + custom_icon = "Aangepast pictogram instellen", + weapon_name = "wapen naam", + moveup = "Omhoog gaan", + movedown = "Naar beneden verplaatsen", + movepoint = "Punt verplaatsen", + swap = "Ruil", + swapmod = "Swap-mod ingeschakeld. Klik om uit te schakelen", + copy_from_ent = "Kopiëren van kijkende entiteit", + set_pos_self = "Instellen op uw positie", + set_pos_aim = "Ingesteld op kijkpunt", + spawn_point = "spawn punt", + spawn_ang = "Spawn hoek", + mark_area = "Markeer gebied", + time_wait = "Tijd om te wachten", + map_marker = "Selecteer kaartmarkering", + in_sec = "in seconden", + def_units = "Standaard %s eenheden", -- "Standaard 350 units" het verlof %s zoals het is + def_seconds = "Standaard %s seconden", -- "Standaard 10 seconds" het verlof %s zoals het is + ent_show_pointer = "Toon aanwijzer boven de entiteit", + ent_arcade_style = "Uiterlijk van entiteit in arcadestijl", + ent_stnd_style = "Standaard uiterlijk van entiteit", + custom_color = "Aangepaste kleur inschakelen", + mat_default = "Leeg laten voor standaard materiaal", + + set_ui = "UI instellingen", + set_hud = "HUD instellingen", + set_hud_pos = "Quest HUD positie", + set_hud_themes = "HUD Thema's", + set_server = "Server instellingen", + set_ui_blur = "Blur achtergrond", + set_ui_mono = "Monochrome achtergrond", + set_ui_vignette = "Vigneteffect voor achtergrond", + set_ui_brightness = "Achtergrondhelderheid", + set_ui_color = "Selecteer de hoofdkleur", + set_ui_align_left = "Horizontale uitlijning naar links", + set_ui_align_right = "Horizontale uitlijning naar rechts", + set_ui_align_top = "Verticale uitlijning naar boven", + set_ui_align_bottom = "Verticale uitlijning naar beneden", + set_ui_offset_h = "Horizontale verschuiving", + set_ui_offset_v = "Verticale verschuiving", + + upl_changes = "Wijzigingen uploaden naar server", + res_changes = "Wijzigingen herstellen", + + -- Player + + dead = "Je bent dood", + time_ex = "Tijd verlopen", + vehicle_bum = "Uw voertuig is vernietigd", + left_area = "Je hebt het gebied verlaten", + m_blew = "You blew up the mission", + m_failed = "Missie gefaald", + m_success = "Missie geslaagd", + m_loop = "Missie update", + + -- Errors + + inv_quest = "Ongeldige zoektocht", + team_bl = "Uw team staat op de zwarte lijst", + no_players = "Server heeft meer spelers nodig om online te zijn voordat je dit kunt doen", + no_players_team = "Server heeft meer spelers nodig voor specifieke team(s) om online te zijn voordat je dit kunt doen", + need_admin = "Alleen beheerders kunnen deze actie uitvoeren", + + -- Quests + + active_quest = "Je hebt een actieve zoektocht", + inactive_quest = "Je kunt deze zoektocht niet spelen", + quest_editor = "Speurtocht Editor", + quest_list = "Speurtocht List", + quests = "Speurtochts", + leave_pnt = "Het verlof point", + + q_editobj = "Doelstellingen bewerken", + q_incvobj = "Ongeldige doelstelling", + q_setobj = "Ongeldige instellingen", + q_newobj = "Nieuwe doelstelling toevoegen", + q_editrwd = "Bewerk beloningen", + q_rwdeditor = "Belonings editor", + q_rwdlist = "Reward list", + q_rwdsets = "Belonings lijst", + q_findmap = "Zoek een zoektocht van andere kaarten", + q_obj_des = "Objectieve beschrijving", + q_dist_point = "Afstand tot punt", + q_dist_from_point = "Afstand vanaf punt", + q_ignore_veh = "Negeer zoektocht voertuig", + q_timer_show = "Toon de timer aan speler", + q_area_stay = "Speler moet in het gebied blijven", + q_start = "Zoektocht starten", + q_new = "Nieuwe zoektocht", + q_submit = "Zoekopdracht indienen", + q_addnew = "Nieuwe zoektocht toevoegen", + q_remove = "Zoektocht verwijderen", + q_id_unique = "ID moet uniek zijn voor elke zoektocht", + q_complete_msg = "Quest voltooid bericht", + q_dotime = "Quest doe tijd", + q_dotime_ok = "Slaag de zoektocht op tijd einde", + q_dotime_fail = "Mislukte zoektocht op tijd einde", + q_death_fail = "Mislukte zoektocht bij dood van speler", + q_loop = "Loop-queeste genomen", + q_loop_reward = "Beloon speler bij elke lus", + q_enable = "Zoektocht inschakelen", + q_events = "Evenementen", + q_eventadd = "Toevoegen Evenementen", + q_eventedit = "Evenementen edit", + q_eventremove = "Evenement verwijderen", + q_in_progress = "Quest in uitvoering", + q_time_left = "Tijd over", + q_ply_limit = "Spelerslimiet voor de zoektocht", + q_ply_team_limit = "Teamlimieten instellen", + q_ply_team_need = "benodigde teamspelers", + q_ply_need = "Spelersbedrag nodig om te starten", + q_play_limit = "Er is een limiet aan hoeveel spelers deze zoektocht kunnen spelen", + q_must_stay_area = "Je moet binnen dit gebied blijven, anders mislukt de zoektocht", + q_time_wait = "Je moet wachten voordat je deze quest opnieuw kunt spelen", + q_dotime_reset = "Reset quest do time", + q_dotime_add = "Quest do time toevoegen", + q_noreplay = "Je kunt deze zoektocht niet opnieuw spelen", + q_dis_replay = "Disable quest replay", + q_needquest = "You need to finish another quest first", + q_needquest_menu = "Voltooide zoektocht vereisen", + q_enterror = "Quest-entiteiten zijn niet voortgekomen, controleer de setup van de missie", + q_get = "Je kunt een quest krijgen van deze NPC's", + q_noquests = "Er is nog geen manier om speurtochten te spelen :(", + q_ent_draw = "Trekafstand van de queeste-entiteit", + q_loop_stop_key = "Stop-toets voor doorlopende zoektocht", + q_hold_key_stop = "Om quest hold te stoppen [%s]", -- To stop quest hold [P] + q_enter_veh = "Voer uw voertuig in", + q_npc_link = "Zoektocht koppelen aan een NPC", + q_icon68 = "Voer url in naar .PNG pictogram 68x68 px", + q_ent_pos_show = "Locatie van entiteiten aan speler tonen", + q_area_size = "Oppervlakte", + q_area_pos = "Gebiedspositie", + q_s_area_size = "Grootte van het zoekgebied", + q_s_area_pos = "Zoekgebied positie", + q_npc_answer_ok = "Positief antwoord van de speler", + q_npc_answer_no = "Negatief antwoord van de speler", + q_npc_answer_noq = "Het antwoord van de speler als er geen speurtochten zijn", + q_npc_quest_no = "NPC spraak als er geen speurtochten zijn", + q_money_give = "Geld om te geven", + + -- Simple NPCs + + npc_editor = "NPC-editor", + npc_new = "Nieuwe NPC", + npc_select = "Selecteer een NPC", + npc_e_speech = "Voer NPC-spraak in", + npc_submit = "Aanmaak van NPC bevestigen", + npc_update = "NPC bijwerken", + npc_remove = "NPC verwijderen", + npc_q_enable = "Zoek-NPC's inschakelen", + npc_did_open = "Dialoogvenster-ID om te openen", + npc_q_target = "NPC is een objectief doelwit", + npc_hostile = "Vijandige NPC", + + + -- Update 1.1.0 + + cam_start = "Startparameters camera", + cam_end = "Camera einde parameters", + cam_pos = "Camera positie", + cam_ang = "Camera hoek", + cam_fov = "Camerabeeldhoek", + cam_effect = "Camerasluitereffect", + q_open_target = "Sta andere spelers toe om NPC's te doden", + q_npc_mind = "Min. afstand tot NPC's", + not_spawned = "niet voortgebracht", + dis_text = "Weergegeven tekst", + cam_speed = "Bewegingssnelheid camera (langzamer getal - langzamere beweging)", + fov_speed = "FOV-veranderingssnelheid (lager getal - langzamere beweging)", + category_des = "Missiecategorie, gebruikt om missies te sorteren", + sortquests_cat = "Sorteer missies op categorie", + search_q = "Zoekopdrachten", + quest_tools = "Quest-tools", + set_anim = "Animatieset", + s_quest_blacklist = "Zwarte lijst voor missies instellen", + s_quest_blacklist_desc = "Selecteer missies die deze missie blokkeren als je ze hebt gespeeld", + hold_use = "Houden [%s] key", + duplicate = "Duplicaat", + unsorted = "ongesorteerd", + search = "Zoekopdracht", + duration = "Looptijd", + category = "Categorie", + blacklist = "Zwarte lijst", + + + -- Update 1.2.0 + + restore_wep = "Wapens herstellen aan het einde van de zoektocht", + e_cmd = "Voer consoleopdracht in", + e_args = "Voer opdrachtargumenten in", + hint_cmd = "Sneltoetsen aanvullen: \n$uid - UserID, \n$sid - SteamID, \n$s64 - SteamID 64, \n$n - Plaag naam", + youaretracked = "Your position is now compromised to other players!", + border_rounded = "Rounded border design", + border_square = "Vierkant randontwerp", + access_settings = "Toegang tot menu", + compact_obj = "Compacte doelstellingenlijst voor speurtochten", + e_usergroup = "Gebruikersgroep invoeren", + ug_isanadmin = "Deze gebruikersgroep heeft al volledige toegang", + find_player_id32 = "Vind spelergegevens op SteamID 32", + user_data = "Editor voor gebruikersgegevens", + access_editors = "Toegang voor quest-editors instellen", + access_admins = "Volledige toegang instellen", + add_usergroup = "Gebruikersgroep toevoegen", + edit_objmod = "Objectvolgorde bewerken", + editmod = "Mode bewerken", + move = "Beweging", + q_errorloop = "Quest ingevoerd in een eindeloze lus", + q_cooldow_perply = "Openbare afkoeltimer", + q_cooldow_publick = "Afkoeltimer per speler", + q_stop_anytime = "Toestaan om de zoektocht handmatig te verlaten", + quest_abandon = "Je hebt de zoektocht opgegeven", + q_dotime_set = "Tijd voor quest instellen", + + + -- Ranks + + enter_path_or_url = "Voer pad of url in", + rank_edit = "Rangen instellen", + rank_list = "Ranglijst", + group_list = "Groepslijst", + group_addnew = "Nieuwe groep toevoegen", + blank = "Blanco", + mrs_show_all = "Toon rangen aan alle spelers", + mrs_show_team = "Toon rangen alleen aan de groep", + mrs_use_sn = "Korte rangnamen weergeven", + use_url = "Gebruik URL", + enter_srt_name = "Voer korte naam in", + srt_name = "Korte naam", + mrs_prom_demote = "De volgende 2 opties zijn alleen van invloed op lagere rangen. Spelers met deze rang kunnen andere spelers niet promoveren naar hogere rangen of dezelfde rang.", + mrs_whilelist = "Als je een rangvereiste voor een baan selecteert, kan de speler deze baan alleen spelen als zijn rang overeenkomt met de gekozen of hoger.", + can_promote = "Kan de spelersrang bevorderen", + can_demote = "Kan spelerrang degraderen", + edit_player_model = "Aangepast spelersmodel bewerken", + enable_player_model = "Aangepast spelermodel inschakelen", + disable_player_model = "Aangepast spelermodel uitschakelen", + edit_custom_stats = "Aangepaste spelerstatistieken bewerken", + autoprom = "Automatisch promoveren naar de volgende rang", + in_min = "in minuten", + mrs_promoted = "Je bent gepromoveerd", + mrs_demoted = "Je bent gedegradeerd", + mrs_job_smallrank = "Je moet %s of hoger zijn om als %s. te spelen", -- You must be Sergeant II or higher to play as Watch Commander + show_group = "Naam ranggroep weergeven", + hide_rank = "Toon alleen rang icoon", + mrs_hud_follow = "Draai de gebruikersinterface rond de speler, afhankelijk van de kijkhoek", + set_overhead = "Spelers info UI", + offline_users = "Offline gebruikers", + mrs_noranks = "Je huidige baan heeft geen rangen", + mrs_nopower = "Je huidige rang heeft geen extra rechten", + promotion = "Promotie", + on_duty = "In functie", + other_players = "andere spelers", + mrs_change_jobname = "Wijzig de taaknaam in de rangnaam", + mrs_set_prefix = "Voeg de rangnaam toe als voorvoegsel aan de taaknaam", + copy_all_data = "Kopieer alle gegevens", + copy_only_stats = "Kopieer alleen statistieken en spelersmodel", +} + +-- Other phrases +local lng = "nl" + +MSD.Language[lng]["Move to point"] = "Verplaatsen naar punt" +MSD.Language[lng]["Leave area"] = "Verlaat gebied" +MSD.Language[lng]["Kill NPC"] = "Dood NPC" +MSD.Language[lng]["Collect quest ents"] = "Verzamel zoektochten" +MSD.Language[lng]["Talk to NPC"] = "Praat met NPC" +MSD.Language[lng]["There is no quests available"] = "Er zijn geen speurtochten beschikbaar" +MSD.Language[lng]["Give weapon"] = "Geef wapen" +MSD.Language[lng]["Give ammo"] = "Geef munitie" +MSD.Language[lng]["Strip Weapon"] = "Stripwapen" +MSD.Language[lng]["Spawn quest entity"] = "Spawn Quest-entiteit" +MSD.Language[lng]["Spawn entity"] = "Spawn entiteit" +MSD.Language[lng]["Spawn npc"] = "spawn npc" +MSD.Language[lng]["Manage do time"] = "Beheer doe tijd" +MSD.Language[lng]["Spawn vehicle"] = "Spawn voertuig" +MSD.Language[lng]["Remove vehicle"] = "Voertuig verwijderen" +MSD.Language[lng]["Remove all entites"] = "Alle entiteiten verwijderen" +MSD.Language[lng]["Set HP"] = "Stel HP" +MSD.Language[lng]["Set Armor"] = "Pantser instellen" +MSD.Language[lng]["DarkRP Money"] = "DarkRP Geld" +MSD.Language[lng]["Quest NPCs are disabled"] = "Quest-NPC's zijn uitgeschakeld" +MSD.Language[lng]["You can enable them in settings"] = "Je kunt ze inschakelen in instellingen" diff --git a/addons/msd_ui/lua/msd/language/ru.lua b/addons/msd_ui/lua/msd/language/ru.lua new file mode 100644 index 0000000..9f723dc --- /dev/null +++ b/addons/msd_ui/lua/msd/language/ru.lua @@ -0,0 +1,366 @@ +MSD.Language["ru"] = { + + -- UI + + lang_name = "Русский", + + ok = "ОК", + map = "Карта", + off = "Выкл", + on = "Вкл", + time_add = "Добавить время", + type = "Тип", + delay = "Задержка", + cancel = "Отменить", + enable = "Активировать", + model = "Модель", + name = "Название", + settings = "Настройки", + editor = "Редактор", + red = "Красный", + green = "Зеленый", + blue = "Синий", + admin_menu = "Админ меню", + ui_settings = "Настройки интерфейса", + active = "Активно", + inactive = "Неактивно", + disabled = "Отключено", + warning = "Внимание!", + remove = "Удалить", + theme = "Тема", + dark_theme = "Темная тема", + payment = "Оплата", + load_autosave = "Загрузить последнее автосохранение?", + load_save = "Загрузить сохранение", + create_new = "Создать новый", + enable_option = "Включить опцию", + main_opt = "Главные опции", + copy_data = "Копировать данные", + save_chng = "Сохранить изменения", + enter_name = "Ввести название", + enter_id = "Ввести ID", + confirm_action = "Подтвердите пожалуйста действия", + check_fpr_errors = "Проверить на ошибки", + enter_description = "Ввести описание", + cooldown_ok = "Задержка при успехе", + cooldown_fail = "Задержка при провале", + s_team_whitelist = "Настроить командный вайтлист", + whitelist_blacklist = "Вайтлист=блэклист", + custom_val = "Установить собственное значение", + set_hp_full = "сделать полное ХП", + dist_to_close = "Дистанция к ближайшему", + + e_text = "Введите текст", + e_number = "Введите номер", + e_class = "Введите класс", + e_value = "Введите значение", + e_blank_dis = "Оставьте пустым чтобы отключить", + e_blank_default = "Оставьте пустым чтобы использовать по-умолчанию", + e_url = "Введите URL", + e_model = "Введите путь модели", + e_material = "Введите путь материала", + e_wep_class = "Введите класс оружия", + e_ent_class = "Введите класс энтити", + e_veh_class = "Введите класс транспорта", + e_npc_class = "Введите класс НПС", + + select_ammo = "Выбранные патроны", + amount_ammo = "Количество патронов", + disable_phys = "Отключить физику", + none = "Нет", + custom_icon = "Выбрать кастомную иконку", + weapon_name = "Название оружия", + moveup = "Передвинуть вверх", + movedown = "Передвинуть вниз", + movepoint = "Передвинуть точку", + swap = "Поменять", + swapmod = "Мод замены включен. Нажмите чтобы отключить", + copy_from_ent = "Коп. с предмета", + set_pos_self = "Выставить свою поз.", + set_pos_aim = "Выставить по взгляду", + spawn_point = "Точка спавна", + spawn_ang = "Угол спавна", + mark_area = "Обозначить территорию", + time_wait = "Время ожидания", + map_marker = "Выберите маркер карты", + in_sec = "В секундах", + def_units = "единицы по-умолчанию", -- "По-умолчанию 350 единиц" оставьте %s без изменений + def_seconds = "Секунды по-умолчанию", -- "По-умолчанию 10 секунд" оставьте %s без изменений + ent_show_pointer = "Показать маркер над энтити", + ent_arcade_style = "Аркадный стиль появления энтити", + ent_stnd_style = "Стандартное появление энтити", + custom_color = "Включить кастомный цвет", + mat_default = "Оставьте пустым для материала по-умолчанию", + + set_ui = "UI настройки", + set_hud = "HUD настройки", + set_hud_pos = "Позиция интерфейса квеста", + set_hud_themes = "Темы интерфейса", + set_server = "Настройки сервера", + set_ui_blur = "Размытие заднего фона", + set_ui_mono = "Монохром заднего фона", + set_ui_vignette = "Эффект виньетки заднего фона", + set_ui_brightness = "Яркость заднего фона", + set_ui_color = "Выберите главный цвет", + set_ui_align_left = "Горизонтальное выравнивание влево", + set_ui_align_right = "Горизонтальное выравнивание вправо", + set_ui_align_top = "Вертикальное выравнивание вверх", + set_ui_align_bottom = "Вертикальное выравнивание вниз", + set_ui_offset_h = "Горизонтальное смещение", + set_ui_offset_v = "Вертикальное смещение", + + upl_changes = "Загрузить изменения на сервер", + res_changes = "Восстановить изменения", + + -- Player + + dead = "Вы мертвы", + time_ex = "Время вышло", + vehicle_bum = "Ваш транспорт уничтожен", + left_area = "Вы покинули территорию", + m_blew = "Ваша миссия раскрыта", + m_failed = "Миссия провалена", + m_success = "Миссия выполнена", + m_loop = "Обновление миссии", + + -- Errors + + inv_quest = "Неправильный квест", + team_bl = "Ваша команда в блэклисте", + no_players = "Нужно больше игроков онлайн для данного действия", + no_players_team = "Нужно больше игроков онлайн в определенных командах для данного действия", + need_admin = "Только администратор может это сделать", + + -- Quests + + active_quest = "У Вас есть активный квест", + inactive_quest = "Вы не можете взять этот квест", + quest_editor = "Редактор квеста", + quest_list = "Список квестов", + quests = "Квесты", + leave_pnt = "Покинуть точку", + + q_editobj = "Редактировать задачи", + q_incvobj = "Неправильная задача", + q_setobj = "Настройки задачи", + q_newobj = "Добавить задачу", + q_editrwd = "Редактировать награду", + q_rwdeditor = "Редактор награды", + q_rwdlist = "Список наград", + q_rwdsets = "Настройка наград", + q_findmap = "Найти квесты с других карт", + q_obj_des = "Описание задачи", + q_dist_point = "Расстояние до точки", + q_dist_from_point = "Расстояние от точки", + q_ignore_veh = "Игнорировать квестовый транспорт", + q_timer_show = "Показывать таймер игроку", + q_area_stay = "Игрок должен находиться в зоне", + q_start = "Начать квест", + q_new = "Новый квест", + q_submit = "Опубликовать квест", + q_addnew = "Добавить новый квест", + q_remove = "Удалить квест", + q_id_unique = "ID должен быть уникален для каждого квеста", + q_complete_msg = "Сообщение при выполнение квеста", + q_dotime = "Время квеста", + q_dotime_ok = "Засчитать выполнение квеста по истечению таймера", + q_dotime_fail = "Засчитать провал квеста по истечению таймера", + q_death_fail = "Засчитать провал квеста при смерти игрока", + q_loop = "Зациклить задачи квеста", + q_loop_reward = "Награждать игрока при выполнении цикла", + q_enable = "Включить квест", + q_events = "Эвенты", + q_eventadd = "Добавить эвент", + q_eventedit = "редактировать эвент", + q_eventremove = "убрать эвент", + q_in_progress = "Квест в работе", + q_time_left = "Оставшееся время", + q_ply_limit = "Лимит игроков для квеста", + q_ply_team_limit = "Установить лимит команд", + q_ply_team_need = "Требование к игрокам в команде", + q_ply_need = "Требование к кол-ву игроков для старта", + q_play_limit = "Установлен лимит на кол-во игроков для квеста", + q_must_stay_area = "Вы должны находится в этой зоне или квест будет провален", + q_time_wait = "Вы должны подождать прежде чем начинать квест снова", + q_dotime_reset = "Сбросить время выполнения квеста", + q_dotime_add = "Добавить время выполнения квеста", + q_noreplay = "Вы не можете повторить этот квест", + q_dis_replay = "Выключить повтор квеста", + q_needquest = "Сначала Вы должны закончить другой квест", + q_needquest_menu = "Необходим выполненный квест", + q_enterror = "Энтити квеста не заспавнились, проверьте настройки квеста", + q_get = "Вы можете взять квест у этих НПС", + q_noquests = "Пока что нет способа начать квесты :(", + q_ent_draw = "Дистанция прорисовки квестового энтити", + q_loop_stop_key = "Кнопка остановки повторяемого квеста", + q_hold_key_stop = "Чтобы остановить квест удерживайте [%s]", -- Чтобы остановить квест удерживайте [P] + q_enter_veh = "Садитесь в свое транспортное средство", + q_npc_link = "Привязать квест к НПС", + q_icon68 = "Введите url для .PNG иконки 68x68 пикс.", + q_ent_pos_show = "Показывать локацию энтити игроку", + q_area_size = "Размер зоны", + q_area_pos = "Позиция зоны", + q_s_area_size = "Поиск размера зоны", + q_s_area_pos = "Поиск позиции зоны", + q_npc_answer_ok = "Положительный ответ игрока", + q_npc_answer_no = "Отрицательный ответ игрока", + q_npc_answer_noq = "Ответ игрока при отсутствии квестов", + q_npc_quest_no = "Реплика НПСа при отсутствии квестов", + q_money_give = "Деньги для выдачи", + + -- Simple NPCs + + npc_editor = "Редактор НПС", + npc_new = "Новый НПС", + npc_select = "Выберите НПС", + npc_e_speech = "Введите реплику НПС", + npc_submit = "Подтвердите создание НПС", + npc_update = "Обновить НПС", + npc_remove = "Убрать НПС", + npc_q_enable = "Включить квестовых НПС", + npc_did_open = "ID диалога для открытия", + npc_q_target = "Цель задачи - НПС", + npc_hostile = "Врадждебный НПС", + + -- Update 1.1.0 + + duration = "Продолжительность", + dis_text = "Отображаемый текст", + cam_speed = "Скорость движения камеры (меньше число - медленнее движение)", + fov_speed = "Скорость изменения поля зрения (меньше число - медленнее движение)", + cam_start = "Параметры запуска камеры", + cam_end = "Параметры конца камеры", + cam_pos = "Позиция камеры", + cam_ang = "Угол камеры", + cam_fov = "Поля зрения камеры", + cam_effect = "Эффект затвора камеры", + not_spawned = "не создан", + q_open_target = "Разрешить другим игрокам убивать NPC", + q_npc_mind = "Мин. расстояние до NPC", + duplicate = "Дублировать", + hold_use = "Удерживайте кнопку [%s]", + category = "Категория", + category_des = "Категория квестов, используемая для сортировки квестов", + sortquests_cat = "Сортировать квесты по категориям", + unsorted = "Без Категория", + search = "Поиск", + search_q = "Искать квесты", + quest_tools = "Инструменты", + set_anim = "Установить анимацию", + s_quest_blacklist = "Установить черный список квестов", + blacklist = "Черный список", + s_quest_blacklist_desc = "Выберите квесты, которые заблокируют этот квест, если вы их сыграли", + + -- Update 1.2.0 + + restore_wep = "Восстановить оружие по окончании квеста", + e_cmd = "Введите консольную команду", + e_args = "Введите аргументы команды", + hint_cmd = "Параметры для автозаполнение: \n$uid - UserID, \n$sid - SteamID, \n$s64 - SteamID 64, \n$n - Имя игрока", + youaretracked = "Ваше местоположение теперь видно другим игрокам!", + border_rounded = "Дизайн с закругленными краями", + border_square = "Дизайн с квадратными краями", + access_settings = "Доступ к меню", + compact_obj = "Компактный список задач для квестов", + e_usergroup = "Введите группу пользователей", + ug_isanadmin = "Эта группа пользователей уже имеет полный доступ", + find_player_id32 = "Найти данные игрока по SteamID 32", + user_data = "Редактор данных игрока", + access_editors = "Установить доступ для редакторов квестов", + access_admins = "Установить полный доступ", + add_usergroup = "Добавить группу пользователей", + edit_objmod = "Изменить порядок задач", + editmod = "Режим редактирования", + move = "Передвинуть", + q_errorloop = "Квест вошел в бесконечный цикл", + q_cooldow_perply = "Публичный таймер", + q_cooldow_publick = "Индивидуальный таймер", + q_stop_anytime = "Разрешить отказаться от квеста вручную", + quest_abandon = "Вы отказались от квеста", + q_dotime_set = "Установить время выполнения квеста", + + + -- Ranks + + enter_path_or_url = "Введите путь или веб адрес", + rank_edit = "Настройка рангов", + rank_list = "Список рангов", + group_list = "Список групп", + group_addnew = "Добавить новую группу", + blank = "Пустой", + mrs_show_all = "Показывать ранги всем игрокам", + mrs_show_team = "Показывать ранги только группе", + mrs_use_sn = "Отображать короткие названия рангов", + use_url = "Использовать веб адрес", + enter_srt_name = "Введите короткое имя", + srt_name = "Короткое имя", + mrs_prom_demote = "Следующие 2 параметра влияют только на более низкие ранги. Игроки с этим рангом не смогут повышать других игроков до более высоких рангов или того же ранга.", + mrs_whilelist = "Если вы выберете требование ранга для профессии, игрок сможет играть за эту профессию, только если его ранг соответсвует выбранному или выше.", + can_promote = "Может повышать ранг игрока", + can_demote = "Может понижать ранг игрока", + edit_player_model = "Настроить модель игрока", + enable_player_model = "Включить уникальную модель игрока", + disable_player_model = "Выключить уникальную модель игрока", + edit_custom_stats = "Настроить параметры игрока", + autoprom = "Автоматическое повышение до следующего ранга", + in_min = "в минутах", + mrs_promoted = "Вы получили повышение", + mrs_demoted = "Вы были понижены", + mrs_job_smallrank = "Вы должны быть %s или выше что бы играть за %s", + show_group = "Отображение названия группы рангов", + hide_rank = "Отображать только значок ранга", + mrs_hud_follow = "Интерфейс игрока следует за камерой", + set_overhead = "Интерфейс информации об игроке", + offline_users = "Офлайн-пользователи", + mrs_noranks = "Ваша текущая работа не имеет званий", + mrs_nopower = "У вашего текущего ранга нет дополнительных разрешений", + promotion = "Повышение", + on_duty = "На службе", + other_players = "Другие игроки", + mrs_change_jobname = "Поменять название профессии на название ранга", + mrs_set_prefix = "Добавьте ранг в качестве префикса к названию профессии", + mrs_change_plyname = "Поменять имя игрока на название ранга", + mrs_set_prefix_ply = "Добавьте ранг в качестве префикса к имени игрока", + + promote_limit = "Ограничение повышения", + demote_limit = "Ограничение понижения", + salary_value = "Заработная плата", + salary_set = "Установить данное значение в качестве зарплаты", + salary_add = "Добавить указанное значение к зарплате", + salary_multiply = "Умножить зарплату на заданное значение", + force_team = "Принудительно cменить команду на", + mrs_hud_3d2d = "Использовать 3D2D интерфейс", + mrs_chat_command = "Чат-команда для открытия меню рангов", + mrs_promote_command = "Чат-команда для повышения игрока", + mrs_demote_command = "Чат-команда для понижения игрока", +} + +-- Other phrases +local lng = "ru" + +MSD.Language[lng]["Move to point"] = "Выдвигайтесь к точке" +MSD.Language[lng]["Leave area"] = "Покиньте территорию" +MSD.Language[lng]["Kill NPC"] = "Убейте НПС" +MSD.Language[lng]["Collect quest ents"] = "Соберите квестовые энтити" +MSD.Language[lng]["Talk to NPC"] = "Поговорите с НПС" +MSD.Language[lng]["Wait time"] = "Ожидать время" + +MSD.Language[lng]["There is no quests avalible"] = "На данный момент квесты отсутствуют" + +MSD.Language[lng]["Give weapon"] = "Дать оружие" +MSD.Language[lng]["Give ammo"] = "Дать патроны" +MSD.Language[lng]["Strip Weapon"] = "Забрать оружие" +MSD.Language[lng]["Spawn quest entity"] = "Заспавнить квестовый энтити" +MSD.Language[lng]["Spawn entity"] = "Заспавнить энтити" +MSD.Language[lng]["Spawn npc"] = "Заспавнить НПС" +MSD.Language[lng]["Manage do time"] = "Управление временем для квеста" +MSD.Language[lng]["Spawn vehicle"] = "Заспавнить транспорт" +MSD.Language[lng]["Remove vehicle"] = "Убрать транспорт" +MSD.Language[lng]["Remove all entites"] = "Убрать все энтити" +MSD.Language[lng]["Set HP"] = "Установить ХП" +MSD.Language[lng]["Set Armor"] = "Установить броню" + +MSD.Language[lng]["DarkRP money"] = "DarkRP Валюта" + +MSD.Language[lng]["Quest NPCs are disabled"] = "Квестовые НПС выключены" +MSD.Language[lng]["You can enable them in settings"] = "Вы можете включить их в настройках" \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/language/tr.lua b/addons/msd_ui/lua/msd/language/tr.lua new file mode 100644 index 0000000..442630c --- /dev/null +++ b/addons/msd_ui/lua/msd/language/tr.lua @@ -0,0 +1,351 @@ +MSD.Language["tr"] = { + + lang_name = "Türkçe", + + ok = "Tamam", + map = "Harita", + off = "Kapalı", + on = "Açık", + time_add = "Eklenecek zaman", + type = "Tür", + delay = "Gecikme", + cancel = "İptal", + enable = "Aktif Et", + model = "Model", + name = "İsim", + settings = "Ayarlar", + editor = "Düzenleyici", + red = "Kırmızı", + green = "Yeşil", + blue = "Mavi", + admin_menu = "Yetkili Menüsü", + ui_settings = "Arayüz Ayarları", + active = "Aktif", + inactive = "Deaktif", + disabled = "Devre Dışı", + warning = "Uyarı!", + remove = "Kaldır", + theme = "Tema", + dark_theme = "Karanlık Tema", + payment = "Ödeme", + load_autosave = "Son kaydı yükleyecek misin?", + load_save = "Kaydı yükle", + create_new = "Yeni oluştur", + enable_option = "Ayarı aktif et", + main_opt = "Ana ayarlar", + copy_data = "Veriyi kopyala", + save_chng = "Değişiklikleri kaydet", + enter_name = "İsimi giriniz", + enter_id = "ID giriniz", + confirm_action = "Eylemlerinizi lütfen onaylayınız", + check_fpr_errors = "Hataları kontrol et", + enter_description = "Açıklama gir", + cooldown_ok = "Başarı sonucu bekleme süresi", + cooldown_fail = "Hata sonucu bekleme süresi", + s_team_whitelist = "Takım beyaz listesini ayarla", + whitelist_blacklist = "Beyaz liste kara liste", + custom_val = "Özel değer ayarla", + set_hp_full = "Sağlık değerini fulle", + dist_to_close = "En yakınına mesafe", + + e_text = "Yazı giriniz", + e_number = "Sayı giriniz", + e_class = "Sınıf giriniz", + e_value = "Değer giriniz", + e_blank_dis = "Devre dışı bırakmak için boş bırakın", + e_blank_default = "Varsayılan ayarlar için boş bırakın", + e_url = "Bağlantı giriniz", + e_model = "Model uzantısını giriniz", + e_material = "Materyal uzantısını giriniz", + e_wep_class = "Silah sınıfını giriniz", + e_ent_class = "Varlık sınıfını giriniz", + e_veh_class = "Araç sınıfını giriniz", + e_npc_class = "NPC sınıfını giriniz", + + select_ammo = "Seçili mermi", + amount_ammo = "Mermi asyısı", + disable_phys = "Fiziği devre dışı bırak", + none = "Hiç", + custom_icon = "Özel ikon ayarla", + weapon_name = "Silah isim", + moveup = "Yukarı git", + movedown = "Aşağıya git", + movepoint = "Noktayı hareket ettir", + swap = "Değiş", + swapmod = "Değişim modu aktif. Tıklayarak devre dışı bırak", + copy_from_ent = "Baktığın varlıktan kopyala", + set_pos_self = "Pozisyonunu ayarla", + set_pos_aim = "Baktığın noktaya ayarla", + spawn_point = "Nokta oluştur", + spawn_ang = "Açı oluştur", + mark_area = "Alanı işaretle", + time_wait = "Bekleme süresi", + map_marker = "Harita işaretçisini ayarla", + in_sec = "saniye olarak", + def_units = "Varsayılan %s ünit", -- "Default 350 units" leave %s as is + def_seconds = "Varasyılan %s saniye", -- "Default 10 seconds" leave %s as is + ent_show_pointer = "İaşretçiyi varlığın üstünde göstr", + ent_arcade_style = "Arcade-tarzında entitiy görünüşü", + ent_stnd_style = "Standart varlık görünüşü", + custom_color = "Özel rengi aktif et", + mat_default = "Varsayılan materyal için boş bırak", + + set_ui = "Kullanıcı Arayüzü ayarları", + set_hud = "HUD ayarları", + set_hud_pos = "Görev HUD'u ayarları", + set_hud_themes = "HUD Temaları", + set_server = "Sunucu ayarları", + set_ui_blur = "Arkaplanı bulanıklaştır", + set_ui_mono = "Monokrom arkaplan", + set_ui_vignette = "Arka plan için vignette efekti", + set_ui_brightness = "Arkaplan parlaklığı", + set_ui_color = "Ana rengi seç", + set_ui_align_left = "Sola yatay hizalama", + set_ui_align_right = "Sağa yatay hizalama", + set_ui_align_top = "Yukarıya dikey hizalama", + set_ui_align_bottom = "Aşağıya dikey hizalama", + set_ui_offset_h = "Yatay Ofset", + set_ui_offset_v = "Dikey Ofset", + + upl_changes = "Değişiklikleri sunucuya yükle", + res_changes = "Değişimleri eski haline getir", + + -- Player + + dead = "Öldün", + time_ex = "Zaman doldu", + vehicle_bum = "Aracın yok oldu", + left_area = "Bölgeyi terk ettin", + m_blew = "Görevi batırdın", + m_failed = "Görev başarısız", + m_success = "Görev başarılı", + m_loop = "Görev güncellemesi", + + -- Errors + + inv_quest = "Geçersiz görev", + team_bl = "Takımın karalistede", + no_players = "Bunu yapman için sunucuda daha fazla oyuncu olması gerekmekte", + no_players_team = "Bunu yapman için belirli takımlarda daha fazla oyuncu olması gerekmekte", + need_admin = "Sadece adminler bu eylemi gerçekleştirebilir", + + -- Quests + + active_quest = "Aktif bir görevin var", + inactive_quest = "Bu görevi oynayamazsın", + quest_editor = "Görev Düzenleyicisi", + quest_list = "Görev Listesi", + quests = "Görevler", + leave_pnt = "Noktadan ayrıl", + + q_editobj = "hedefleri düzenle", + q_incvobj = "Geçersiz hedef", + q_setobj = "Hedef ayarları", + q_newobj = "Yeni bir hedef oluştur", + q_editrwd = "Ödülleri düzenle", + q_rwdeditor = "Ödül Düzenleyicisir", + q_rwdlist = "Ödül Listesi", + q_rwdsets = "Ödül Ayarları", + q_findmap = "Diğer haritalardan görev bul", + q_obj_des = "Hedef açıklaması", + q_dist_point = "Noktaya olan uzaklık", + q_dist_from_point = "Noktadan olan uzaklık", + q_ignore_veh = "Görev aracını görmezden gel", + q_timer_show = "Oyuncuya zamanlayıcıyı göster", + q_area_stay = "Oyuncu bölgede kalmalı", + q_start = "Görevi başlat", + q_new = "Yeni görev", + q_submit = "Görevi sun", + q_addnew = "Yeni görev ekle", + q_remove = "Görevi kaldır", + q_id_unique = "Her bir görevin ID'si kendine özel olmalı", + q_complete_msg = "Görevi bitirme mesajı", + q_dotime = "Görev süresi", + q_dotime_ok = "Süre bitince görev başarıyla tamamlansın", + q_dotime_fail = "Süre bitince görev başarısız olsun", + q_death_fail = "Oyuncu öldüğünde görevi iptal et", + q_loop = "Görevleri tekrara al", + q_loop_reward = "Her bir tekrarda oyuncuyu ödüllendir", + q_enable = "Görevi aktif et", + q_events = "Etkinlikler", + q_eventadd = "Etkinlik Ekle", + q_eventedit = "etkinlik düzenle", + q_eventremove = "Etkinlik kaldır", + q_in_progress = "Görev işlem sürecinde", + q_time_left = "Kalan süre", + q_ply_limit = "Görev için azami oyuncu sayısı", + q_ply_team_limit = "Takım limitlerini ayarla", + q_ply_team_need = "Gerekli takım oyuncuları", + q_ply_need = "Başlamak için gereken oyuncu sayısı", + q_play_limit = "Görevi kaç oyuncunun oynayabileceğinin limiti", + q_must_stay_area = "Bu bölgenin içinde kalman gerekiyor yoksa görev başarısız olur", + q_time_wait = "Bu görevi tekrarlaman için beklemen lazım", + q_dotime_reset = "Görevin süresini sıfırla", + q_dotime_add = "Göreve yapma süresi ekle", + q_noreplay = "Bu görevi tekrar yapamazsın", + q_dis_replay = "Görev tekrarını iptal et", + q_needquest = "İlk başka bir görevi yapman gerekmekte", + q_needquest_menu = "Bitirilmiş görev gerekmektedir", + q_enterror = "Görev varlıkları oluşmamakta, görev kurulumunu kontrol et", + q_get = "Bu NPC'lerden görev alabilirsin", + q_noquests = "Görev yapabileceğin herhangi bir yol bulunmamakta :(", + q_ent_draw = "Görev varlığı çizim mesafesi", + q_loop_stop_key = "Görev tekrarlama durdurma tuşu", + q_hold_key_stop = "görevi durdurmak için [%s] tuşuna basılı tutun", -- To stop quest hold [P] + q_enter_veh = "Aracına bin", + q_npc_link = "Görevi bir NPC'ye bağla", + q_icon68 = ".PNG ve 68x68 pixel olucak şekilde bir bağlantı giriniz", + q_ent_pos_show = "Varlık lokasyonlarını oyunculara göster", + q_area_size = "Bölge boyutu", + q_area_pos = "Bölge pozisyonu", + q_s_area_size = "Bölge boyutunu ara", + q_s_area_pos = "Bölge pozisyonunu ara", + q_npc_answer_ok = "Oyuncunun pozitif cevabı", + q_npc_answer_no = "Oyuncunun negatif cevabı", + q_npc_answer_noq = "Görev yoksa oyuncunun cevabı", + q_npc_quest_no = "Görev yoksa NPC'nin konuşması", + q_money_give = "Verilecek para", + + -- Simple NPCs + + npc_editor = "NPC Düzenleyicisi", + npc_new = "Yeni NPC", + npc_select = "Bir NPC seçin", + npc_e_speech = "NPC konuşması giriniz", + npc_submit = "NPC oluşumunu onayla", + npc_update = "NPC'yi güncelle", + npc_remove = "NPC'yi kaldır", + npc_q_enable = "Görev NPC'lerini aktif et", + npc_did_open = "Dialog ID'si açılırken gereksin", + npc_q_target = "NPC objektif bir hedef", + npc_hostile = "Düşman NPC", + + + -- Update 1.1.0 + + cam_start = "Kamera başlama parametreleri", + cam_end = "Kamera parametrelerini bitir", + cam_pos = "Kamera pozisyonu", + cam_ang = "Kamera açısı", + cam_fov = "Kamera FOV'u", + cam_effect = "Kamera kapanma efekti", + q_open_target = "Diğer oyuncuların NPCleri öldürmelerine izin ver", + q_npc_mind = "NPClere olan Min. uzaklık", + not_spawned = "spawnlanmamış", + dis_text = "Sergilenen Yazı", + cam_speed = "Kamera hızı (düşük sayı - düşük hız)", + fov_speed = "FOV değişme hızı (düşük sayı - düşük hız)", + category_des = "Görev kategorisi, görevleri sınıflamak için kullanılır", + sortquests_cat = "Görevleri kategoriye göre düzenle", + search_q = "Görev ara", + quest_tools = "Görev araçları", + set_anim = "Animasyon ayarla", + s_quest_blacklist = "Görev karalistesi oluştur", + s_quest_blacklist_desc = "Bitirildiğinde bu görevi engelleyecek görevleri seçin", + hold_use = "Basılı Tut [%s]", + duplicate = "Çiftle", + unsorted = "Sınıflandırılmamış", + search = "Ara", + duration = "Süre", + category = "Kategori", + blacklist = "Karaliste", + + + -- Update 1.2.0 + + restore_wep = "Görev sonunda silahları geri yükle", + e_cmd = "Konsol komutunu girin", + e_args = "Komut bağımsız değişkenlerini girin", + hint_cmd = "Otomatik doldurma kısayolları: \n$uid - Kullanıcı Kimliği, \n$sid - SteamID, \n$s64 - SteamID 64, \n$n - Oyuncu adı", + youaretracked = "Konumunuzu artık diğer oyuncular da görebiliyor!", + border_rounded = "Yuvarlak kenarlık tasarımı", + border_square = "Kare kenarlık tasarımı", + access_settings = "Menü erişimi", + compact_obj = "Görevler için kompakt hedef listesi", + e_usergroup = "Kullanıcı grubu girin", + ug_isanadmin = "Bu kullanıcı grubu zaten tam erişime sahip", + find_player_id32 = "SteamID 32 ile oyuncu verilerini bulun", + user_data = "Kullanıcı verileri düzenleyicisi", + access_editors = "Görev düzenleyicilerinin erişimini ayarla", + access_admins = "Tam erişimi ayarla", + add_usergroup = "Kullanıcı grubu ekle", + edit_objmod = "Objektif sırasını düzenle", + editmod = "Düzenleme modu", + move = "Taşı", + q_errorloop = "Görev sonsuz bir döngüye girdi", + q_cooldow_perply = "Genel bekleme süresi sayacı", + q_cooldow_publick = "Oyuncu Başına bekleme süresi sayacı", + q_stop_anytime = "Görevi manuel olarak bırakmaya izin ver", + quest_abandon = "Görevi terk ettin", + q_dotime_set = "Görev yapma zamanını ayarla", + + -- Ranks + + enter_path_or_url = "Klasör yolu ya da url girin", + rank_edit = "Rütbe Ayarla", + rank_list = "Rütbe Listesi", + group_list = "Grup Listesi", + group_addnew = "Yeni grup ekle", + blank = "Boş", + mrs_show_all = "Tüm oyunculara rütbeleri göster", + mrs_show_team = "Sadece gruplara rütbeleri göster", + mrs_use_sn = "Rütbe kısaltmalarını göster", + use_url = "URL kullan", + enter_srt_name = "Kısaltma gir", + srt_name = "Kısaltma", + mrs_prom_demote = "Sıradaki 2 ayar sadece düşük rütbeleri etkileyecek. Bu rütbeye sahip oyuncular, diğer oyuncuları daha yüksek rütbelere veya aynı rütbeye terfi ettiremezler.", + mrs_whilelist = "Bir iş için rütbe gereksinimini seçerseniz, oyuncu bu işi ancak rütbesi seçilen veya daha yüksek olanla eşleşirse oynayabilir.", + can_promote = "Oyuncuları terfi ettirebilir", + can_demote = "Oyuncuların rütbesini düşürebilir", + edit_player_model = "Özel oyuncu modelini düzenle", + enable_player_model = "Özel oyuncu modelini aktifleştir", + disable_player_model = "Özel oyuncu modelini deaktif et", + edit_custom_stats = "Özel oyuncu istatistiklerini düzenle", + autoprom = "Sonraki rütbeye otomatik yükselt", + in_min = "dakika içinde", + mrs_promoted = "Terfi aldın", + mrs_demoted = "Rütben düşürüldü", + mrs_job_smallrank = "%s ya da daha üst bir rütbeye değilsen %s olarak oynayamazsın", -- You must be Sergeant II or higher to play as Watch Commander + show_group = "Rütbe grup ismini göster", + hide_rank = "Sadece rütbe ikonunu göster", + mrs_hud_follow = "Arayüzü oyuncunun bakış açısına göre döndür", + set_overhead = "Oyuncu bilgisi arayüzü", + offline_users = "Çevrimdışı oyuncular", + mrs_noranks = "Şu anki mesleğinin rütbesi yok", + mrs_nopower = "Şu anki rütbenin ekstra izini yok", + promotion = "Terfi", + on_duty = "Görevde", + other_players = "Diğer oyuncular", + mrs_change_jobname = "Meslek ismini rütbe ismi olarak değiştir", + mrs_set_prefix = "Rütbe ismini mesleğin başına ekle", + copy_all_data = "Bütün verileri kopyala", + copy_only_stats = "Sadece istatistikleri ve oyuncu modelini kopyala", + mrs_change_plyname = "Oyuncunun ismini rütbe ismi yap", + mrs_set_prefix_ply = "Rütbe ismini önek gibi oyuncunun isminin önüne koy", + +} + +-- Other phrases +local lng = "tr" + +MSD.Language[lng]["Move to point"] = "Noktaya git" +MSD.Language[lng]["Leave area"] = "Bölgeden ayrıl" +MSD.Language[lng]["Kill NPC"] = "NPC'yi öldür" +MSD.Language[lng]["Collect quest ents"] = "Görev varlıklarını topla" +MSD.Language[lng]["Talk to NPC"] = "NPC ile konuş" +MSD.Language[lng]["There is no quests available"] = "Mevcut görev bulunmamakta" +MSD.Language[lng]["Give weapon"] = "Silah ver" +MSD.Language[lng]["Give ammo"] = "Mermi ver" +MSD.Language[lng]["Strip Weapon"] = "Silaha el koy" +MSD.Language[lng]["Spawn quest entity"] = "Görev varlığı oluştur" +MSD.Language[lng]["Spawn entity"] = "Varlık oluştur" +MSD.Language[lng]["Spawn npc"] = "NPC oluştur" +MSD.Language[lng]["Manage do time"] = "Yapım süresini yönet" +MSD.Language[lng]["Spawn vehicle"] = "Araç oluştur" +MSD.Language[lng]["Remove vehicle"] = "Aracı kaldır" +MSD.Language[lng]["Remove all entites"] = "Tüm varlıkları kaldır" +MSD.Language[lng]["Set HP"] = "Can Ayarla" +MSD.Language[lng]["Set Armor"] = "Zırh Ayarla" +MSD.Language[lng]["DarkRP Money"] = "DarkRP Parası" +MSD.Language[lng]["Quest NPCs are disabled"] = "Görev NPC'leri devre dışı" +MSD.Language[lng]["You can enable them in settings"] = "Ayarlardan aktif edebilirsin" \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/sh_config.lua b/addons/msd_ui/lua/msd/sh_config.lua new file mode 100644 index 0000000..fa7eeec --- /dev/null +++ b/addons/msd_ui/lua/msd/sh_config.lua @@ -0,0 +1,150 @@ +-- ┏━┓┏━┳━━━┳━━━┓─────────────────────── +-- ┃┃┗┛┃┃┏━┓┣┓┏┓┃─────────────────────── +-- ┃┏┓┏┓┃┗━━┓┃┃┃┃──By MacTavish <3────── +-- ┃┃┃┃┃┣━━┓┃┃┃┃┃─────────────────────── +-- ┃┃┃┃┃┃┗━┛┣┛┗┛┃─────────────────────── +-- ┗┛┗┛┗┻━━━┻━━━┛─────────────────────── +-- MIT License +-- Copyright (c) 2021 Ayden Mactavish +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +MSD.Config.Language = "en" +MSD.Config.Rounded = 8 +MSD.Config.Blur = false +MSD.Config.Vignette = false +MSD.Config.BgrColor = Color(45, 45, 45) + +MSD.Config.MainColor = { + ["p"] = Color(0, 155, 255), + ["r"] = Color(255, 0, 0), + ["rd"] = Color(220, 0, 0), +} + +MSD.Config.HUD = { + ShowIcon = false, + Icon = "https://i.imgur.com/ND3b6Do.png", + Text = "MacNCo", + X = 0.5, + Y = 0.5, + AlignX = 0, + IconRight = false, + IconSize = 48, + FontSize = 28, + ShowGroup = true, + TeamColor = false, + Follow = true, + Dist = 200, +} + +function MSD.AddModule(name, menu, icon) + local mod = { + name = name, + icon = icon, + menu = menu + } + + local id = MSD.ModuleIds[name] + + if id then + MSD.Modules[id] = mod + else + id = table.insert(MSD.Modules, mod) + MSD.ModuleIds[name] = id + end + + return id +end + +--──────────────────────────────────-- +------------- CFG Saving ------------- +--──────────────────────────────────-- +net.Receive("MSD.GetConfigData", function(l, ply) + if CLIENT then + MSD.Config = net.ReadTable() + else + net.Start("MSD.GetConfigData") + net.WriteTable(MSD.Config) + net.Send(ply) + end +end) + +MSD.SaveConfig = function() + if CLIENT then + local json_data = util.TableToJSON(MSD.Config, false) + local cd = util.Compress(json_data) + local bn = string.len(cd) + net.Start("MSD.SaveConfig") + net.WriteInt(bn, 32) + net.WriteData(cd, bn) + net.SendToServer() + else + net.Start("MSD.GetConfigData") + net.WriteTable(MSD.Config) + net.Broadcast() + file.Write("msd_data/config.txt", util.TableToJSON(MSD.Config, true)) + end +end + +function MSD.LoadConfig() + if CLIENT then + net.Start("MSD.GetConfigData") + net.SendToServer() + else + net.Receive("MSD.SaveConfig", function(l, ply) + if MSD.cfgLastChange and MSD.cfgLastChange > CurTime() then return end + MSD.cfgLastChange = CurTime() + 1 + if not ply:IsSuperAdmin() then return end + local bytes_number = net.ReadInt(32) + local compressed_data = net.ReadData(bytes_number) + local json_data = util.Decompress(compressed_data) + local config = util.JSONToTable(json_data) + MSD.Config = config + MSD.SaveConfig() + end) + + if not file.Exists("msd_data/config.txt", "DATA") then + file.Write("msd_data/config.txt", util.TableToJSON(MSD.Config, true)) + else + local config = util.JSONToTable(file.Read("msd_data/config.txt", "DATA")) + + for k, v in pairs(config) do + if MSD.Config[k] ~= nil then + MSD.Config[k] = v + end + end + + if #player.GetAll() > 0 then + net.Start("MSD.GetConfigData") + net.WriteTable(MSD.Config) + net.Broadcast() + end + end + end +end + +if SERVER then + hook.Add("PostGamemodeLoaded", "MQMSDS.Load.SV", function() + MSD.LoadConfig() + end) +else + hook.Add("InitPostEntity", "MSD.Load.CL", function() + MSD.LoadConfig() + end) +end + +if GAMEMODE then + MSD.LoadConfig() +end \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/sh_language.lua b/addons/msd_ui/lua/msd/sh_language.lua new file mode 100644 index 0000000..3bd4e73 --- /dev/null +++ b/addons/msd_ui/lua/msd/sh_language.lua @@ -0,0 +1,30 @@ +-- ┏━┓┏━┳━━━┳━━━┓─────────────────────── +-- ┃┃┗┛┃┃┏━┓┣┓┏┓┃─────────────────────── +-- ┃┏┓┏┓┃┗━━┓┃┃┃┃──By MacTavish <3────── +-- ┃┃┃┃┃┣━━┓┃┃┃┃┃─────────────────────── +-- ┃┃┃┃┃┃┗━┛┣┛┗┛┃─────────────────────── +-- ┗┛┗┛┗┻━━━┻━━━┛─────────────────────── +local files = file.Find("msd/language/*", "LUA") + +for k, v in ipairs(files) do + if (SERVER) then + include("msd/language/" .. v) + AddCSLuaFile("msd/language/" .. v) + MsgC(Color(174, 0, 255), "[MSD] " .. v .. " language found\n") + else + include("msd/language/" .. v) + end +end + +function MSD.GetPhrase(name, ...) + local lang = MSD.Language[MSD.Config.Language] or MSD.Language["en"] + local prase = lang[name] + + if not prase then + prase = MSD.Language["en"][name] + end + + if not prase then return name end + + return string.format(prase, ...) +end \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/ui/assets.lua b/addons/msd_ui/lua/msd/ui/assets.lua new file mode 100644 index 0000000..465eaf4 --- /dev/null +++ b/addons/msd_ui/lua/msd/ui/assets.lua @@ -0,0 +1,423 @@ +MSD.Icons48 = { + cross = Material("msd/icons/cross.png", "smooth"), + cog = Material("msd/icons/cog.png", "smooth"), + eye = Material("msd/icons/eye.png", "smooth"), + box = Material("mqs/map_markers/b5.png", "smooth"), + box_open = Material("mqs/icons/box_open.png", "smooth"), + layers = Material("msd/icons/layers.png", "smooth"), + layers_plus = Material("msd/icons/layers-plus.png", "smooth"), + layers_remove = Material("msd/icons/layers-remove.png", "smooth"), + briefcase = Material("msd/icons/briefcase.png", "smooth"), + account = Material("msd/icons/account.png", "smooth"), + account_plus = Material("msd/icons/account-plus.png", "smooth"), + account_edit = Material("msd/icons/account-edit.png", "smooth"), + account_multiple = Material("msd/icons/account-multiple.png", "smooth"), + account_convert = Material("msd/icons/account-convert.png", "smooth"), + arrow_up = Material("msd/icons/arrow_up.png", "smooth"), + arrow_down = Material("msd/icons/arrow_down.png", "smooth"), + folder_open = Material("msd/icons/folder-open.png", "smooth"), + file_document = Material("msd/icons/file-document.png", "smooth"), + menu = Material("msd/icons/menu.png", "smooth"), + dot = Material("msd/icons/dot.png", "smooth"), + pencil = Material("msd/icons/pencil.png", "smooth"), + play = Material("msd/icons/play.png", "smooth"), + plus = Material("msd/icons/plus.png", "smooth"), + back = Material("msd/icons/back.png", "smooth"), + calendar_check = Material("msd/icons/calendar-check.png", "smooth"), + playlist_edit = Material("msd/icons/playlist-edit.png", "smooth"), + seal = Material("msd/icons/seal.png", "smooth"), + save = Material("msd/icons/content-save.png", "smooth"), + copy = Material("msd/icons/content-copy.png", "smooth"), + submit = Material("msd/icons/check-decagram.png", "smooth"), + alert = Material("msd/icons/alert-circle.png", "smooth"), + arrow_down_color = Material("msd/icons/arrow_down_color.png", "smooth"), + face_agent = Material("msd/icons/face-agent.png", "smooth"), + swap = Material("msd/icons/swap.png", "smooth"), + search = Material("mqs/map_markers/c4.png", "smooth"), + tools = Material("mqs/map_markers/t1.png", "smooth"), + human_female = Material("msd/icons/human-female.png", "smooth"), + human_male = Material("msd/icons/human-male.png", "smooth"), + human_female_dance = Material("msd/icons/human-female-dance.png", "smooth"), + hand_peace_variant = Material("msd/icons/hand-peace-variant.png", "smooth"), + key = Material("msd/icons/key-variant.png", "smooth"), + key_arrow_right = Material("msd/icons/key-arrow-right.png", "smooth"), + key_link = Material("msd/icons/key-link.png", "smooth"), + key_plus = Material("msd/icons/key-plus.png", "smooth"), + key_remove = Material("msd/icons/key-remove.png", "smooth"), + key_star = Material("msd/icons/key-star.png", "smooth"), + door = Material("msd/icons/door.png", "smooth"), + car = Material("mqs/map_markers/v1.png", "smooth"), + cancel = Material("msd/icons/cancel.png", "smooth"), + reload = Material("msd/icons/reload.png", "smooth"), + reload_alert = Material("msd/icons/reload-alert.png", "smooth"), + heart = Material("msd/icons/cards-heart.png", "smooth"), + heart_outline = Material("msd/icons/cards-heart-outline.png", "smooth"), + heart_broken = Material("msd/icons/heart-broken.png", "smooth"), + heart_flash = Material("msd/icons/heart-flash.png", "smooth"), + skip_to = Material("msd/icons/debug-step-over.png", "smooth"), + food = Material("msd/icons/food.png", "smooth"), + food_off = Material("msd/icons/food-off-outline.png", "smooth"), + food_outline = Material("msd/icons/food-outline.png", "smooth"), + cash = Material("msd/icons/cash.png", "smooth"), + magazine = Material("msd/icons/magazine-pistol.png", "smooth"), + ammo = Material("mqs/icons/ammo.png", "smooth"), + armor = Material("mqs/map_markers/a1.png", "smooth"), + armor_outline = Material("mqs/map_markers/a2.png", "smooth"), + armor_broken = Material("mqs/map_markers/a4.png", "smooth"), + armor_flash = Material("mqs/map_markers/a5.png", "smooth") +} + +MSD.Materials = { + vignette = Material("msd/vignette.png", "smooth"), + gradient = Material("gui/gradient", "smooth"), + gradient_right = Material("msd/gradient_right.png", "smooth"), +} + +MSD.PinPoints = { + [0] = Material("mqs/icons/pin.png", "smooth"), +} + +local files = file.Find("materials/mqs/map_markers/*", "GAME") + +for k, v in pairs(files) do + MSD.PinPoints[k] = Material("mqs/map_markers/" .. v, "smooth") +end + +MSD.ColorPresets = {Color(255, 20, 20), Color(255, 115, 0), Color(210, 255, 0), Color(0, 170, 25), Color(0, 155, 255), Color(0, 100, 200), Color(135, 0, 255), Color(255, 0, 100),} + +MSD.Theme = { + ["d_na"] = Color(25, 25, 26), + ["d"] = Color(0, 5, 10, 165), + ["m"] = Color(0, 5, 10, 120), + ["l"] = Color(0, 5, 10, 85), +} + +MSD.Text = { + ["a"] = Color(150, 150, 150, 200), + ["n"] = Color(150, 150, 150), + ["d"] = Color(220, 220, 220), + ["s"] = Color(235, 235, 235), + ["m"] = Color(245, 245, 245), + ["l"] = Color(255, 255, 255), +} + +local NewFont = surface.CreateFont + +for i = 0, 40 do + NewFont("MSDFont." .. 12 + i, { + font = "AdihausDIN", + extended = true, + size = 12 + i, + weight = 500 + }) +end + +for i = 0, 20 do + NewFont("MSDFontB." .. 16 + i, { + font = "AdihausDIN", + extended = true, + size = 16 + i, + weight = 800 + }) +end + +NewFont("MSDFont.Big", { + font = "AdihausDIN", + extended = true, + size = 45, + weight = 500 +}) + +NewFont("MSDFont.Biger", { + font = "AdihausDIN", + extended = true, + size = 55, + weight = 500 +}) + +function MSD.DrawBG(panel, w, h) + if MSD.Config.Blur then + MSD.Blur(panel, 1, 3, 255, 250 - MSD.Config.BgrColor.r, w, h) + else + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Config.BgrColor) + end +end + +function MSD.DrawTexturedOutlined(x, y, w, h, mat, color, outlinewidth, ocolor) + if isstring(mat) then + mat = Material(mat) + end + + surface.SetMaterial(mat) + surface.SetDrawColor(ocolor) + local steps = ( outlinewidth * 2 ) / 3 + if ( steps < 1 ) then steps = 1 end + + for _x = -outlinewidth, outlinewidth, steps do + for _y = -outlinewidth, outlinewidth, steps do + surface.DrawTexturedRect(x + _x, y + _y, w, h) + end + end + + surface.SetDrawColor(color) + surface.DrawTexturedRect(x, y, w, h) +end + +local cached_mat = {} + +function MSD.DrawTexturedRect(x, y, w, h, mat, color) + if isstring(mat) then + local crc = util.CRC(mat) + if not cached_mat[crc] then + cached_mat[crc] = Material(mat, "smooth") + end + mat = cached_mat[crc] + end + + surface.SetDrawColor(color) + surface.SetMaterial(mat) + surface.DrawTexturedRect(x, y, w, h) +end + +function MSD.DrawTexturedRectRotated(rot, x, y, w, h, mat, color) + if isstring(mat) then + local crc = util.CRC(mat) + if not cached_mat[crc] then + cached_mat[crc] = Material(mat, "smooth") + end + mat = cached_mat[crc] + end + + surface.SetDrawColor(color) + surface.SetMaterial(mat) + surface.DrawTexturedRectRotated(x, y, w, h, rot) +end + +function MSD.ColorAlpha(cl, al) + local new_cl = table.Copy(cl) + new_cl.a = al + return new_cl +end + +local blur = Material("pp/blurscreen") + +function MSD.Blur(panel, inn, density, alpha, back_alpha, w, h) + local x, y = panel:LocalToScreen(0, 0) + surface.SetDrawColor(255, 255, 255, alpha) + surface.SetMaterial(blur) + + for i = 1, 3 do + blur:SetFloat("$blur", (i / inn) * density) + blur:Recompute() + render.UpdateScreenEffectTexture() + + if w and h then + render.SetScissorRect(-x, -y, x + w, y + h, true) + surface.DrawTexturedRect(-x, -y, ScrW(), ScrH()) + render.SetScissorRect(0, 0, 0, 0, false) + else + surface.DrawTexturedRect(-x, -y, ScrW(), ScrH()) + end + end + + if back_alpha and back_alpha > 0 then + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.ColorAlpha(color_black, back_alpha)) + end +end + +-- subUTF8 functions +local function SubStringGetByteCount(str, index) + local curByte = string.byte(str, index) + local byteCount = 1 + + if curByte == nil then + byteCount = 0 + elseif curByte > 0 and curByte <= 127 then + byteCount = 1 + elseif curByte >= 192 and curByte <= 223 then + byteCount = 2 + elseif curByte >= 224 and curByte <= 239 then + byteCount = 3 + elseif curByte >= 240 and curByte <= 247 then + byteCount = 4 + end + + return byteCount +end + +local function SubStringGetTotalIndex(str) + local curIndex = 0 + local i = 1 + local lastCount = 1 + repeat + lastCount = SubStringGetByteCount(str, i) + i = i + lastCount + curIndex = curIndex + 1 + until (lastCount == 0) + + return curIndex - 1 +end + +local function SubStringGetTrueIndex(str, index) + local curIndex = 0 + local i = 1 + local lastCount = 1 + repeat + lastCount = SubStringGetByteCount(str, i) + i = i + lastCount + curIndex = curIndex + 1 + until (curIndex >= index) + + return i - lastCount +end + +function string.subUTF8(str, startIndex, endIndex) + if startIndex < 0 then + startIndex = SubStringGetTotalIndex(str) + startIndex + 1 + end + + if endIndex ~= nil and endIndex < 0 then + endIndex = SubStringGetTotalIndex(str) + endIndex + 1 + end + + if endIndex == nil then + return string.sub(str, SubStringGetTrueIndex(str, startIndex)) + else + return string.sub(str, SubStringGetTrueIndex(str, startIndex), SubStringGetTrueIndex(str, endIndex + 1) - 1) + end +end + +-- Same used in DarkRP, used it here so we can use it with any gamemodes +local function CharWrap(t, w) + local a = 0 + + t = t:gsub(".", function(c) + a = a + surface.GetTextSize(c) + + if a >= w then + a = 0 + + return "\n" .. c + end + + return c + end) + + return t, a +end + +function MSD.TextWrap(text, font, w) + local total = 0 + surface.SetFont(font) + local spaceSize = surface.GetTextSize(' ') + + text = text:gsub("(%s?[%S]+)", function(word) + local char = string.subUTF8(word, 1, 1) + + if char == "\n" or char == "\t" then + total = 0 + end + + local wordlen = surface.GetTextSize(word) + total = total + wordlen + + if wordlen >= w then + local splitWord, splitPoint = CharWrap(word, w - (total - wordlen)) + total = splitPoint + + return splitWord + elseif total < w then + return word + end + + if char == ' ' then + total = wordlen - spaceSize + + return '\n' .. string.subUTF8(word, 2) + end + + total = wordlen + + return '\n' .. word + end) + + local w_end, h_end = surface.GetTextSize(text) + + return text, w_end, h_end +end + +-- Image Lib +MSD.ImgLib = {} +MSD.ImgLib.Images = {} +MSD.ImgLib.PreCacheStarted = {} +MSD.ImgLib.Avatars = {} +MSD.ImgLib.NoMaterial = Material("msd/icons/file-hidden.png", "smooth noclamp") + +function MSD.ImgLib.GetMaterial(url, jpg) + if url == "" then + return MSD.ImgLib.NoMaterial + end + + local crc = util.CRC(url) .. ".png" + + if jpg then + crc = util.CRC(url) .. ".jpg" + end + + if MSD.ImgLib.Images[crc] then return MSD.ImgLib.Images[crc] end + + if (file.Exists("msd_imgs/" .. crc, "DATA")) then + MSD.ImgLib.Images[crc] = Material("data/msd_imgs/" .. crc, "smooth noclamp") + + return MSD.ImgLib.Images[crc] + else + return MSD.ImgLib.PreCacheMaterial(url, crc, jpg) + end +end + +function MSD.ImgLib.PreCacheMaterial(url, crc, jpg) + if not crc then + crc = util.CRC(url) .. ".png" + end + + if not file.Exists("msd_imgs", "DATA") then + file.CreateDir("msd_imgs") + end + + if not MSD.ImgLib.PreCacheStarted[crc] then + MSD.ImgLib.PreCacheStarted[crc] = true + + http.Fetch(url, function(body, size, headers, code) + if not jpg and (body:find("^.PNG")) then + file.Write("msd_imgs/" .. crc, body) + MSD.ImgLib.Images[crc] = Material("data/msd_imgs/" .. crc, "smooth noclamp") + + return MSD.ImgLib.Images[crc] + elseif jpg then + file.Write("msd_imgs/" .. crc, body) + MSD.ImgLib.Images[crc] = Material("data/msd_imgs/" .. crc, "smooth noclamp") + else + print("Image is not a PNG, url - " .. url) + end + end, function() + print("Failed to get image, url - " .. url) + end) + end + + return MSD.ImgLib.NoMaterial +end + +function MSD.ImgLib.GetAvatar(crc) + crc = tostring(crc) + if not MSD.ImgLib.Avatars[crc] then + MSD.ImgLib.Avatars[crc] = "" + + http.Fetch("https://macnco.one/steamid/avatar.php?input=" .. crc, function(body, size, headers, code) + MSD.ImgLib.Avatars[crc] = body + end, function() + print("Failed to get link, url - " .. url) + end) + end + + return MSD.ImgLib.Avatars[crc] or "" +end \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/ui/elements.lua b/addons/msd_ui/lua/msd/ui/elements.lua new file mode 100644 index 0000000..e075d19 --- /dev/null +++ b/addons/msd_ui/lua/msd/ui/elements.lua @@ -0,0 +1,1989 @@ +function MSD.WorkSpacePanel(parent, title, wd, hd, a_ignore) + if not wd or not hd then + wd, hd = 1.1, 1.3 + end + + local panel = vgui.Create("DPanel") + + if not a_ignore then + panel:SetAlpha(0) + panel:AlphaTo(255, 0.3) + end + + panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + MSD.Blur(self, 3, 5, 255, 50, w, h) + end + + panel.Close = function() + panel:AlphaTo(0, 0.3, 0, function() + panel:Remove() + end) + end + + panel.PerformLayout = function(self) + local children = self:GetChildren() + + for k, v in pairs(children) do + v:InvalidateLayout() + end + end + + parent:AddToWorkSpace(panel) + local child = vgui.Create("DPanel", panel) + child:SetSize(math.Clamp(panel:GetWide() / wd, 500, 900), panel:GetTall() / hd) + child:Center() + + child.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + draw.DrawText(title, "MSDFont.25", 10, 10, color_white, TEXT_ALIGN_LEFT) + end + + child.PerformLayout = function(self) + child:Center() + + if child.clsBut then + child.clsBut:SetPos(child:GetWide() - 38, 5) + end + end + + child.clsBut = MSD.IconButton(child, MSD.Icons48.cross, child:GetWide() - 34, 10, 25, nil, MSD.Config.MainColor["r"], function() + panel.Close() + end) + + return panel, child +end + +function MSD.IconButton(parent, mat, x, y, s, color, color2, func) + local button = vgui.Create("DButton") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = s, + fixed_h = s, + minw = s, + minh = s + } + end + + button:SetSize(s, s) + button:SetText("") + button.hovered = false + button.alpha = 0 + button.mat = mat + + button.Paint = function(self, w, h) + if self.hover or self.hovered then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + MSD.DrawTexturedRect(0, 0, w, h, self.mat, MSD.ColorAlpha(color or MSD.Text.l, 255 - self.alpha * 255)) + + if self.alpha > 0 then + MSD.DrawTexturedRect(0, 0, w, h, self.mat, MSD.ColorAlpha(color2 or MSD.Config.MainColor["p"], self.alpha * 255)) + end + + return true + end + + button.DoClick = func + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoRightClick = func + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.IconButtonText(parent, text, mat, x, y, s, color, color2, func) + local button = vgui.Create("DButton") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = s * 2, + fixed_h = s + 32, + minw = s * 2, + minh = s + 16 + } + end + + button:SetSize(s, s + 32) + button:SetText(text) + button.hovered = false + button.alpha = 0 + button.mat = mat + + button.Paint = function(self, w, h) + if self.hover or self.hovered then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + MSD.DrawTexturedRect(w / 2 - s / 2, 0, s, s, self.mat, MSD.ColorAlpha(color or MSD.Text.d, 255 - self.alpha * 255)) + + if self.alpha > 0 then + MSD.DrawTexturedRect(w / 2 - s / 2, 0, s, s, self.mat, MSD.ColorAlpha(color2 or MSD.Config.MainColor["p"], self.alpha * 255)) + end + + draw.DrawText(MSD.TextWrap(self:GetText(), "MSDFont.16", w - 4), "MSDFont.16", w / 2, s, color or MSD.Text.d, TEXT_ALIGN_CENTER) + + return true + end + + button.DoClick = func + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoRightClick = func + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.IconButtonBG(parent, mat, x, y, s, color, color2, func) + local button = vgui.Create("DButton") + button:SetSize(s, s) + + if x then + button:SetParent(parent) + button:SetPos(x, y) + end + + button:SetText("") + button.hovered = false + button.alpha = 0 + button.mat = mat + + button.Paint = function(self, w, h) + if self.hover or self.hovered then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme.d) + + if self.alpha > 0 then + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.ColorAlpha(MSD.Config.MainColor["p"], 255 * self.alpha)) + end + + MSD.DrawTexturedRect(w / 2 - 12, h / 2 - 12, 24, 24, self.mat, color_white) + + return true + end + + button.DoClick = func + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoRightClick = func + + if not x then + parent:AddItem(button) + end + + return button +end + +function MSD.MenuButton(parent, mat, x, y, sw, sh, text, func, rfunc, small) + local button = vgui.Create("DButton") + button:SetSize(sw, sh) + + if x then + button:SetParent(parent) + button:SetPos(x, y) + end + + button:SetText("") + button.hovered = false + button.alpha = 0 + button.mat = mat + + button.Paint = function(self, w, h) + local icon_size = small and 16 or h - 20 + if self.hovered then + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + end + + if self.hover then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + local rf = MSD.Config.Rounded + if self.alpha > 0.01 then + draw.RoundedBox(rf, rf, rf, math.max((w - rf * 2) * self.alpha, icon_size + 12 - rf), h - rf * 2, MSD.ColorAlpha(MSD.Config.MainColor["p"], 255 * self.alpha)) + end + + MSD.DrawTexturedRect(small and h / 2 - rf or 10, small and h / 2 - rf or 10, icon_size, icon_size, self.mat, color_white) + draw.DrawText(text, "MSDFont.22", 55, 12, color_white, TEXT_ALIGN_LEFT) + + return true + end + + button.DoClick = func + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoRightClick = func + + if not x then + parent:AddItem(button) + end + + return button +end + +function MSD.MenuButtonTop(parent, mat, x, y, sw, sh, text, func, rfunc, small) + local button = vgui.Create("DButton") + + if sw == "auto" and text ~= "" then + surface.SetFont("MSDFont.22") + local tw = surface.GetTextSize(text) + button:SetSize(70 + tw, sh) + elseif sw == "auto" and text == "" then + button:SetSize(sh, sh) + else + button:SetSize(sw, sh) + end + + if x then + button:SetParent(parent) + button:SetPos(x, y) + end + + button:SetText("") + button.hovered = false + button.alpha = 0 + button.mat = mat + + button.Paint = function(self, w, h) + local icon_size = small and 16 or h - 20 + local rf = MSD.Config.Rounded + + if self.hovered then + draw.RoundedBox(rf, 0, 0, w, h, MSD.Theme["d"]) + end + + if self.hover then + self.alpha = Lerp(FrameTime() * 7, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 7, self.alpha, 0) + end + + if self.alpha > 0.01 then + draw.RoundedBoxEx(rf, rf, h - 5, w - rf * 2, 5 + rf, MSD.ColorAlpha(MSD.Config.MainColor["p"], 255 * self.alpha), true, true, false, false) + end + + MSD.DrawTexturedRect(small and h / 2 - rf or 10, small and h / 2 - rf or 10, icon_size, icon_size, self.mat, color_white) + draw.DrawText(text, "MSDFont.22", 55, 12, color_white, TEXT_ALIGN_LEFT) + + return true + end + + button.DoClick = func + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoRightClick = func + + if not x then + parent:AddItem(button) + end + + return button +end + +function MSD.Header(parent, text, back, icon, align) + local panel = vgui.Create("DPanel") + + panel.StaticScale = { + w = 1, + fixed_h = 50, + minw = 250, + minh = 50 + } + + panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["l"]) + draw.DrawText(text, "MSDFont.25", align and 50 or w / 2, 12, color_white, align and TEXT_ALIGN_LEFT or TEXT_ALIGN_CENTER) + end + + parent:AddItem(panel) + + if back then + MSD.IconButton(panel, icon or MSD.Icons48.back, 13, 13, 24, nil, nil, back) + end + + return panel +end + +function MSD.InfoHeader(parent, text, wd) + local panel = vgui.Create("DPanel") + wd = wd or 1 + panel.StaticScale = { + w = wd, + fixed_h = 25, + minw = 250, + minh = 25 + } + + panel.Paint = function(self, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["l"]) + draw.DrawText(text, "MSDFont.20", 5, h / 2 - 11, color_white, TEXT_ALIGN_LEFT) + end + + parent:AddItem(panel) + + return panel +end + +function MSD.InfoText(parent, text) + local panel = vgui.Create("DPanel") + + panel.StaticScale = { + w = 1, + fixed_h = 25, + minw = 250, + minh = 25 + } + + panel.Paint = function(self, w, h) + local ts, _, th = MSD.TextWrap(text, "MSDFont.18", w - 10) + draw.DrawText(ts, "MSDFont.18", 5, 5, MSD.Text.d, TEXT_ALIGN_LEFT) + + if th > h then + self.StaticScale.fixed_h = th + 10 + end + end + + parent:AddItem(panel) + + return panel +end + +function MSD.TextEntry(parent, x, y, w, h, text, label, value, func, auto_update, focuse_update, multi, num) + local Entry = vgui.Create("DTextEntry") + + if x and y then + Entry:SetParent(parent) + Entry:SetPos(x, y) + end + + if x == "static" then + Entry.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + Entry:SetSize(w, h) + end + + Entry:SetNumeric(num or false) + Entry:SetText("") + Entry:SetFont("MSDFont.22") + Entry:SetMultiline(multi or false) + Entry.alpha = 0 + Entry:SetDrawLanguageID(false) + + Entry.Paint = function(self, wd, hd) + if self:HasFocus() then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 255) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + local rf = MSD.Config.Rounded + draw.RoundedBox(rf, 0, 0, wd, hd, MSD.Theme["l"]) + draw.RoundedBox(0, rf, hd - 1, wd - rf * 2, 1, MSD.ColorAlpha(MSD.Text["n"], 255 - self.alpha)) + draw.RoundedBox(0, rf, hd - 1, wd - rf * 2, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha)) + + if self:GetValue() == "" then + draw.SimpleText(text, "MSDFont.22", 3, multi and 1 or hd / 2 - 10, MSD.ColorAlpha(MSD.Text["d"], 120), TEXT_ALIGN_LEFT) + end + + if label and not self.error then + draw.SimpleText(label, "MSDFont.16", 3, 0, MSD.ColorAlpha(MSD.Text["d"], 120), TEXT_ALIGN_LEFT) + end + + if self.error then + draw.SimpleText(self.error, "MSDFont.16", 3, 0, MSD.Config.MainColor["r"], TEXT_ALIGN_LEFT) + end + + self:DrawTextEntryText(self.error and MSD.Config.MainColor["rd"] or MSD.Text["l"], MSD.Config.MainColor["p"], MSD.Text["d"]) + end + + Entry.OnEnter = func + + if focuse_update then + Entry.OnFocusChanged = function(self, gained) + if not gained then + func(self, self:GetValue()) + end + end + end + + Entry:SetText(value or "") + + if auto_update then + Entry:SetUpdateOnType(true) + + function Entry:OnValueChange(vl) + if func then + func(self, vl) + end + end + end + + if not x or not y then + parent:AddItem(Entry) + end + + return Entry +end + +function MSD.VectorDisplay(parent, x, y, w, h, text, vector, func) + local Entry = vgui.Create("DButton") + Entry:SetText("") + if x and y then + Entry:SetParent(parent) + Entry:SetPos(x, y) + end + + if x == "static" then + Entry.StaticScale = { w = w, fixed_h = h, minw = 50, minh = h } + else + Entry:SetSize(w, h) + end + Entry.vector = vector or Vector(0, 0, 0) + Entry.Paint = function(self, sw, sh) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, sw, sh, MSD.Theme["l"]) + draw.RoundedBox(0, MSD.Config.Rounded, sh - 1, sw - MSD.Config.Rounded * 2, 1, MSD.Text["n"]) + + if text then + draw.SimpleText(text, "MSDFont.16", 3, 0, MSD.ColorAlpha(MSD.Text["d"], 120), TEXT_ALIGN_LEFT) + end + + draw.SimpleText("x: " .. self.vector.x .. " y: " .. self.vector.y .. " z: " .. self.vector.z, "MSDFont.22", 3, h / 2 - 10, MSD.Text["d"], TEXT_ALIGN_LEFT) + end + Entry.DoClick = function(self) + + if self.rebuild or self.disabled then return end + self.rebuild = true + + if IsValid(self.cpanel) then + self.cpanel:Remove() + self.cpanel = nil + self:SizeTo(self:GetWide(), h, 0.2, 0, -1, function() + Entry.StaticScale = { w = w, fixed_h = h, minw = 50, minh = h} + parent:Rebuild() + self.rebuild = nil + end) + return + end + + Entry.StaticScale = { + w = w, + fixed_h = h + 50, + minw = 50, + minh = h + 50 + } + parent:Rebuild() + self:SetSize(self:GetWide(), h) + self:SizeTo(self:GetWide(), h + 50, 0.2, 0, -1, function() + self.rebuild = nil + end) + local mpw = self:GetWide() + self.cpanel = vgui.Create("DPanel", self) + self.cpanel:SetSize(mpw, 50) + self.cpanel:SetPos(0, h) + self.cpanel.Paint = function() end + + self.x = MSD.TextEntry(self.cpanel, 0, 0, mpw / 3, 45, "", "X", self.vector.x, function(sp, value) + value = tonumber(value) or 0 + self.vector.x = value + func(self.vector, self) + end, true, nil, false, true) + + self.y = MSD.TextEntry(self.cpanel, mpw / 3, 0, mpw / 3, 45, "", "Y", self.vector.y, function(sp, value) + value = tonumber(value) or 0 + self.vector.y = value + func(self.vector, self) + end, true, nil, false, true) + + self.z = MSD.TextEntry(self.cpanel, mpw - mpw / 3, 0, mpw / 3, 45, "", "Z", self.vector.z, function(sp, value) + value = tonumber(value) or 0 + self.vector.z = value + func(self.vector, self) + end, true, nil, false, true) + end + + if not x or not y then + parent:AddItem(Entry) + end + + return Entry +end + +function MSD.AngleDisplay(parent, x, y, w, h, text, angle, func) + local Entry = vgui.Create("DButton") + Entry:SetText("") + if x and y then + Entry:SetParent(parent) + Entry:SetPos(x, y) + end + + if x == "static" then + Entry.StaticScale = { w = w, fixed_h = h, minw = 50, minh = h } + else + Entry:SetSize(w, h) + end + + Entry.angle = angle or Angle(0, 0, 0) + Entry.Paint = function(self, sw, sh) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, sw, h, MSD.Theme["l"]) + draw.RoundedBox(0, MSD.Config.Rounded, sh - 1, sw - MSD.Config.Rounded * 2, 1, MSD.Text["n"]) + + if text then + draw.SimpleText(text, "MSDFont.16", 3, 0, MSD.ColorAlpha(MSD.Text["d"], 120), TEXT_ALIGN_LEFT) + end + + draw.SimpleText("p: " .. self.angle.p .. " y: " .. self.angle.y .. " r: " .. self.angle.r, "MSDFont.22", 3, h / 2 - 10, MSD.Text["d"], TEXT_ALIGN_LEFT) + end + Entry.DoClick = function(self) + + if self.rebuild or self.disabled then return end + self.rebuild = true + + if IsValid(self.cpanel) then + self.cpanel:Remove() + self.cpanel = nil + self:SizeTo(self:GetWide(), h, 0.2, 0, -1, function() + Entry.StaticScale = { w = w, fixed_h = h, minw = 50, minh = h} + parent:Rebuild() + self.rebuild = nil + end) + return + end + + Entry.StaticScale = { + w = w, + fixed_h = h + 50, + minw = 50, + minh = h + 50 + } + parent:Rebuild() + self:SetSize(self:GetWide(), h) + self:SizeTo(self:GetWide(), h + 50, 0.2, 0, -1, function() + self.rebuild = nil + end) + local mpw = self:GetWide() + self.cpanel = vgui.Create("DPanel", self) + self.cpanel:SetSize(mpw, 50) + self.cpanel:SetPos(0, h) + self.cpanel.Paint = function() end + + self.x = MSD.TextEntry(self.cpanel, 0, 0, mpw / 3, 45, "", "X", self.angle.p, function(sp, value) + value = tonumber(value) or 0 + self.angle.p = value + func(self.angle, self) + end, true, nil, false, true) + + self.y = MSD.TextEntry(self.cpanel, mpw / 3, 0, mpw / 3, 45, "", "Y", self.angle.y, function(sp, value) + value = tonumber(value) or 0 + self.angle.y = value + func(self.angle, self) + end, true, nil, false, true) + + self.z = MSD.TextEntry(self.cpanel, mpw - mpw / 3, 0, mpw / 3, 45, "", "Z", self.angle.r, function(sp, value) + value = tonumber(value) or 0 + self.angle.r = value + func(self.angle, self) + end, true, nil, false, true) + end + + if not x or not y then + parent:AddItem(Entry) + end + + return Entry +end + +function MSD.ColorSelectBut(parent, x, y, w, h, color, func) + local button = vgui.Create("DButton") + button:SetText("") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 10, + minh = h + } + else + button:SetSize(w, h) + end + + button.alpha = 0 + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, color) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + return true + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + func(self) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.Binder(parent, x, y, w, h, text, var, func) + local binder = vgui.Create("DBinder") + + if x and y then + binder:SetParent(parent) + binder:SetPos(x, y) + end + + if x == "static" then + binder.StaticScale = { + w = w, + fixed_h = h, + minw = 150, + minh = h + } + else + binder:SetSize(w, h) + end + + binder:SetValue(var) + binder.alpha = 0 + + binder.Paint = function(self, wd, hd) + local rf = MSD.Config.Rounded + draw.RoundedBox(rf, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered or self.Trapping) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(text, "MSDFont.22", 5, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(text, "MSDFont.22", 5, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(string.upper(self:GetText()), "MSDFont.22", wd - wd / 3 / 2, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_CENTER) + draw.DrawText(string.upper(self:GetText()), "MSDFont.22", wd - wd / 3 / 2, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_CENTER) + draw.RoundedBox(0, rf, hd - 1, (wd / 3) * 2 - 5 - rf, 1, MSD.ColorAlpha(MSD.Text["n"], 255 - self.alpha * 255)) + draw.RoundedBox(0, rf, hd - 1, (wd / 3) * 2 - 5 - rf, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255)) + draw.RoundedBox(0, wd - wd / 3, hd - 1, wd / 3 - rf, 1, MSD.ColorAlpha(MSD.Text["n"], 255 - self.alpha * 255)) + draw.RoundedBox(0, wd - wd / 3, hd - 1, wd / 3 - rf, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255)) + + return true + end + + binder.OnCursorEntered = function(self) + self.hover = true + end + + binder.OnCursorExited = function(self) + self.hover = false + end + + function binder:OnChange(num) + if num > 106 and num < 114 then + binder:SetValue(var) + else + func(num) + end + end + + if not x or not y then + parent:AddItem(binder) + end +end + +function MSD.ButtonScr(parent, x, y, w, h, text, func, al_left) + local button = vgui.Create("DButton") + button:SetText(text) + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + h_w = true, + minw = 150 + } + else + button:SetSize(w, h) + end + + button.alpha = 0 + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(MSD.TextWrap(self:GetText(), "MSDFont.18", w - 20), "MSDFont.18", al_left and 5 or wd / 2, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), al_left and TEXT_ALIGN_LEFT or TEXT_ALIGN_CENTER) + draw.DrawText(MSD.TextWrap(self:GetText(), "MSDFont.18", w - 20), "MSDFont.18", al_left and 5 or wd / 2, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), al_left and TEXT_ALIGN_LEFT or TEXT_ALIGN_CENTER) + draw.RoundedBox(MSD.Config.Rounded, 0, hd - 1, wd, 1, MSD.ColorAlpha(MSD.Text["l"], 255 - self.alpha * 255)) + draw.RoundedBox(MSD.Config.Rounded, 0, hd - 1, wd, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255)) + + return true + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + func(self) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.Button(parent, x, y, w, h, text, func, al_left) + local button = vgui.Create("DButton") + button:SetText(text) + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 150, + minh = h + } + else + button:SetSize(w, h) + end + + button.alpha = 0 + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(self:GetText(), "MSDFont.22", al_left and 5 or wd / 2, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), al_left and TEXT_ALIGN_LEFT or TEXT_ALIGN_CENTER) + draw.DrawText(self:GetText(), "MSDFont.22", al_left and 5 or wd / 2, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), al_left and TEXT_ALIGN_LEFT or TEXT_ALIGN_CENTER) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(MSD.Text["n"], 255 - self.alpha * 255)) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255)) + + return true + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + func(self) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.ButtonSimple(parent, x, y, w, h, text, fsize, func) + local button = vgui.Create("DButton") + button:SetText(text) + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 150, + minh = h + } + else + button:SetSize(w, h) + end + + button.Paint = function(self, wd, hd) + if self.Check and self.Check() and not self.disabled then + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["d"]) + end + + if (self.hover or self.hovered) and not self.disabled then + draw.DrawText(self:GetText(), "MSDFont." .. fsize, 5, hd / 2 - fsize / 2, MSD.Config.MainColor["p"], TEXT_ALIGN_LEFT) + else + draw.DrawText(self:GetText(), "MSDFont." .. fsize, 5, hd / 2 - fsize / 2, self.disabled and MSD.Text["n"] or MSD.Text["s"], TEXT_ALIGN_LEFT) + end + return true + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + func(self) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.NumberWang(parent, x, y, w, h, min, max, val, label, func) + local button = vgui.Create("DNumberWang") + button:SetValue(val) + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.alpha = 0 + button:SetFont("MSDFont.22") + button:SetMin(min) + button:SetMax(max) + + button.Paint = function(self, wd, hd) + if self:HasFocus() then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 255) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(MSD.Text["n"], 255 - self.alpha)) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha)) + + if label and not self.error then + draw.SimpleText(label, "MSDFont.16", 3, 0, MSD.ColorAlpha(MSD.Text["d"], 120), TEXT_ALIGN_LEFT) + end + + if self.error then + draw.SimpleText(self.error, "MSDFont.16", 3, 0, MSD.Config.MainColor["r"], TEXT_ALIGN_LEFT) + end + + self:DrawTextEntryText(self.error and MSD.Config.MainColor["rd"] or MSD.Text["l"], MSD.Config.MainColor["p"], MSD.Text["d"]) + + return true + end + + button.OnValueChanged = function(self) + func(self) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.ButtonIcon(parent, x, y, w, h, text, icon, func, func2, color, color2, drawf) + local button = vgui.Create("DButton") + button:SetText(text) + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.alpha = 0 + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if drawf then drawf(self, wd, hd) end + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(self:GetText(), "MSDFont.22", 48, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(self:GetText(), "MSDFont.22", 48, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(color or MSD.Text["n"], 255 - self.alpha * 255)) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(color2 or MSD.Config.MainColor["p"], self.alpha * 255)) + MSD.DrawTexturedRect(12, hd / 2 - 12, 24, 24, icon, MSD.ColorAlpha(color or MSD.Text["l"], 255 - self.alpha * 255)) + MSD.DrawTexturedRect(12, hd / 2 - 12, 24, 24, icon, MSD.ColorAlpha(color2 or MSD.Config.MainColor["p"], self.alpha * 255)) + + return true + end + + if func then + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + func(self) + end + end + + if func2 then + button.DoRightClick = function(self) + func2(self) + end + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.ButtonIconText(parent, x, y, w, h, text, text2, icon, func, func2, color) + local button = vgui.Create("DButton") + button:SetText(text) + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.alpha = 0 + button.text = text2 + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(self.text, "MSDFont.22", wd - 5, hd / 2 - 11, self.disabled and MSD.Text["n"] or MSD.Text["s"], TEXT_ALIGN_RIGHT) + draw.DrawText(self:GetText(), "MSDFont.22", 48, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(self:GetText(), "MSDFont.22", 48, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(color or MSD.Text["n"], 255 - self.alpha * 255)) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255)) + MSD.DrawTexturedRect(12, hd / 2 - 12, 24, 24, icon, MSD.ColorAlpha(color or MSD.Text["l"], 255 - self.alpha * 255)) + MSD.DrawTexturedRect(12, hd / 2 - 12, 24, 24, icon, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255)) + + return true + end + + if func then + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + func(self) + end + end + + if func2 then + button.DoRightClick = function(self) + func2(self) + end + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.VolumeSlider(parent, x, y, w, h, text, var, func, cl) + local button = vgui.Create("DButton") + button:SetText("") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.var = var or 1 + button.value = var or 1 + button.alpha = 0 + button.disabled = false + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(text, "MSDFont.22", 3, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(text, "MSDFont.22", 3, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + self.var = Lerp(FrameTime() * 7, self.var, self.value) + draw.RoundedBox(MSD.Config.Rounded, wd - wd / 2 + 10, hd / 2 - 10, wd / 2 - 20, 20, MSD.Theme["d"]) + + if self.disabled then + draw.DrawText(MSD.GetPhrase("disabled"), "MSDFont.16", wd - (wd / 2) / 2, hd / 2 - 8, MSD.Text["n"], TEXT_ALIGN_CENTER) + else + draw.RoundedBox(MSD.Config.Rounded, wd - wd / 2 + 10, hd / 2 - 10, math.max((wd / 2 - 19) * self.var, 16), 20, cl or MSD.Config.MainColor["p"]) + draw.DrawText(math.Round(self.value * 100) .. "%", "MSDFont.16", wd - (wd / 2) / 2, hd / 2 - 8, MSD.Text["s"], TEXT_ALIGN_CENTER) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + if self.disabled then return end + local wd = self:GetWide() + local mx, my = gui.MousePos() + mx, my = self:ScreenToLocal(mx, my) + + if mx < wd - wd / 2 + 10 then + self.value = 0 + elseif mx > wd - 10 then + self.value = 1 + else + mx = mx - ((wd - wd / 2) + 10) + mx = mx / ((wd / 2) - 20) + self.value = mx + end + + self.value = math.Round(self.value, 2) + func(self, self.value) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.VolumeScale(parent, x, y, w, h, text, var, func, cl) + local button = vgui.Create("DButton") + button:SetText("") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.var = var or 1 + button.value = var or 1 + button.alpha = 0 + button.disabled = false + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(text, "MSDFont.22", 3, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(text, "MSDFont.22", 3, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + self.var = Lerp(FrameTime() * 7, self.var, self.value) + draw.RoundedBox(MSD.Config.Rounded, wd - wd / 2 + 10, hd / 2 - 10, wd / 2 - 20, 20, MSD.Theme["d"]) + + if self.disabled then + draw.DrawText(MSD.GetPhrase("disabled"), "MSDFont.16", wd - (wd / 2) / 2, hd / 2 - 8, MSD.Text["n"], TEXT_ALIGN_CENTER) + else + draw.RoundedBox(MSD.Config.Rounded, wd - wd / 2 + 10, hd / 2 - 10, (wd / 2 - 19) * ( self.var / 2), 20, cl or MSD.Config.MainColor["p"]) + draw.DrawText(math.Round(self.value * 100) .. "%", "MSDFont.16", wd - (wd / 2) / 2, hd / 2 - 8, MSD.Text["s"], TEXT_ALIGN_CENTER) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + if self.disabled then return end + local wd = self:GetWide() + local mx, my = gui.MousePos() + mx, my = self:ScreenToLocal(mx, my) + + if mx < wd - wd / 2 + 10 then + self.value = 1 + elseif mx > wd - 10 then + self.value = 2 + else + mx = mx - ((wd - wd / 2) + 10) + mx = mx / ((wd / 2) - 20) * 2 + self.value = math.Clamp(mx, 0.01, 2) + end + + self.value = math.Round(self.value, 2) + func(self, self.value) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.BoolSlider(parent, x, y, w, h, text, var, func) + local button = vgui.Create("DButton") + button:SetText("") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.var = var or false + button.pos = var and 1 or 0 + button.alpha = 0 + button.disabled = false + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(text, "MSDFont.22", 3, hd / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(text, "MSDFont.22", 3, hd / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + + if self.var then + self.pos = Lerp(0.1, self.pos, 1) + else + self.pos = Lerp(0.1, self.pos, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, wd - 75, hd / 2 - 10, 68, 20, MSD.Theme["d"]) + + if self.disabled then + draw.DrawText(MSD.GetPhrase("disabled"), "MSDFont.16", wd - 40, hd / 2 - 8, MSD.Text["n"], TEXT_ALIGN_CENTER) + else + draw.DrawText(MSD.GetPhrase("off"), "MSDFont.16", wd - 25, hd / 2 - 8, MSD.ColorAlpha(MSD.Text["s"], 255 - self.pos * 255), TEXT_ALIGN_CENTER) + draw.DrawText(MSD.GetPhrase("on"), "MSDFont.16", wd - 60, hd / 2 - 8, MSD.ColorAlpha(MSD.Text["s"], self.pos * 255), TEXT_ALIGN_CENTER) + draw.RoundedBox(MSD.Config.Rounded, wd - 75 + self.pos * 35, hd / 2 - 10, 34, 20, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.pos * 255)) + draw.RoundedBox(MSD.Config.Rounded, wd - 75 + self.pos * 35, hd / 2 - 10, 34, 20, MSD.ColorAlpha(MSD.Text["n"], 255 - self.pos * 255)) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + if self.disabled then return end + self.var = not self.var + func(self, self.var) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.DTextSlider(parent, x, y, w, h, text1, text2, var, func) + local button = vgui.Create("DButton") + button:SetText("") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.var = var or false + button.pos = var and 1 or 0 + button.alpha = 0 + button.disabled = false + + button.Paint = function(self, wd, hd) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(self.var and text1 or text2, "MSDFont.22", 3, hd / 2 - 10, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(self.var and text1 or text2, "MSDFont.22", 3, hd / 2 - 10, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + + if self.var then + self.pos = Lerp(0.1, self.pos, 1) + else + self.pos = Lerp(0.1, self.pos, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, wd - 75, hd / 2 - 10, 68, 20, MSD.Theme["d"]) + draw.RoundedBox(MSD.Config.Rounded, wd - 75 + self.pos * 35, hd / 2 - 10, 34, 20, MSD.Config.MainColor["p"]) + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + if self.disabled then return end + self.var = not self.var + func(self, self.var) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.ComboBox(parent, x, y, w, h, label, val) + local ComboBox = vgui.Create("DComboBox") + + if x and y then + ComboBox:SetParent(parent) + ComboBox:SetPos(x, y) + end + + if x == "static" then + ComboBox.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + ComboBox:SetSize(w, h) + end + + ComboBox:SetValue(val) + ComboBox:SetFont("MSDFont.22") + ComboBox.alpha = 0 + ComboBox.disabled = false + ComboBox:SetTextColor(MSD.Text["s"]) + + ComboBox.Paint = function(self, wd, hd) + if ( self:IsMenuOpen() or self.pressed ) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 255) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["l"]) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha)) + draw.RoundedBox(0, MSD.Config.Rounded, hd - 1, wd - MSD.Config.Rounded * 2, 1, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha)) + draw.SimpleText(self:GetText(), "MSDFont.22", 3, hd / 2 - 10, self.disabled and MSD.Text["n"] or MSD.Text["d"], TEXT_ALIGN_LEFT) + + if label and not self.error then + draw.SimpleText(label, "MSDFont.16", 3, 0, MSD.ColorAlpha(MSD.Text["d"], 120), TEXT_ALIGN_LEFT) + end + + return true + end + + ComboBox.OnCursorEntered = function(self) + self.pressed = true + end + + ComboBox.OnCursorExited = function(self) + self.pressed = false + end + + function ComboBox:OpenMenu(pControlOpener) + if (pControlOpener and pControlOpener == self.TextEntry) then return end + if (self.disabled) then return end + if (#self.Choices == 0) then return end + + if (IsValid(self.Menu)) then + self.Menu:Remove() + self.Menu = nil + end + + self.Menu = MSD.MenuOpen(false, self) + + for k, v in pairs(self.Choices) do + self.Menu:AddOption(v, function() + self:ChooseOption(v, k) + end) + end + + local mx, my = self:LocalToScreen(0, self:GetTall()) + self.Menu:SetMinimumWidth(self:GetWide()) + self.Menu:Open(mx, my, false, self) + end + + if not x or not y then + parent:AddItem(ComboBox) + end + + return ComboBox +end + +function MSD.BigButton(parent, x, y, w, h, text, icon, func, color, text2, func2, text3, func3) + local button = vgui.Create("DButton") + button:SetText("") + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + + button.alpha = 0 + button.color_idle = color_white + + button.Paint = function(self, wd, hd) + if self.hover and not self.disable then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, wd, hd, MSD.Theme["d"]) + + if func3 then func3(self, wd, hd) end + + MSD.DrawTexturedRect(wd / 2 - 24, hd / 2 - 36, 48, 48, icon, MSD.ColorAlpha(self.color_idle, 255 - self.alpha * 255)) + draw.DrawText(text, "MSDFont.25", wd / 2, hd / 2 + 10, MSD.ColorAlpha(self.color_idle, 255 - self.alpha * 255), TEXT_ALIGN_CENTER) + + if self.alpha > 0.01 then + MSD.DrawTexturedRect(wd / 2 - 24, hd / 2 - 36, 48, 48, icon, MSD.ColorAlpha(color or MSD.Config.MainColor["p"], self.alpha * 255)) + draw.DrawText(text, "MSDFont.25", wd / 2, hd / 2 + 10, MSD.ColorAlpha(color or MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_CENTER) + end + + if text2 then + draw.DrawText("id: " .. text2, "MSDFont.20", 10, 10, MSD.Text.d, TEXT_ALIGN_LEFT) + end + + if text3 then + draw.DrawText(text3, "MSDFont.20", wd / 2, hd - 20, MSD.Text.n, TEXT_ALIGN_CENTER) + end + end + + button.OnCursorEntered = function(self) + self.hover = true + end + + button.OnCursorExited = function(self) + self.hover = false + end + + button.DoClick = function(self) + if self.disable then return end + func(self) + end + + button.DoRightClick = function(self) + if self.disable or not func2 then return end + func2(self) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.ColorSelector(parent, x, y, w, h, text, color, func, alpha_chl) + color = table.Copy(color) + local button = vgui.Create("DButton") + button:SetText(text) + + if x and y then + button:SetParent(parent) + button:SetPos(x, y) + end + + if x == "static" then + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + button:SetSize(w, h) + end + button.alpha = 0 + button.color = color + button.Paint = function(self, sw, sh) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, sw, sh, MSD.Theme["l"]) + + if (self.hover or self.hovered) and not self.disabled then + self.alpha = Lerp(FrameTime() * 5, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 5, self.alpha, 0) + end + + draw.DrawText(self:GetText(), "MSDFont.22", 5, h / 2 - 11, MSD.ColorAlpha(MSD.Config.MainColor["p"], self.alpha * 255), TEXT_ALIGN_LEFT) + draw.DrawText(self:GetText(), "MSDFont.22", 5, h / 2 - 11, MSD.ColorAlpha(self.disabled and MSD.Text["n"] or MSD.Text["s"], 255 - self.alpha * 255), TEXT_ALIGN_LEFT) + + if not self.disabled then draw.RoundedBox(MSD.Config.Rounded, sw - sw / 8, 0, sw / 8, h-1, button.color) end + + return true + end + button.OnCursorEntered = function(self) + self.hover = true + end + button.OnCursorExited = function(self) + self.hover = false + end + button.DoClick = function(self) + + if self.rebuild or self.disabled then return end + + self.rebuild = true + + if IsValid(self.cpanel) then + self.cpanel:Remove() + self.cpanel = nil + self:SizeTo(self:GetWide(), h, 0.2, 0, -1, function() + button.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + parent:Rebuild() + self.rebuild = nil + end) + return + end + + local UpdateColors, SetColors + button.StaticScale = { + w = w, + fixed_h = h + 200, + minw = 50, + minh = h + 200 + } + parent:Rebuild() + self:SetSize(self:GetWide(), h) + self:SizeTo(self:GetWide(), h + 200, 0.2, 0, -1, function() + self.rebuild = nil + end) + + self.cpanel = vgui.Create("DPanel", self) + self.cpanel:SetSize(self:GetWide(), 200) + self.cpanel:SetPos(0, h) + self.cpanel.Paint = function() end + + self.red = MSD.TextEntry(self.cpanel, 235, 5, 50, 60, "", MSD.GetPhrase("red"), 0, function(sp, value) + value = tonumber(value) or 0 + local col = math.Clamp(value,0,255) + self.color = Color(col, self.color.g, self.color.b) + SetColors(self.color, {[sp] = true}) + end, true, nil, false, true) + + self.green = MSD.TextEntry(self.cpanel, 235, 70, 50, 60, "", MSD.GetPhrase("green"), 0, function(sp, value) + value = tonumber(value) or 0 + local col = math.Clamp(value,0,255) + self.color = Color(self.color.r, col, self.color.b) + SetColors(self.color, {[sp] = true}) + end, true, nil, false, true) + + self.blue = MSD.TextEntry(self.cpanel, 235, 135, 50, 60, "", MSD.GetPhrase("blue"), 0, function(sp, value) + value = tonumber(value) or 0 + local col = math.Clamp(value,0,255) + self.color = Color(self.color.r, self.color.g, col) + SetColors(self.color, {[sp] = true}) + end, true, nil, false, true) + + self.HSV = vgui.Create("DColorCube", self.cpanel) + self.HSV:SetPos(alpha_chl and 55 or 40, 5) + self.HSV:SetSize(alpha_chl and 175 or 190, 190) + self.HSV:SetColor(self.color) + self.HSV.OnUserChanged = function(pn, col) + SetColors(col, {[pn] = true, [self.RGB] = true}) + end + + if alpha_chl then + self.AlphaBar = vgui.Create( "DAlphaBar", self.cpanel) + self.AlphaBar:SetPos( 30, 5 ) + self.AlphaBar:SetSize( 20, 190 ) + self.AlphaBar:SetValue( button.color.a / 255 ) + self.AlphaBar.OnChange = function(pn, al) + button.color.a = al * 255 + UpdateColors(button.color) + end + end + + self.RGB = vgui.Create("DRGBPicker", self.cpanel) + self.RGB:SetPos(5, 5) + self.RGB:SetSize(alpha_chl and 20 or 30, 190) + self.RGB.OnChange = function(pn, col) + local oc = ColorToHSV(col) + local _, s, v = ColorToHSV(self.HSV:GetRGB()) + col = HSVToColor(oc, s, v) + self.HSV:SetColor(col) + SetColors(col, {[pn] = true, [self.HSV] = true}) + end + + local rwd_set = vgui.Create("MSDPanelList", self.cpanel) + rwd_set:SetSize(self.cpanel:GetWide() - 290, self.cpanel:GetTall() - 10) + rwd_set:SetPos(290, 5) + rwd_set:EnableVerticalScrollbar() + rwd_set:EnableHorizontal(true) + rwd_set:SetSpacing(1) + rwd_set:SetPadding(1) + rwd_set.IgnoreVbar = true + + for _, cl in pairs(MSD.ColorPresets) do + MSD.ColorSelectBut(rwd_set, "static", nil, 8, 25, cl, function() + SetColors(cl, {}) + end) + end + + function UpdateColors(col) + button.color = col + func(self, col) + end + + function SetColors(col, ignore) + local sh = ColorToHSV( col ) + if not ignore[self.RGB] then self.RGB.LastY = ( 1 - sh / 360 ) * self.RGB:GetTall() end + if not ignore[self.HSV] then self.HSV:SetColor( col ) end + if not ignore[self.red] then self.red:SetText(col.r) end + if not ignore[self.green] then self.green:SetText(col.g) end + if not ignore[self.blue] then self.blue:SetText(col.b) end + if self.AlphaBar and not ignore[self.AlphaBar] then self.AlphaBar:SetValue( col.a / 255 ) end + UpdateColors(col) + end + + SetColors(self.color, {}) + end + + if not x or not y then + parent:AddItem(button) + end + + return button +end + +function MSD.VectorSelectorList(parent, text, vector, showa, angle, texta, copy_but, func) + local vecd, amgl + vecd = MSD.VectorDisplay(parent, "static", nil, 1, 50, text, vector, function(vec) + func(vec, showa and amgl.angle) + end) + if showa then + amgl = MSD.AngleDisplay(parent, "static", nil, 1, 50, texta, angle, function(ang) + func(vecd.vector, ang) + end) + end + + if copy_but then + MSD.Button(parent, "static", nil, 3, 50, MSD.GetPhrase("set_pos_self"), function() + local vec = LocalPlayer():GetPos() vecd.vector = vec + if showa then local ang = Angle(0, LocalPlayer():GetAngles().y, 0) amgl.angle = ang end + func(vecd.vector, showa and amgl.angle) + end) + + MSD.Button(parent, "static", nil, 3, 50, MSD.GetPhrase("set_pos_aim"), function() + local vec = LocalPlayer():GetEyeTrace().HitPos + if not vec then return end + vecd.vector = vec + if showa then local ang = Angle(0, LocalPlayer():GetAngles().y, 0) amgl.angle = ang end + func(vecd.vector, showa and amgl.angle) + end) + + MSD.Button(parent, "static", nil, 3, 50, MSD.GetPhrase("copy_from_ent"), function() + local vec = LocalPlayer():GetEyeTrace().Entity + if not vec then return end + vecd.vector = vec:GetPos() + if showa then local ang = vec:GetAngles() amgl.angle = ang end + func(vecd.vector, showa and amgl.angle) + end) + end + +end + +function MSD.NPCModelFrame(parent, x, y, w, h, model, anim) + if not model then + model = "models/Humans/Group01/Male_01.mdl" + end + + if ScrH() > 1000 then + modelsize = 500 + end + + local icon = vgui.Create("DModelPanel") + + if x and y then + icon:SetParent(parent) + icon:SetPos(x, y) + end + + if x == "static" then + icon.StaticScale = { + w = w, + fixed_h = h, + minw = 50, + minh = h + } + else + icon:SetSize(w, h) + end + + icon:SetFOV(20) + icon:SetCamPos(Vector(0, 0, 0)) + icon:SetDirectionalLight(BOX_RIGHT, Color(255, 160, 80, 255)) + icon:SetDirectionalLight(BOX_LEFT, Color(80, 160, 255, 255)) + icon:SetAmbientLight(Vector(-64, -64, -64)) + icon:SetAnimated(true) + icon.Angles = Angle(0, 0, 0) + icon:SetLookAt(Vector(-100, 0, -22)) + icon:SetModel(model) + icon.Entity:ResetSequence(anim or 1) + icon.Entity:SetPos(Vector(-100, 0, -61)) + function icon:DragMousePress() + self.PressX, self.PressY = gui.MousePos() + self.Pressed = true + end + function icon:DoDoubleClick() + if icon:GetFOV() < 10 then + icon:SetFOV(50) + else + icon:SetFOV(icon:GetFOV() - 5) + end + end + function icon:DragMouseRelease() + self.Pressed = false + end + function icon:LayoutEntity(ent) + if (self.bAnimated) then + self:RunAnimation() + end + + if (self.Pressed) then + local mx = gui.MousePos() + self.Angles = self.Angles - Angle(0, (self.PressX or mx) - mx, 0) + self.PressX, self.PressY = gui.MousePos() + end + + ent:SetAngles(self.Angles) + end + + function icon:UpdateModelValue(value) + if value == "" then return end + icon:SetModel(value) + + if icon.Entity then + icon.Entity:ResetSequence("idle") + icon.Entity:SetPos(Vector(-100, 0, -61)) + end + end + + if not x or not y then + parent:AddItem(icon) + end + + return icon +end + +function MSD.BigModelButton(parent, x, y, wd, hd, text, icon, func, text2, tr, color, func2) + local pnl = vgui.Create("DPanel") + if x and y then + pnl:SetParent(parent) + pnl:SetPos(x, y) + end + if x == "static" then + pnl.StaticScale = { w = wd, fixed_h = hd, minw = 150, minh = hd } + else + pnl:SetSize(wd, hd) + end + pnl.Paint = function() + if not IsValid(pnl.Icon.Entity) then return end + local ent_color = pnl.Icon:GetColor() + ent_color.a = pnl:GetAlpha() + end + pnl.SetCustomModel = function(mdl) + pnl.Icon:SetModel( mdl ) + pnl.Iconmdl = mdl + local mn, mx = pnl.Icon.Entity:GetRenderBounds() + local size = 0 + size = math.max(size, math.abs(mn.x) + math.abs(mx.x)) + size = math.max(size, math.abs(mn.y) + math.abs(mx.y)) + size = math.max(size, math.abs(mn.z) + math.abs(mx.z)) + pnl.Icon:SetFOV(90 - size) + pnl.Icon:SetCamPos(Vector(size, size + 5, 23)) + pnl.Icon:SetLookAt((mn + mx) * 0.95) + end + + pnl.Icon = vgui.Create("DModelPanel", pnl) + pnl.Icon:SetModel("") + pnl.Icon:SetMouseInputEnabled(false) + function pnl.Icon:LayoutEntity(Entity) + return + end + + local button = vgui.Create("DButton", pnl) + button:SetText("") + button.alpha = 0 + button.color_idle = color_white + button.text = text + button.Paint = function(self, w, h) + if self.hover and not self.disable then + self.alpha = Lerp(FrameTime() * 7, self.alpha, 1) + else + self.alpha = Lerp(FrameTime() * 7, self.alpha, 0) + end + local mida = pnl.Iconmdl and not tr + draw.RoundedBox(0, 0, 0, w, h, MSD.Theme["d"]) + + if not pnl.Iconmdl then + MSD.DrawTexturedRect(w / 2 - 24, h / 2 - 36, 48, 48, icon, MSD.ColorAlpha(self.color_idle, 255 - self.alpha * 255)) + else + draw.RoundedBox(0, 0, 0, w * self.alpha, h, MSD.Theme["l"]) + end + draw.DrawText(button.text, "MSDFont.25", w / 2, mida and h / 2 - 12 or h - 30, MSD.ColorAlpha(self.color_idle, 255 - self.alpha * 255), mida and TEXT_ALIGN_LEFT or TEXT_ALIGN_CENTER) + if text2 then draw.DrawText(text2, "MSDFont.21", w / 2, h / 2 + 12 , self.color_idle, TEXT_ALIGN_LEFT) end + + if self.alpha > 0.01 then + if not pnl.Iconmdl then MSD.DrawTexturedRect(w / 2 - 24, h / 2 - 36, 48, 48, icon, MSD.ColorAlpha(color or MSD.Config.MainColor["p"], self.alpha * 255)) end + draw.DrawText(button.text, "MSDFont.25", w / 2, mida and h / 2 - 12 or h - 30, MSD.ColorAlpha(color or MSD.Config.MainColor["p"], self.alpha * 255), mida and TEXT_ALIGN_LEFT or TEXT_ALIGN_CENTER) + end + end + button.OnCursorEntered = function(self) self.hover = true end + button.OnCursorExited = function(self) self.hover = false end + button.DoClick = function(self) if self.disable then return end func(self) end + button.DoRightClick = function(self) if self.disable or not func2 then return end func2(self) end + pnl.button = button + function pnl:PerformLayout() + self.button:StretchToParent( 0, 0, 0, 0 ) + local mida = pnl.Iconmdl and not tr + if not mida then + self.Icon:StretchToParent( 5, 5, 5, 5 ) + else + self.Icon:StretchToParent( 5, 5, self:GetWide() / 2, 5 ) + end + end + if not x or not y then parent:AddItem(pnl) end + return pnl +end \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/ui/msdcontext.lua b/addons/msd_ui/lua/msd/ui/msdcontext.lua new file mode 100644 index 0000000..7f9fcc3 --- /dev/null +++ b/addons/msd_ui/lua/msd/ui/msdcontext.lua @@ -0,0 +1,338 @@ +local ScrW, ScrH = ScrW, ScrH +local Ln = MSD.GetPhrase +local logo = Material("msd/macnco.png", "smooth") +CreateClientConVar("mmd_lastscr", 0, true, false) + +function MSD.AdminAccess(ply) + if MQS then + return MQS.IsEditor(ply) + end + if MRS then + return MRS.IsAdministrator(ply) + end + return ply:IsSuperAdmin() +end + +function MSD.OpenMenuManager(parrent, mod_open) + if not MSD.AdminAccess(LocalPlayer()) then return end + + if IsValid(MSD.SetupMenu) then + if not MSD.SetupMenu:IsVisible() then + MSD.SetupMenu:AlphaTo(255, 0.4) + MSD.SetupMenu:Show() + MSD.SetupMenu:Center() + return + end + if parrent then + MSD.SetupMenu:Center() + return + else + MSD.SetupMenu:Close() + end + end + + local pnl_w, pnl_h = ScrW(), ScrH() + pnl_w, pnl_h = pnl_w - pnl_w / 4, pnl_h - pnl_h / 6 + local panel, setbut + + if parrent then + panel = parrent:Add("MSDSimpleFrame") + else + panel = vgui.Create("MSDSimpleFrame") + panel:MakePopup() + end + + panel:SetSize(pnl_w, pnl_h) + panel:Center() + panel:SetAlpha(0) + panel:AlphaTo(255, 0.3) + + panel.Paint = function(self, w, h) + MSD.DrawBG(self, w, h) + + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, 50, MSD.Theme["d"]) + draw.RoundedBox(MSD.Config.Rounded, 0, 52, w, h - 52, MSD.Theme["l"]) + end + + panel.clsBut = MSD.IconButton(panel, MSD.Icons48.cross, panel:GetWide() - 34, 10, 25, nil, MSD.Config.MainColor.p, function() + if panel.OnPress then + panel.OnPress() + + return + end + + panel:AlphaTo(0, 0.4, 0, function() + panel:Close() + end) + end) + + function panel:OnClose() + if panel.ModuleSwitch then panel.ModuleSwitch() panel.ModuleSwitch = nil end + MSD.SetupMenu = nil + end + + if not parrent then + panel.clsHide = MSD.IconButton(panel, MSD.Icons48.dot, panel:GetWide() - 64, 10, 25, nil, MSD.Config.MainColor.p, function() + if panel.OnPress then + panel.OnPress() + + return + end + + panel:AlphaTo(0, .4, 0, function() + MSD.SetupMenu:Hide() + end) + end) + end + + panel.Menu = vgui.Create("MSDPanelList", panel) + panel.Menu:SetSize(panel:GetWide() / 2, 50) + panel.Menu:SetPos(0, 0) + --panel.Menu:EnableVerticalScrollbar() + panel.Menu:EnableHorizontal(true) + panel.Menu:SetSpacing(2) + panel.Menu.IgnoreVbar = true + panel.Menu.Paint = function() end + panel.Menu.Deselect = function(but) + if not but then return end + but.hovered = true + + for k, v in pairs(panel.Menu:GetItems()) do + if v and v:IsValid() and v ~= but then + v.hovered = false + end + end + end + + function panel.ReOpenCanvas() + if IsValid(panel.Canvas) then panel.Canvas:Remove() end + panel.Canvas = vgui.Create("DPanel", panel) + panel.Canvas:SetSize(panel:GetWide(), panel:GetTall() - 52) + panel.Canvas:SetPos(0, 52) + panel.Canvas.Paint = function() end + end + + local cnv = GetConVar("mmd_lastscr") + + function panel.OpenSettings() + cnv:SetInt(-1) + panel.ReOpenCanvas() + panel.Menu.Deselect(setbut) + MSD.OpenSettingsMenu(panel.Canvas, panel) + end + + for id, mod in ipairs(MSD.Modules) do + local button = MSD.MenuButtonTop(panel.Menu, mod.icon, nil, nil, "auto", 50, mod.name, function(self) + if panel.ModuleSwitch then panel.ModuleSwitch() panel.ModuleSwitch = nil end + cnv:SetInt(id) + panel.ReOpenCanvas() + panel.Menu.Deselect(self) + mod.menu(panel.Canvas, panel) + end) + + if (mod_open and id == mod_open) or (cnv:GetInt() == 0 and id == 1) or cnv:GetInt() == id then + panel.ReOpenCanvas() + panel.Menu.Deselect(button) + mod.menu(panel.Canvas, panel) + end + end + + setbut = MSD.MenuButtonTop(panel.Menu, MSD.Icons48.cog, nil, nil, "auto", 50, "", function(self) + panel.OpenSettings() + end) + + if cnv:GetInt() == -1 and not mod_open then + panel.OpenSettings() + end + + MSD.SetupMenu = panel + + return panel +end + +function MSD.OpenSettingsMenu(panel) + + oldcfg = MSD.Config + + panel.Canvas = vgui.Create("MSDPanelList", panel) + panel.Canvas:SetSize(panel:GetWide() / 2 - 5, panel:GetTall()) + panel.Canvas:SetPos(panel:GetWide() / 2 + 5, 0) + panel.Canvas:EnableVerticalScrollbar() + panel.Canvas:EnableHorizontal(true) + panel.Canvas:SetSpacing(2) + panel.Canvas.IgnoreVbar = true + panel.Canvas.Paint = function() end + + panel.Settings = vgui.Create("MSDPanelList", panel) + panel.Settings:SetSize(panel:GetWide() / 2, panel:GetTall() - 80) + panel.Settings:SetPos(0, 0) + panel.Settings:EnableVerticalScrollbar() + panel.Settings:EnableHorizontal(true) + panel.Settings:SetSpacing(2) + panel.Settings.IgnoreVbar = true + panel.Settings.Paint = function() end + + panel.Settings.Update = function() + panel.Settings:Clear() + + MSD.Header(panel.Settings, Ln("set_ui")) + local combo = MSD.ComboBox(panel.Settings, "static", nil, 1, 50, "Language:", Ln("none")) + + combo.OnSelect = function(self, index, text, data) + MSD.Config.Language = data + panel.Settings.Update() + end + + for k, v in pairs(MSD.Language) do + combo:AddChoice(v.lang_name, k) + end + + combo:SetValue(Ln("lang_name")) + local sld1 + + local function sliderCL(cl) + local c = math.Clamp(math.Round(cl * 255), 30, 250) + MSD.Config.BgrColor = Color(c, c, c) + end + + MSD.DTextSlider(panel.Settings, "static", nil, 1, 50, Ln("set_ui_blur"), Ln("set_ui_mono"), MSD.Config.Blur, function(self, value) + MSD.Config.Blur = value + if value then + sld1.value = 1 + sliderCL(sld1.value) + else + sld1.value = 0.18 + sliderCL(sld1.value) + end + end) + + sld1 = MSD.VolumeSlider(panel.Settings, "static", nil, 1, 50, Ln("set_ui_brightness"), MSD.Config.BgrColor.r / 255, function(self, var) + sliderCL(var) + end) + + MSD.ColorSelector(panel.Settings, "static", nil, 1, 50, Ln("set_ui_color"), MSD.Config.MainColor.p, function(self, color) + MSD.Config.MainColor.p = color + end) + + MSD.DTextSlider(panel.Settings, "static", nil, 1, 50, Ln("border_rounded"), Ln("border_square"), MSD.Config.Rounded == 8, function(self, value) + if value then + MSD.Config.Rounded = 8 + else + MSD.Config.Rounded = 0 + end + end) + + if not MSD.HUD then return end + + MSD.Header(panel.Settings, Ln("set_hud")) + + MSD.BoolSlider(panel.Settings, "static", nil, 1, 50, Ln("custom_icon"), MSD.Config.HUD.ShowIcon, function(self, value) + MSD.Config.HUD.ShowIcon = value + end) + + MSD.TextEntry(panel.Settings, "static", nil, 1, 50, Ln("q_icon68"), Ln("e_url") .. ":", MSD.Config.HUD.Icon, function(self, value) + MSD.Config.HUD.Icon = value + end) + + MSD.TextEntry(panel.Settings, "static", nil, 1, 50, Ln("e_text"), Ln("e_text") .. ":", MSD.Config.HUD.Text, function(self, value) + MSD.Config.HUD.Text = value + end) + + local acombo = MSD.ComboBox(panel.Settings, "static", nil, 1, 50, "", "") + + local algm = { + Ln("set_ui_align_left"), + Ln("set_ui_align_center"), + Ln("set_ui_align_right") + } + for i,t in pairs(algm) do + acombo:AddChoice(t, i - 1) + acombo:SetValue(t) + end + + acombo.OnSelect = function(self, index, text, data) + MSD.Config.HUD.AlignX = data + end + + local sld0, txt1, sld2, txt2 + sld0 = MSD.VolumeSlider(panel.Settings, "static", nil, 1.2, 50, Ln("set_ui_offset_h"), MSD.Config.HUD.X, function(self, var) + var = math.Round(var, 3) + MSD.Config.HUD.X = var + txt1:SetText(var * 100) + end) + + txt1 = MSD.TextEntry(panel.Settings, "static", nil, 6, 50, "", "", MSD.Config.HUD.X * 100, function(self, value) + value = math.Clamp((tonumber(value) or 0) / 100,0,1) + sld0.value = value + MSD.Config.HUD.X = value + end, true, nil, nil, true) + + sld2 = MSD.VolumeSlider(panel.Settings, "static", nil, 1.2, 50, Ln("set_ui_offset_v"), MSD.Config.HUD.Y, function(self, var) + var = math.Round(var, 3) + MSD.Config.HUD.Y = var + txt2:SetText(var * 100) + end) + + txt2 = MSD.TextEntry(panel.Settings, "static", nil, 6, 50, "", "", MSD.Config.HUD.Y * 100, function(self, value) + value = math.Clamp((tonumber(value) or 0) / 100,0,1) + sld2.value = value + MSD.Config.HUD.Y = value + end, true, nil, nil, true) + + MSD.BoolSlider(panel.Settings, "static", nil, 1, 50, Ln("show_team"), MSD.Config.HUD.ShowGroup, function(self, value) + MSD.Config.HUD.ShowGroup = value + end) + + MSD.BoolSlider(panel.Settings, "static", nil, 1, 50, Ln("mrs_hud_follow"), MSD.Config.HUD.Follow, function(self, value) + MSD.Config.HUD.Follow = value + end) + + MSD.BoolSlider(panel.Settings, "static", nil, 1, 50, Ln("use_team_colors"), MSD.Config.HUD.TeamColor, function(self, value) + MSD.Config.TeamColor = value + end) + + MSD.BoolSlider(panel.Settings, "static", nil, 1, 50, Ln("icon_right"), MSD.Config.HUD.IconRight, function(self, value) + MSD.Config.HUD.IconRight = value + end) + + MSD.VolumeSlider(panel.Settings, "static", nil, 1, 50, Ln("icon_size"), (MSD.Config.HUD.IconSize - 24) / 40, function(self, var) + var = math.Round(var, 3) + MSD.Config.HUD.IconSize = math.Clamp(24 + math.Round(var * 40), 24, 64) + end) + + MSD.VolumeSlider(panel.Settings, "static", nil, 1, 50, Ln("font_size"), (MSD.Config.HUD.FontSize - 16) / 30, function(self, var) + var = math.Round(var, 3) + MSD.Config.HUD.FontSize = math.Clamp(16 + math.Round(var * 30), 16, 46) + end) + end + + panel.Settings.Update() + + if LocalPlayer():IsSuperAdmin() then + MSD.BigButton(panel, 0, panel:GetTall() - 80, panel:GetWide() / 4, 80, Ln("upl_changes"), MSD.Icons48.save, function() + MSD.SaveConfig() + panel.Settings.Update() + end) + + MSD.BigButton(panel, panel:GetWide() / 4, panel:GetTall() - 80, panel:GetWide() / 4, 80, Ln("res_changes"), MSD.Icons48.cross, function() + MSD.Config = oldcfg + panel.Settings.Update() + end) + end + + local pnl = vgui.Create("DPanel") + + pnl.StaticScale = { + w = 1, + h = 1, + minw = 150, + minh = 150 + } + + pnl.Paint = function(self, w, h) + MSD.DrawTexturedRect(w / 2 - 128, h / 2 - 128, 256, 236, logo, MSD.Text["l"]) + draw.DrawText("MSD UI version - " .. MSD.Version, "MSDFont.25", w / 2, h / 2 + 130, MSD.Text["l"], TEXT_ALIGN_CENTER) + end + + panel.Canvas:AddItem(pnl) +end \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/ui/msdframe.lua b/addons/msd_ui/lua/msd/ui/msdframe.lua new file mode 100644 index 0000000..14c0a00 --- /dev/null +++ b/addons/msd_ui/lua/msd/ui/msdframe.lua @@ -0,0 +1,187 @@ +local PANEL = {} +AccessorFunc(PANEL, "m_bIsMenuComponent", "IsMenu", FORCE_BOOL) +AccessorFunc(PANEL, "m_bDraggable", "Draggable", FORCE_BOOL) +AccessorFunc(PANEL, "m_bSizable", "Sizable", FORCE_BOOL) +AccessorFunc(PANEL, "m_bScreenLock", "ScreenLock", FORCE_BOOL) +AccessorFunc(PANEL, "m_bDeleteOnClose", "DeleteOnClose", FORCE_BOOL) +AccessorFunc(PANEL, "m_bPaintShadow", "PaintShadow", FORCE_BOOL) +AccessorFunc(PANEL, "m_iMinWidth", "MinWidth", FORCE_NUMBER) +AccessorFunc(PANEL, "m_iMinHeight", "MinHeight", FORCE_NUMBER) + +function PANEL:Init() + self:SetFocusTopLevel(true) + self:SetPaintShadow(true) + self:SetDraggable(true) + self:SetSizable(false) + self:SetScreenLock(false) + self:SetDeleteOnClose(true) + self:SetMinWidth(50) + self:SetMinHeight(50) + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) + self.m_fCreateTime = SysTime() + + self.WorkSpace = { + x = 0, + y = 52, + w = 0, + h = 0 + } + + self.WorkComponents = {} +end + +function PANEL:Close() + self:SetVisible(false) + + if (self:GetDeleteOnClose()) then + self:Remove() + end + + self:OnClose() +end + +function PANEL:OnClose() +end + +function PANEL:Center() + self:InvalidateLayout(true) + self:CenterVertical() + self:CenterHorizontal() +end + +function PANEL:IsActive() + if (self:HasFocus()) then return true end + if (vgui.FocusedHasParent(self)) then return true end + + return false +end + +function PANEL:Think() + local mousex = math.Clamp(gui.MouseX(), 1, ScrW() - 1) + local mousey = math.Clamp(gui.MouseY(), 1, ScrH() - 1) + + if (self.Dragging) then + local x = mousex - self.Dragging[1] + local y = mousey - self.Dragging[2] + + -- Lock to screen bounds if screenlock is enabled + if (self:GetScreenLock()) then + x = math.Clamp(x, 0, ScrW() - self:GetWide()) + y = math.Clamp(y, 0, ScrH() - self:GetTall()) + end + + self:SetPos(x, y) + end + + if (self.Sizing) then + local x = mousex - self.Sizing[1] + local y = mousey - self.Sizing[2] + local px, py = self:GetPos() + + if (x < self.m_iMinWidth) then + x = self.m_iMinWidth + elseif (x > ScrW() - px and self:GetScreenLock()) then + x = ScrW() - px + end + + if (y < self.m_iMinHeight) then + y = self.m_iMinHeight + elseif (y > ScrH() - py and self:GetScreenLock()) then + y = ScrH() - py + end + + self:SetSize(x, y) + self:SetCursor("sizenwse") + + return + end + + local screenX, screenY = self:LocalToScreen(0, 0) + + if (self.Hovered and self.m_bSizable and mousex > (screenX + self:GetWide() - 20) and mousey > (screenY + self:GetTall() - 20)) then + self:SetCursor("sizenwse") + + return + end + + if (self.Hovered and self:GetDraggable() and mousey < (screenY + 24)) then + self:SetCursor("sizeall") + + return + end + + self:SetCursor("arrow") + + -- Don't allow the frame to go higher than 0 + if (self.y < 0) then + self:SetPos(self.x, 0) + end +end + +function PANEL:AddToWorkSpace(panel) + panel:SetParent(self) + panel:SetSize(self.WorkSpace.w, self.WorkSpace.h) + panel:SetPos(self.WorkSpace.x, self.WorkSpace.y) + local id = table.insert(self.WorkComponents, panel) + + panel.OnRemove = function() + self.WorkComponents[id] = nil + end +end + +function PANEL:Paint(w, h) + return true +end + +function PANEL:OnMousePressed() + local screenX, screenY = self:LocalToScreen(0, 0) + + if (self.m_bSizable and gui.MouseX() > (screenX + self:GetWide() - 20) and gui.MouseY() > (screenY + self:GetTall() - 20)) then + self.Sizing = {gui.MouseX() - self:GetWide(), gui.MouseY() - self:GetTall()} + + self:MouseCapture(true) + + return + end + + if (self:GetDraggable() and gui.MouseY() < (screenY + 24)) then + self.Dragging = {gui.MouseX() - self.x, gui.MouseY() - self.y} + + self:MouseCapture(true) + + return + end +end + +function PANEL:OnMouseReleased() + self.Dragging = nil + self.Sizing = nil + self:MouseCapture(false) +end + +function PANEL:PerformLayout() + local Wide = self:GetWide() + local Tall = self:GetTall() + + self.WorkSpace = { + x = 0, + y = 52, + w = Wide, + h = Tall - 52 + } + + if self.WorkComponents and #self.WorkComponents > 0 then + for i = 1, #self.WorkComponents do + local panel = self.WorkComponents[i] + + if panel and IsValid(panel) then + panel:SetSize(self.WorkSpace.w, self.WorkSpace.h) + panel:SetPos(self.WorkSpace.x, self.WorkSpace.y) + panel:InvalidateLayout() + end + end + end +end + +derma.DefineControl("MSDSimpleFrame", "A simple window for msd", PANEL, "EditablePanel") \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/ui/msdmdlmenu.lua b/addons/msd_ui/lua/msd/ui/msdmdlmenu.lua new file mode 100644 index 0000000..d7e89c6 --- /dev/null +++ b/addons/msd_ui/lua/msd/ui/msdmdlmenu.lua @@ -0,0 +1,234 @@ +local PANEL = {} + +AccessorFunc( PANEL, "m_fAnimSpeed", "AnimSpeed" ) +AccessorFunc( PANEL, "Entity", "Entity" ) +AccessorFunc( PANEL, "vCamPos", "CamPos" ) +AccessorFunc( PANEL, "fFOV", "FOV" ) +AccessorFunc( PANEL, "vLookatPos", "LookAt" ) +AccessorFunc( PANEL, "aLookAngle", "LookAng" ) +AccessorFunc( PANEL, "colAmbientLight", "AmbientLight" ) +AccessorFunc( PANEL, "colColor", "Color" ) +AccessorFunc( PANEL, "bAnimated", "Animated" ) + +function PANEL:Init() + + self.Entity = nil + self.SubEntitys = {} + self.LastPaint = 0 + self.DirectionalLight = {} + self.FarZ = 4096 + self.Angles = Angle(0, 0, 0) + + self:SetCamPos( Vector(0, 0, 0) ) + self:SetLookAt( Vector(-100, 0, -22) ) + self:SetFOV( 50 ) + + self:SetText( "" ) + self:SetAnimSpeed( 0.5 ) + self:SetAnimated( true ) + + self:SetAmbientLight( Color( 50, 50, 50 ) ) + + self:SetDirectionalLight( BOX_TOP, Color( 255, 255, 255 ) ) + self:SetDirectionalLight( BOX_FRONT, Color( 255, 255, 255 ) ) + + self:SetDirectionalLight(BOX_RIGHT, Color(255, 160, 80, 255)) + self:SetDirectionalLight(BOX_LEFT, Color(80, 160, 255, 255)) + + self:SetColor( Color( 255, 255, 255, 255 ) ) + +end + +function PANEL:SetDirectionalLight( iDirection, color ) + self.DirectionalLight[ iDirection ] = color +end + +function PANEL:SetModel( strModelName ) + if ( IsValid( self.Entity ) ) then + self.Entity:Remove() + self.Entity = nil + end + + if ( !ClientsideModel ) then return end + + self.Entity = ClientsideModel( strModelName, RENDERGROUP_OTHER ) + if ( !IsValid( self.Entity ) ) then return end + + self.Entity:SetNoDraw( true ) + self.Entity:SetIK( false ) + + local iSeq = self.Entity:LookupSequence( "walk_all" ) + if ( iSeq <= 0 ) then iSeq = self.Entity:LookupSequence( "WalkUnarmed_all" ) end + if ( iSeq <= 0 ) then iSeq = self.Entity:LookupSequence( "walk_all_moderate" ) end + + if ( iSeq > 0 ) then self.Entity:ResetSequence( iSeq ) end + +end + +function PANEL:GetModel() + + if ( !IsValid( self.Entity ) ) then return end + + return self.Entity:GetModel() + +end + +function PANEL:DrawModel() + + local curparent = self + local leftx, topy = self:LocalToScreen( 0, 0 ) + local rightx, bottomy = self:LocalToScreen( self:GetWide(), self:GetTall() ) + while ( curparent:GetParent() != nil ) do + curparent = curparent:GetParent() + + local x1, y1 = curparent:LocalToScreen( 0, 0 ) + local x2, y2 = curparent:LocalToScreen( curparent:GetWide(), curparent:GetTall() ) + + leftx = math.max( leftx, x1 ) + topy = math.max( topy, y1 ) + rightx = math.min( rightx, x2 ) + bottomy = math.min( bottomy, y2 ) + previous = curparent + end + + render.SetScissorRect( leftx, topy, rightx, bottomy, true ) + + local ret = self:PreDrawModel( self.Entity ) + if ( ret != false ) then + self.Entity:DrawModel() + + for k,v in pairs(self.SubEntitys) do + if IsValid(v) then + v:DrawModel() + if v.bone and isnumber(v.bone) then + local pos, ang = MCS.GetBoneOrientation(self.Entity, v.bone, v.pos, v.ang) + ang:RotateAroundAxis(ang:Up(), v.ang.y) + ang:RotateAroundAxis(ang:Right(), v.ang.p) + ang:RotateAroundAxis(ang:Forward(), v.ang.r) + v:SetPos(pos + ang:Forward() * v.pos.x + ang:Right() * v.pos.y + ang:Up() * v.pos.z) + v:SetAngles(ang) + end + end + end + + self:PostDrawModel( self.Entity ) + end + + render.SetScissorRect( 0, 0, 0, 0, false ) + +end + +function PANEL:PreDrawModel( ent ) + return true +end + +function PANEL:PostDrawModel( ent ) + +end + +function PANEL:DragMousePress() + self.PressX, self.PressY = gui.MousePos() + self.Pressed = true +end + +function PANEL:DoDoubleClick() + if self:GetFOV() < 10 then + self:SetFOV(50) + else + self:SetFOV(self:GetFOV() - 5) + end +end + +function PANEL:DragMouseRelease() + self.Pressed = false +end + +function PANEL:Paint( w, h ) + + if ( !IsValid( self.Entity ) ) then return end + + local x, y = self:LocalToScreen( 0, 0 ) + + self:LayoutEntity( self.Entity ) + + local ang = self.aLookAngle + if ( !ang ) then + ang = ( self.vLookatPos - self.vCamPos ):Angle() + end + + cam.Start3D( self.vCamPos, ang, self.fFOV, x, y, w, h, 5, self.FarZ ) + + render.SuppressEngineLighting( true ) + render.SetLightingOrigin( self.Entity:GetPos() ) + render.ResetModelLighting( self.colAmbientLight.r / 255, self.colAmbientLight.g / 255, self.colAmbientLight.b / 255 ) + render.SetColorModulation( self.colColor.r / 255, self.colColor.g / 255, self.colColor.b / 255 ) + render.SetBlend( ( self:GetAlpha() / 255 ) * ( self.colColor.a / 255 ) ) -- * surface.GetAlphaMultiplier() + + for i = 0, 6 do + local col = self.DirectionalLight[ i ] + if ( col ) then + render.SetModelLighting( i, col.r / 255, col.g / 255, col.b / 255 ) + end + end + + self:DrawModel() + + render.SuppressEngineLighting( false ) + cam.End3D() + + self.LastPaint = RealTime() + +end + +function PANEL:RunAnimation() + self.Entity:FrameAdvance( ( RealTime() - self.LastPaint ) * self.m_fAnimSpeed ) +end + +function PANEL:StartScene( name ) + + if ( IsValid( self.Scene ) ) then + self.Scene:Remove() + end + + self.Scene = ClientsideScene( name, self.Entity ) +end + +function PANEL:LayoutEntity(ent) + if (self.bAnimated) then + self:RunAnimation() + end + + if (self.Pressed) then + local mx = gui.MousePos() + self.Angles = self.Angles - Angle(0, (self.PressX or mx) - mx, 0) + self.PressX, self.PressY = gui.MousePos() + end + + ent:SetAngles(self.Angles) +end + +function PANEL:OnRemove() + if ( IsValid( self.Entity ) ) then + self.Entity:Remove() + end + + for k,v in pairs(self.SubEntitys) do + if IsValid(v) then + v:Remove() + end + end + +end + +function PANEL:GenerateExample( ClassName, PropertySheet, Width, Height ) + + local ctrl = vgui.Create( ClassName ) + ctrl:SetSize( 300, 300 ) + ctrl:SetModel( "models/props_junk/PlasticCrate01a.mdl" ) + ctrl:GetEntity():SetSkin( 2 ) + + PropertySheet:AddSheet( ClassName, ctrl, nil, true, true ) + +end + +derma.DefineControl( "MSDModelPanel", "A panel containing a model but more epic", PANEL, "DButton" ) \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/ui/msdmenu.lua b/addons/msd_ui/lua/msd/ui/msdmenu.lua new file mode 100644 index 0000000..89f68bf --- /dev/null +++ b/addons/msd_ui/lua/msd/ui/msdmenu.lua @@ -0,0 +1,186 @@ +if SERVER then return end +local tblOpenMenus = {} + +function RegisterDermaMenuForClose(dmenu) + table.insert(tblOpenMenus, dmenu) +end + +function MSD.MenuOpen(parentmenu, parent, bg) + if (not parentmenu) then + CloseDermaMenus() + end + + local dmenu = vgui.Create("MSD.DMenu", parent) + dmenu.ShadowStatic = 0 + dmenu.ShadowInt = 1 + + dmenu.Paint = function(self, w, h) + if bg then + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d_na"]) + else + MSD.Blur(self, 1, 2, 255, 55, w, h) + draw.RoundedBox(MSD.Config.Rounded, 0, 0, w, h, MSD.Theme["d"]) + end + end + + return dmenu +end + +function CloseDermaMenus() + for k, dmenu in pairs(tblOpenMenus) do + if (IsValid(dmenu)) then + dmenu:SetVisible(false) + + if (dmenu:GetDeleteSelf()) then + dmenu:Remove() + end + end + end + + tblOpenMenus = {} + hook.Run("CloseDermaMenus") +end + +local function DermaDetectMenuFocus(panel, mousecode) + if (IsValid(panel)) then + if (panel.m_bIsMenuComponent) then return end + + return DermaDetectMenuFocus(panel:GetParent(), mousecode) + end + + CloseDermaMenus() +end + +hook.Add("VGUIMousePressed", "MatDMenuDetectMenuFocus", DermaDetectMenuFocus) +local PANEL = {} +AccessorFunc(PANEL, "m_bBorder", "DrawBorder") +AccessorFunc(PANEL, "m_bDeleteSelf", "DeleteSelf") +AccessorFunc(PANEL, "m_iMinimumWidth", "MinimumWidth") +AccessorFunc(PANEL, "m_bDrawColumn", "DrawColumn") +AccessorFunc(PANEL, "m_iMaxHeight", "MaxHeight") +AccessorFunc(PANEL, "m_pOpenSubMenu", "OpenSubMenu") + +function PANEL:Init() + self:SetIsMenu(true) + self:SetDrawBorder(true) + self:SetPaintBackground(true) + self:SetMinimumWidth(100) + self:SetDrawOnTop(true) + self:SetMaxHeight(ScrH() * 0.9) + self:SetDeleteSelf(true) + self:SetPadding(0) + RegisterDermaMenuForClose(self) +end + +function PANEL:AddOption(strText, funcFunction, icon, check) + if check and not check() then return end + + local pnl = vgui.Create("MSD.DMenuOption", self) + pnl:SetMenu(self) + pnl:SetText(strText) + if icon then + pnl.icon = icon + pnl:SetTextInset(38, 0) + end + + if (funcFunction) then + pnl.DoClick = funcFunction + end + + self:AddPanel(pnl) + + return pnl +end + +function PANEL:AddSubMenu(strText, funcFunction) + local pnl = vgui.Create("MSD.DMenuOption", self) + local SubMenu = pnl:AddSubMenu(strText, funcFunction) + pnl:SetText(strText) + + if (funcFunction) then + pnl.DoClick = funcFunction + end + + self:AddPanel(pnl) + + return SubMenu, pnl +end + +derma.DefineControl("MSD.DMenu", "A Menu 2", PANEL, "DMenu") + +PANEL = {} +AccessorFunc(PANEL, "m_pMenu", "Menu") +AccessorFunc(PANEL, "m_bChecked", "Checked") +AccessorFunc(PANEL, "m_bCheckable", "IsCheckable") + +function PANEL:Init() + self:SetContentAlignment(4) + self:SetTextInset(10, 0) + self:SetFont("MSDFont.16") + self:SetTextColor(MSD.Text["s"]) + self:SetChecked(false) + self.ChangeCC = true + self.ColorText = MSD.Text["s"] +end + +function PANEL:OnCursorEntered() + self.ColorText = MSD.Config.MainColor["p"] + self.ChangeCC = true + + if (IsValid(self.ParentMenu)) then + self.ParentMenu:OpenSubMenu(self, self.SubMenu) + + return + end + + self:GetParent():OpenSubMenu(self, self.SubMenu) +end + +function PANEL:OnCursorExited() + self.ColorText = MSD.Text["l"] + self.ChangeCC = true +end + +function PANEL:AddSubMenu() + local SubMenu = MSD.MenuOpen(true, self) + SubMenu:SetVisible(false) + SubMenu:SetParent(self) + self:SetSubMenu(SubMenu) + + return SubMenu +end + +function PANEL:Paint(w, h) + if self.ChangeCC then + self:SetTextColor(self.ColorText) + self.ChangeCC = nil + end + + if self.icon then + local ih = h - 6 + MSD.DrawTexturedRect(5, 3, ih, ih, self.icon, self.ColorText) + end +end + +function PANEL:PerformLayout( w, h ) + + self:SizeToContents() + self:SetWide( self:GetWide() + 30 ) + + w = math.max( self:GetParent():GetWide(), self:GetWide() ) + + self:SetSize( w, self.icon and 30 or 22 ) + + if ( IsValid( self.SubMenuArrow ) ) then + + self.SubMenuArrow:SetSize( 15, 15 ) + self.SubMenuArrow:CenterVertical() + self.SubMenuArrow:AlignRight( 4 ) + + end + + DButton.PerformLayout( self, w, h ) + +end + +derma.DefineControl("MSD.DMenuOption", "Menu Option Line 2", PANEL, "DMenuOption") \ No newline at end of file diff --git a/addons/msd_ui/lua/msd/ui/msdpanellist.lua b/addons/msd_ui/lua/msd/ui/msdpanellist.lua new file mode 100644 index 0000000..7c31c9b --- /dev/null +++ b/addons/msd_ui/lua/msd/ui/msdpanellist.lua @@ -0,0 +1,413 @@ +local PANEL = {} +AccessorFunc(PANEL, "m_bSizeToContents", "AutoSize") +AccessorFunc(PANEL, "m_bStretchHorizontally", "StretchHorizontally") +AccessorFunc(PANEL, "m_bNoSizing", "NoSizing") +AccessorFunc(PANEL, "m_bSortable", "Sortable") +AccessorFunc(PANEL, "m_fAnimTime", "AnimTime") +AccessorFunc(PANEL, "m_fAnimEase", "AnimEase") +AccessorFunc(PANEL, "m_strDraggableName", "DraggableName") +AccessorFunc(PANEL, "Spacing", "Spacing") +AccessorFunc(PANEL, "Padding", "Padding") + +function PANEL:Init() + self:SetDraggableName("GlobalDPanel") + self.pnlCanvas = vgui.Create("DPanel", self) + self.pnlCanvas:SetPaintBackground(false) + + self.pnlCanvas.OnMousePressed = function(s, code) + s:GetParent():OnMousePressed(code) + end + + self.pnlCanvas.OnChildRemoved = function() + self:OnChildRemoved() + end + + self.pnlCanvas:SetMouseInputEnabled(true) + + self.pnlCanvas.InvalidateLayout = function() + self:InvalidateLayout() + end + + self.pnlCanvas.MasterPanel = self + self.Items = {} + self.YOffset = 0 + self.m_fAnimTime = 0 + self.m_fAnimEase = -1 + self.m_iBuilds = 0 + self.IgnoreVbar = true + self:SetSpacing(0) + self:SetPadding(0) + self:EnableHorizontal(false) + self:SetAutoSize(false) + self:SetPaintBackground(true) + self:SetNoSizing(false) + self:SetMouseInputEnabled(true) + + self:SetPaintBackgroundEnabled(false) + self:SetPaintBorderEnabled(false) +end + +function PANEL:OnModified() + -- Override me +end + +function PANEL:SizeToContents() + self:SetSize(self.pnlCanvas:GetSize()) +end + +function PANEL:GetItems() + return self.Items +end + +function PANEL:EnableHorizontal(bHoriz) + self.Horizontal = bHoriz +end + +local sdw = Color(0, 0, 0, 70) + +function PANEL:EnableVerticalScrollbar() + if (self.VBar) then return end + self.VBar = vgui.Create("DVScrollBar", self) + + self.VBar.Paint = function(s, w, h) + draw.RoundedBox(4, 3, 13, 8, h - 24, sdw) + end + + self.VBar.btnUp.Paint = function(s, w, h) end + self.VBar.btnDown.Paint = function(s, w, h) end + + self.VBar.btnGrip.Paint = function(s, w, h) + draw.RoundedBox(4, 5, 0, 4, h + 22, sdw) + end +end + +function PANEL:GetCanvas() + return self.pnlCanvas +end + +function PANEL:Clear(nDelete) + for k, panel in pairs(self.Items) do + if (not IsValid(panel)) then continue end + panel:Remove() + + if (nDelete) then + panel:SetVisible(false) + end + end + + self.Items = {} +end + +function PANEL:ClearEX(ex_panel) + if (not IsValid(ex_panel)) then return end + + for k, panel in pairs(self.Items) do + if (not IsValid(panel)) then continue end + + if (panel ~= ex_panel) then + panel:Remove() + end + end + + self.Items = {} + table.insert(self.Items, ex_panel) +end + +function PANEL:AddItem(item, strLineState) + if (not IsValid(item)) then return end + item:SetVisible(true) + item:SetParent(self:GetCanvas()) + item.m_strLineState = strLineState or item.m_strLineState + table.insert(self.Items, item) + item:SetSelectable(self.m_bSelectionCanvas) + self:InvalidateLayout() +end + +function PANEL:InsertBefore(before, insert, strLineState) + table.RemoveByValue(self.Items, insert) + self:AddItem(insert, strLineState) + local key = table.KeyFromValue(self.Items, before) + + if (key) then + table.RemoveByValue(self.Items, insert) + table.insert(self.Items, key, insert) + end +end + +function PANEL:InsertAfter(before, insert, strLineState) + table.RemoveByValue(self.Items, insert) + self:AddItem(insert, strLineState) + local key = table.KeyFromValue(self.Items, before) + + if (key) then + table.RemoveByValue(self.Items, insert) + table.insert(self.Items, key + 1, insert) + end +end + +function PANEL:InsertAtTop(insert, strLineState) + table.RemoveByValue(self.Items, insert) + self:AddItem(insert, strLineState) + local key = 1 + + if (key) then + table.RemoveByValue(self.Items, insert) + table.insert(self.Items, key, insert) + end +end + +function PANEL.DropAction(Slot, RcvSlot) + local PanelToMove = Slot.Panel + + if (dragndrop.m_MenuData == "copy") then + if (PanelToMove.Copy) then + PanelToMove = Slot.Panel:Copy() + PanelToMove.m_strLineState = Slot.Panel.m_strLineState + else + return + end + end + + PanelToMove:SetPos(RcvSlot.Data.pnlCanvas:ScreenToLocal(gui.MouseX() - dragndrop.m_MouseLocalX, gui.MouseY() - dragndrop.m_MouseLocalY)) + + if (dragndrop.DropPos == 4 or dragndrop.DropPos == 8) then + RcvSlot.Data:InsertBefore(RcvSlot.Panel, PanelToMove) + else + RcvSlot.Data:InsertAfter(RcvSlot.Panel, PanelToMove) + end +end + +function PANEL:RemoveItem(item, bDontDelete) + for k, panel in pairs(self.Items) do + if (panel == item) then + self.Items[k] = nil + + if (not bDontDelete) then + panel:Remove() + end + + self:InvalidateLayout() + end + end +end + +function PANEL:CleanList() + for k, panel in pairs(self.Items) do + if (not IsValid(panel) or panel:GetParent() ~= self.pnlCanvas) then + self.Items[k] = nil + end + end +end + +function PANEL:HorizontalRebuild(Offset) + local x, y = self.Padding, self.Padding + local l_highest = 0 + + for k, panel in pairs(self.Items) do + if (panel:IsVisible()) then + if panel.StaticScale then + local w, h + + if isstring(panel.StaticScale.w) then + w = tonumber(panel.StaticScale.w) + w = (self.pnlCanvas:GetWide() - (self.pnlCanvas:GetWide() / w)) - (self.Spacing + self.Padding) + elseif panel.StaticScale.w == 1 then + w = self.pnlCanvas:GetWide() / panel.StaticScale.w - (self.Spacing + self.Padding) + else + w = self.pnlCanvas:GetWide() / panel.StaticScale.w - (self.Spacing + self.Padding) / 1.5 + end + + if panel.StaticScale.fixed_h then + h = panel.StaticScale.fixed_h + elseif panel.StaticScale.h_w then + h = w + elseif panel.StaticScale.h then + if isstring(panel.StaticScale.h) then + h = tonumber(panel.StaticScale.h) + h = (self:GetTall() - (self:GetTall() / h)) - (self.Spacing + self.Spacing / h + self.Padding) + elseif panel.StaticScale.h == 1 then + h = self:GetTall() / panel.StaticScale.h - (self.Spacing + self.Padding) + else + h = self:GetTall() / panel.StaticScale.h - (self.Spacing + self.Padding) + end + end + + if panel.StaticScale.minw > w then + w = panel.StaticScale.minw + end + + if panel.StaticScale.h and panel.StaticScale.minh > h then + h = panel.StaticScale.minh + end + + panel:SetSize(w, h) + end + + local OwnLine = (panel.m_strLineState and panel.m_strLineState == "ownline") + local w = panel:GetWide() + local h = panel:GetTall() + local vbar = 0 + + if (self.VBar and self.VBar.Enabled and not self.IgnoreVbar) then + vbar = 13 + end + + if (x > self.Padding and (x + w > (self:GetWide() - vbar) or OwnLine)) then + x = self.Padding + y = y + l_highest + self.Spacing + l_highest = h + end + + if h > l_highest then + l_highest = h + end + + if (self.m_fAnimTime > 0 and self.m_iBuilds > 1) then + panel:MoveTo(x, y, self.m_fAnimTime, 0, self.m_fAnimEase) + else + panel:SetPos(x, y) + end + + x = x + w + self.Spacing + Offset = y + l_highest + self.Spacing + + if (OwnLine) then + x = self.Padding + y = y + h + self.Spacing + end + end + end + + return Offset +end + +function PANEL:NormalRebuild(Offset) + for k, panel in pairs(self.Items) do + if (panel:IsVisible()) then + if (self.m_bNoSizing) then + panel:SizeToContents() + + if (self.m_fAnimTime > 0 and self.m_iBuilds > 1) then + panel:MoveTo((self:GetCanvas():GetWide() - panel:GetWide()) * 0.5, self.Padding + Offset, self.m_fAnimTime, 0, self.m_fAnimEase) + else + panel:SetPos((self:GetCanvas():GetWide() - panel:GetWide()) * 0.5, self.Padding + Offset) + end + else + panel:SetWide(self:GetCanvas():GetWide() - self.Padding * 2) + + if (self.m_fAnimTime > 0 and self.m_iBuilds > 1) then + panel:MoveTo(self.Padding, self.Padding + Offset, self.m_fAnimTime, self.m_fAnimEase) + else + panel:SetPos(self.Padding, self.Padding + Offset) + end + end + + panel:InvalidateLayout(true) + Offset = Offset + panel:GetTall() + self.Spacing + end + end + + Offset = Offset + self.Padding + + return Offset +end + +function PANEL:Rebuild() + local Offset = 0 + self.m_iBuilds = self.m_iBuilds + 1 + self:CleanList() + + if (self.Horizontal) then + Offset = self:HorizontalRebuild(Offset) + else + Offset = self:NormalRebuild(Offset) + end + + self:GetCanvas():SetTall(Offset + self.Padding - self.Spacing) + + if (self.m_bNoSizing and self:GetCanvas():GetTall() < self:GetTall()) then + self:GetCanvas():SetPos(0, (self:GetTall() - self:GetCanvas():GetTall()) * 0.5) + end +end + +function PANEL:OnMouseWheeled(dlta) + if (self.VBar) then return self.VBar:OnMouseWheeled(dlta) end +end + +function PANEL:Paint(w, h) + derma.SkinHook("Paint", "PanelList", self, w, h) + + return true +end + +function PANEL:OnVScroll(iOffset) + self.pnlCanvas:SetPos(0, iOffset) +end + +function PANEL:PerformLayout() + local Wide = self:GetWide() + local Tall = self.pnlCanvas:GetTall() + local YPos = 0 + + if (not self.Rebuild) then + debug.Trace() + end + + self:Rebuild() + + if (self.VBar) then + self.VBar:SetPos(self:GetWide() - 13, 0) + self.VBar:SetSize(13, self:GetTall()) + self.VBar:SetUp(self:GetTall(), self.pnlCanvas:GetTall()) + YPos = self.VBar:GetOffset() + + if not self.IgnoreVbar then + Wide = Wide - 13 + end + end + + self.pnlCanvas:SetPos(0, YPos) + self.pnlCanvas:SetWide(Wide) + + if (self:GetAutoSize()) then + self:SetTall(self.pnlCanvas:GetTall()) + self.pnlCanvas:SetPos(0, 0) + end + + if (self.VBar and not self:GetAutoSize() and Tall ~= self.pnlCanvas:GetTall()) then + self.VBar:SetScroll(self.VBar:GetScroll()) + end +end + +function PANEL:OnChildRemoved() + self:CleanList() + self:InvalidateLayout() +end + +function PANEL:ScrollToChild(panel) + local _, y = self.pnlCanvas:GetChildPosition(panel) + local _, h = panel:GetSize() + y = y + h * 0.5 + y = y - self:GetTall() * 0.5 + self.VBar:AnimateTo(y, 0.5, 0, 0.5) +end + +function PANEL:SortByMember(key, desc) + desc = desc or true + + table.sort(self.Items, function(a, b) + if (desc) then + local ta = a + local tb = b + a = tb + b = ta + end + + if (a[key] == nil) then return false end + if (b[key] == nil) then return true end + + return a[key] > b[key] + end) +end + +derma.DefineControl("MSDPanelList", "Fancy DpanelList", PANEL, "DPanel") \ No newline at end of file diff --git a/addons/msd_ui/materials/mce/icons/a.png b/addons/msd_ui/materials/mce/icons/a.png new file mode 100644 index 0000000..063e931 Binary files /dev/null and b/addons/msd_ui/materials/mce/icons/a.png differ diff --git a/addons/msd_ui/materials/mce/icons/b.png b/addons/msd_ui/materials/mce/icons/b.png new file mode 100644 index 0000000..a10f855 Binary files /dev/null and b/addons/msd_ui/materials/mce/icons/b.png differ diff --git a/addons/msd_ui/materials/mce/icons/c.png b/addons/msd_ui/materials/mce/icons/c.png new file mode 100644 index 0000000..cda738a Binary files /dev/null and b/addons/msd_ui/materials/mce/icons/c.png differ diff --git a/addons/msd_ui/materials/mce/logo64.png b/addons/msd_ui/materials/mce/logo64.png new file mode 100644 index 0000000..9f5025e Binary files /dev/null and b/addons/msd_ui/materials/mce/logo64.png differ diff --git a/addons/msd_ui/materials/mqs/icons/ammo.png b/addons/msd_ui/materials/mqs/icons/ammo.png new file mode 100644 index 0000000..40d9414 Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/ammo.png differ diff --git a/addons/msd_ui/materials/mqs/icons/box_open.png b/addons/msd_ui/materials/mqs/icons/box_open.png new file mode 100644 index 0000000..5de70e5 Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/box_open.png differ diff --git a/addons/msd_ui/materials/mqs/icons/box_open_star.png b/addons/msd_ui/materials/mqs/icons/box_open_star.png new file mode 100644 index 0000000..be6ca57 Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/box_open_star.png differ diff --git a/addons/msd_ui/materials/mqs/icons/heart.png b/addons/msd_ui/materials/mqs/icons/heart.png new file mode 100644 index 0000000..c2099ea Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/heart.png differ diff --git a/addons/msd_ui/materials/mqs/icons/info.png b/addons/msd_ui/materials/mqs/icons/info.png new file mode 100644 index 0000000..b858f91 Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/info.png differ diff --git a/addons/msd_ui/materials/mqs/icons/music_circle.png b/addons/msd_ui/materials/mqs/icons/music_circle.png new file mode 100644 index 0000000..ed18275 Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/music_circle.png differ diff --git a/addons/msd_ui/materials/mqs/icons/pin.png b/addons/msd_ui/materials/mqs/icons/pin.png new file mode 100644 index 0000000..7a80ba3 Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/pin.png differ diff --git a/addons/msd_ui/materials/mqs/icons/pistol.png b/addons/msd_ui/materials/mqs/icons/pistol.png new file mode 100644 index 0000000..b749d0a Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/pistol.png differ diff --git a/addons/msd_ui/materials/mqs/icons/pistol_remove.png b/addons/msd_ui/materials/mqs/icons/pistol_remove.png new file mode 100644 index 0000000..c7734af Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/pistol_remove.png differ diff --git a/addons/msd_ui/materials/mqs/icons/user_plus.png b/addons/msd_ui/materials/mqs/icons/user_plus.png new file mode 100644 index 0000000..114e27d Binary files /dev/null and b/addons/msd_ui/materials/mqs/icons/user_plus.png differ diff --git a/addons/msd_ui/materials/mqs/logo.png b/addons/msd_ui/materials/mqs/logo.png new file mode 100644 index 0000000..69239d3 Binary files /dev/null and b/addons/msd_ui/materials/mqs/logo.png differ diff --git a/addons/msd_ui/materials/mqs/logo64.png b/addons/msd_ui/materials/mqs/logo64.png new file mode 100644 index 0000000..15e0dd1 Binary files /dev/null and b/addons/msd_ui/materials/mqs/logo64.png differ diff --git a/addons/msd_ui/materials/mqs/logo_msd.png b/addons/msd_ui/materials/mqs/logo_msd.png new file mode 100644 index 0000000..be53d1b Binary files /dev/null and b/addons/msd_ui/materials/mqs/logo_msd.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a1.png b/addons/msd_ui/materials/mqs/map_markers/a1.png new file mode 100644 index 0000000..cf6369d Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a2.png b/addons/msd_ui/materials/mqs/map_markers/a2.png new file mode 100644 index 0000000..ccfaeef Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a3.png b/addons/msd_ui/materials/mqs/map_markers/a3.png new file mode 100644 index 0000000..de9a3c9 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a4.png b/addons/msd_ui/materials/mqs/map_markers/a4.png new file mode 100644 index 0000000..838ec65 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a5.png b/addons/msd_ui/materials/mqs/map_markers/a5.png new file mode 100644 index 0000000..68f6cfb Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a6.png b/addons/msd_ui/materials/mqs/map_markers/a6.png new file mode 100644 index 0000000..83a08d5 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a6.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a7.png b/addons/msd_ui/materials/mqs/map_markers/a7.png new file mode 100644 index 0000000..46cdecb Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a7.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/a8.png b/addons/msd_ui/materials/mqs/map_markers/a8.png new file mode 100644 index 0000000..898c885 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/a8.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/b1.png b/addons/msd_ui/materials/mqs/map_markers/b1.png new file mode 100644 index 0000000..5481bae Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/b1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/b2.png b/addons/msd_ui/materials/mqs/map_markers/b2.png new file mode 100644 index 0000000..8ba446f Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/b2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/b3.png b/addons/msd_ui/materials/mqs/map_markers/b3.png new file mode 100644 index 0000000..74890ea Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/b3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/b4.png b/addons/msd_ui/materials/mqs/map_markers/b4.png new file mode 100644 index 0000000..ebf4600 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/b4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/b5.png b/addons/msd_ui/materials/mqs/map_markers/b5.png new file mode 100644 index 0000000..d6fa2e2 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/b5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c1.png b/addons/msd_ui/materials/mqs/map_markers/c1.png new file mode 100644 index 0000000..dc11c62 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c2.png b/addons/msd_ui/materials/mqs/map_markers/c2.png new file mode 100644 index 0000000..eedf55b Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c3.png b/addons/msd_ui/materials/mqs/map_markers/c3.png new file mode 100644 index 0000000..718a95a Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c4.png b/addons/msd_ui/materials/mqs/map_markers/c4.png new file mode 100644 index 0000000..7d4444b Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c5.png b/addons/msd_ui/materials/mqs/map_markers/c5.png new file mode 100644 index 0000000..26c114c Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c6.png b/addons/msd_ui/materials/mqs/map_markers/c6.png new file mode 100644 index 0000000..d0e5e73 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c6.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c7.png b/addons/msd_ui/materials/mqs/map_markers/c7.png new file mode 100644 index 0000000..42cfbb0 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c7.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/c8.png b/addons/msd_ui/materials/mqs/map_markers/c8.png new file mode 100644 index 0000000..3835096 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/c8.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m1.png b/addons/msd_ui/materials/mqs/map_markers/m1.png new file mode 100644 index 0000000..4bdc41c Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m10.png b/addons/msd_ui/materials/mqs/map_markers/m10.png new file mode 100644 index 0000000..3aa4e83 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m10.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m11.png b/addons/msd_ui/materials/mqs/map_markers/m11.png new file mode 100644 index 0000000..9a3557e Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m11.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m12.png b/addons/msd_ui/materials/mqs/map_markers/m12.png new file mode 100644 index 0000000..5e980e9 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m12.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m13.png b/addons/msd_ui/materials/mqs/map_markers/m13.png new file mode 100644 index 0000000..02ca21f Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m13.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m14.png b/addons/msd_ui/materials/mqs/map_markers/m14.png new file mode 100644 index 0000000..8940244 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m14.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m15.png b/addons/msd_ui/materials/mqs/map_markers/m15.png new file mode 100644 index 0000000..276792e Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m15.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m16.png b/addons/msd_ui/materials/mqs/map_markers/m16.png new file mode 100644 index 0000000..fb4492b Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m16.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m17.png b/addons/msd_ui/materials/mqs/map_markers/m17.png new file mode 100644 index 0000000..90ce81d Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m17.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m18.png b/addons/msd_ui/materials/mqs/map_markers/m18.png new file mode 100644 index 0000000..ed93622 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m18.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m19.png b/addons/msd_ui/materials/mqs/map_markers/m19.png new file mode 100644 index 0000000..f1d12bf Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m19.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m2.png b/addons/msd_ui/materials/mqs/map_markers/m2.png new file mode 100644 index 0000000..af0bacc Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m3.png b/addons/msd_ui/materials/mqs/map_markers/m3.png new file mode 100644 index 0000000..71beced Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m4.png b/addons/msd_ui/materials/mqs/map_markers/m4.png new file mode 100644 index 0000000..d638d44 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m5.png b/addons/msd_ui/materials/mqs/map_markers/m5.png new file mode 100644 index 0000000..f7894c8 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m6.png b/addons/msd_ui/materials/mqs/map_markers/m6.png new file mode 100644 index 0000000..ee7119c Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m6.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m7.png b/addons/msd_ui/materials/mqs/map_markers/m7.png new file mode 100644 index 0000000..099d692 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m7.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m8.png b/addons/msd_ui/materials/mqs/map_markers/m8.png new file mode 100644 index 0000000..54eafa8 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m8.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/m9.png b/addons/msd_ui/materials/mqs/map_markers/m9.png new file mode 100644 index 0000000..0a8f621 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/m9.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/p1.png b/addons/msd_ui/materials/mqs/map_markers/p1.png new file mode 100644 index 0000000..3fa68c5 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/p1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/p2.png b/addons/msd_ui/materials/mqs/map_markers/p2.png new file mode 100644 index 0000000..5de59a9 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/p2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/r1.png b/addons/msd_ui/materials/mqs/map_markers/r1.png new file mode 100644 index 0000000..971eff8 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/r1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/r2.png b/addons/msd_ui/materials/mqs/map_markers/r2.png new file mode 100644 index 0000000..448e9a4 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/r2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/r3.png b/addons/msd_ui/materials/mqs/map_markers/r3.png new file mode 100644 index 0000000..7095c21 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/r3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/r4.png b/addons/msd_ui/materials/mqs/map_markers/r4.png new file mode 100644 index 0000000..3135240 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/r4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/r5.png b/addons/msd_ui/materials/mqs/map_markers/r5.png new file mode 100644 index 0000000..73ebf90 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/r5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/r6.png b/addons/msd_ui/materials/mqs/map_markers/r6.png new file mode 100644 index 0000000..67ab6ad Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/r6.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s1.png b/addons/msd_ui/materials/mqs/map_markers/s1.png new file mode 100644 index 0000000..86bb2f1 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s2.png b/addons/msd_ui/materials/mqs/map_markers/s2.png new file mode 100644 index 0000000..14a833f Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s3.png b/addons/msd_ui/materials/mqs/map_markers/s3.png new file mode 100644 index 0000000..c8d5c8a Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s4.png b/addons/msd_ui/materials/mqs/map_markers/s4.png new file mode 100644 index 0000000..847599e Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s5.png b/addons/msd_ui/materials/mqs/map_markers/s5.png new file mode 100644 index 0000000..4d95b86 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s6.png b/addons/msd_ui/materials/mqs/map_markers/s6.png new file mode 100644 index 0000000..3415250 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s6.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s7.png b/addons/msd_ui/materials/mqs/map_markers/s7.png new file mode 100644 index 0000000..af38674 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s7.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/s8.png b/addons/msd_ui/materials/mqs/map_markers/s8.png new file mode 100644 index 0000000..8994318 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/s8.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/t1.png b/addons/msd_ui/materials/mqs/map_markers/t1.png new file mode 100644 index 0000000..0a1f6fe Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/t1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/t2.png b/addons/msd_ui/materials/mqs/map_markers/t2.png new file mode 100644 index 0000000..dc7f90d Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/t2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/t3.png b/addons/msd_ui/materials/mqs/map_markers/t3.png new file mode 100644 index 0000000..fa22e96 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/t3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/t4.png b/addons/msd_ui/materials/mqs/map_markers/t4.png new file mode 100644 index 0000000..4b21945 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/t4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/t5.png b/addons/msd_ui/materials/mqs/map_markers/t5.png new file mode 100644 index 0000000..444b075 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/t5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v1.png b/addons/msd_ui/materials/mqs/map_markers/v1.png new file mode 100644 index 0000000..b2fdfbd Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v1.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v2.png b/addons/msd_ui/materials/mqs/map_markers/v2.png new file mode 100644 index 0000000..6128057 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v2.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v3.png b/addons/msd_ui/materials/mqs/map_markers/v3.png new file mode 100644 index 0000000..e67ff80 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v3.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v4.png b/addons/msd_ui/materials/mqs/map_markers/v4.png new file mode 100644 index 0000000..72dc8cc Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v4.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v5.png b/addons/msd_ui/materials/mqs/map_markers/v5.png new file mode 100644 index 0000000..f3d24ef Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v5.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v6.png b/addons/msd_ui/materials/mqs/map_markers/v6.png new file mode 100644 index 0000000..098c4a0 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v6.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v7.png b/addons/msd_ui/materials/mqs/map_markers/v7.png new file mode 100644 index 0000000..dfaeb0e Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v7.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v8.png b/addons/msd_ui/materials/mqs/map_markers/v8.png new file mode 100644 index 0000000..c34bb3d Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v8.png differ diff --git a/addons/msd_ui/materials/mqs/map_markers/v9.png b/addons/msd_ui/materials/mqs/map_markers/v9.png new file mode 100644 index 0000000..acafe35 Binary files /dev/null and b/addons/msd_ui/materials/mqs/map_markers/v9.png differ diff --git a/addons/msd_ui/materials/msd/gradient_right.png b/addons/msd_ui/materials/msd/gradient_right.png new file mode 100644 index 0000000..2232254 Binary files /dev/null and b/addons/msd_ui/materials/msd/gradient_right.png differ diff --git a/addons/msd_ui/materials/msd/icons/account-cash.png b/addons/msd_ui/materials/msd/icons/account-cash.png new file mode 100644 index 0000000..1234819 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/account-cash.png differ diff --git a/addons/msd_ui/materials/msd/icons/account-convert.png b/addons/msd_ui/materials/msd/icons/account-convert.png new file mode 100644 index 0000000..a5d7b6b Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/account-convert.png differ diff --git a/addons/msd_ui/materials/msd/icons/account-cowboy-hat.png b/addons/msd_ui/materials/msd/icons/account-cowboy-hat.png new file mode 100644 index 0000000..573fbf5 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/account-cowboy-hat.png differ diff --git a/addons/msd_ui/materials/msd/icons/account-edit.png b/addons/msd_ui/materials/msd/icons/account-edit.png new file mode 100644 index 0000000..2da5b30 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/account-edit.png differ diff --git a/addons/msd_ui/materials/msd/icons/account-multiple.png b/addons/msd_ui/materials/msd/icons/account-multiple.png new file mode 100644 index 0000000..ca4cafc Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/account-multiple.png differ diff --git a/addons/msd_ui/materials/msd/icons/account-plus.png b/addons/msd_ui/materials/msd/icons/account-plus.png new file mode 100644 index 0000000..539c6d8 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/account-plus.png differ diff --git a/addons/msd_ui/materials/msd/icons/account.png b/addons/msd_ui/materials/msd/icons/account.png new file mode 100644 index 0000000..98a5221 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/account.png differ diff --git a/addons/msd_ui/materials/msd/icons/alert-circle.png b/addons/msd_ui/materials/msd/icons/alert-circle.png new file mode 100644 index 0000000..31b040f Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/alert-circle.png differ diff --git a/addons/msd_ui/materials/msd/icons/arrow-left-bold.png b/addons/msd_ui/materials/msd/icons/arrow-left-bold.png new file mode 100644 index 0000000..88f4f91 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/arrow-left-bold.png differ diff --git a/addons/msd_ui/materials/msd/icons/arrow-right-bold.png b/addons/msd_ui/materials/msd/icons/arrow-right-bold.png new file mode 100644 index 0000000..1b114f1 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/arrow-right-bold.png differ diff --git a/addons/msd_ui/materials/msd/icons/arrow_down.png b/addons/msd_ui/materials/msd/icons/arrow_down.png new file mode 100644 index 0000000..23a2c19 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/arrow_down.png differ diff --git a/addons/msd_ui/materials/msd/icons/arrow_down_color.png b/addons/msd_ui/materials/msd/icons/arrow_down_color.png new file mode 100644 index 0000000..86dfb0e Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/arrow_down_color.png differ diff --git a/addons/msd_ui/materials/msd/icons/arrow_up.png b/addons/msd_ui/materials/msd/icons/arrow_up.png new file mode 100644 index 0000000..660bdf5 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/arrow_up.png differ diff --git a/addons/msd_ui/materials/msd/icons/back.png b/addons/msd_ui/materials/msd/icons/back.png new file mode 100644 index 0000000..2d65e35 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/back.png differ diff --git a/addons/msd_ui/materials/msd/icons/badge-account-horizontal.png b/addons/msd_ui/materials/msd/icons/badge-account-horizontal.png new file mode 100644 index 0000000..e59dcda Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/badge-account-horizontal.png differ diff --git a/addons/msd_ui/materials/msd/icons/badge-account.png b/addons/msd_ui/materials/msd/icons/badge-account.png new file mode 100644 index 0000000..19b00dd Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/badge-account.png differ diff --git a/addons/msd_ui/materials/msd/icons/baseball-diamond.png b/addons/msd_ui/materials/msd/icons/baseball-diamond.png new file mode 100644 index 0000000..1280f51 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/baseball-diamond.png differ diff --git a/addons/msd_ui/materials/msd/icons/basket.png b/addons/msd_ui/materials/msd/icons/basket.png new file mode 100644 index 0000000..b72bb8a Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/basket.png differ diff --git a/addons/msd_ui/materials/msd/icons/biohazard.png b/addons/msd_ui/materials/msd/icons/biohazard.png new file mode 100644 index 0000000..11da9e2 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/biohazard.png differ diff --git a/addons/msd_ui/materials/msd/icons/briefcase-check.png b/addons/msd_ui/materials/msd/icons/briefcase-check.png new file mode 100644 index 0000000..dc70ed7 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/briefcase-check.png differ diff --git a/addons/msd_ui/materials/msd/icons/briefcase-remove.png b/addons/msd_ui/materials/msd/icons/briefcase-remove.png new file mode 100644 index 0000000..2db7bf7 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/briefcase-remove.png differ diff --git a/addons/msd_ui/materials/msd/icons/briefcase.png b/addons/msd_ui/materials/msd/icons/briefcase.png new file mode 100644 index 0000000..86b7a2d Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/briefcase.png differ diff --git a/addons/msd_ui/materials/msd/icons/calendar-check.png b/addons/msd_ui/materials/msd/icons/calendar-check.png new file mode 100644 index 0000000..79e1c00 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/calendar-check.png differ diff --git a/addons/msd_ui/materials/msd/icons/cancel.png b/addons/msd_ui/materials/msd/icons/cancel.png new file mode 100644 index 0000000..85cc32b Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cancel.png differ diff --git a/addons/msd_ui/materials/msd/icons/cards-heart-outline.png b/addons/msd_ui/materials/msd/icons/cards-heart-outline.png new file mode 100644 index 0000000..892b20c Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cards-heart-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/cards-heart.png b/addons/msd_ui/materials/msd/icons/cards-heart.png new file mode 100644 index 0000000..29a9288 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cards-heart.png differ diff --git a/addons/msd_ui/materials/msd/icons/cart.png b/addons/msd_ui/materials/msd/icons/cart.png new file mode 100644 index 0000000..04334b9 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cart.png differ diff --git a/addons/msd_ui/materials/msd/icons/cash.png b/addons/msd_ui/materials/msd/icons/cash.png new file mode 100644 index 0000000..c846e53 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cash.png differ diff --git a/addons/msd_ui/materials/msd/icons/cellphone-basic.png b/addons/msd_ui/materials/msd/icons/cellphone-basic.png new file mode 100644 index 0000000..e346388 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cellphone-basic.png differ diff --git a/addons/msd_ui/materials/msd/icons/cellphone-text.png b/addons/msd_ui/materials/msd/icons/cellphone-text.png new file mode 100644 index 0000000..5bc4b7e Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cellphone-text.png differ diff --git a/addons/msd_ui/materials/msd/icons/cellphone.png b/addons/msd_ui/materials/msd/icons/cellphone.png new file mode 100644 index 0000000..0f3232e Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cellphone.png differ diff --git a/addons/msd_ui/materials/msd/icons/check-all.png b/addons/msd_ui/materials/msd/icons/check-all.png new file mode 100644 index 0000000..4c03a12 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/check-all.png differ diff --git a/addons/msd_ui/materials/msd/icons/check-bold.png b/addons/msd_ui/materials/msd/icons/check-bold.png new file mode 100644 index 0000000..88defe2 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/check-bold.png differ diff --git a/addons/msd_ui/materials/msd/icons/check-decagram.png b/addons/msd_ui/materials/msd/icons/check-decagram.png new file mode 100644 index 0000000..eec46c4 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/check-decagram.png differ diff --git a/addons/msd_ui/materials/msd/icons/check.png b/addons/msd_ui/materials/msd/icons/check.png new file mode 100644 index 0000000..c1e3422 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/check.png differ diff --git a/addons/msd_ui/materials/msd/icons/clipboard-arrow-up.png b/addons/msd_ui/materials/msd/icons/clipboard-arrow-up.png new file mode 100644 index 0000000..46d7fa2 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/clipboard-arrow-up.png differ diff --git a/addons/msd_ui/materials/msd/icons/close-box.png b/addons/msd_ui/materials/msd/icons/close-box.png new file mode 100644 index 0000000..38a379c Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/close-box.png differ diff --git a/addons/msd_ui/materials/msd/icons/close-thick.png b/addons/msd_ui/materials/msd/icons/close-thick.png new file mode 100644 index 0000000..b2e32d2 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/close-thick.png differ diff --git a/addons/msd_ui/materials/msd/icons/cog.png b/addons/msd_ui/materials/msd/icons/cog.png new file mode 100644 index 0000000..6fb409e Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cog.png differ diff --git a/addons/msd_ui/materials/msd/icons/compass-rose.png b/addons/msd_ui/materials/msd/icons/compass-rose.png new file mode 100644 index 0000000..b0019d0 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/compass-rose.png differ diff --git a/addons/msd_ui/materials/msd/icons/console.png b/addons/msd_ui/materials/msd/icons/console.png new file mode 100644 index 0000000..90dd105 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/console.png differ diff --git a/addons/msd_ui/materials/msd/icons/content-copy.png b/addons/msd_ui/materials/msd/icons/content-copy.png new file mode 100644 index 0000000..597748c Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/content-copy.png differ diff --git a/addons/msd_ui/materials/msd/icons/content-save.png b/addons/msd_ui/materials/msd/icons/content-save.png new file mode 100644 index 0000000..797e42a Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/content-save.png differ diff --git a/addons/msd_ui/materials/msd/icons/cross.png b/addons/msd_ui/materials/msd/icons/cross.png new file mode 100644 index 0000000..0b5005a Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cross.png differ diff --git a/addons/msd_ui/materials/msd/icons/cup-outline.png b/addons/msd_ui/materials/msd/icons/cup-outline.png new file mode 100644 index 0000000..657c530 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cup-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/cup.png b/addons/msd_ui/materials/msd/icons/cup.png new file mode 100644 index 0000000..342d0f9 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/cup.png differ diff --git a/addons/msd_ui/materials/msd/icons/debug-step-over.png b/addons/msd_ui/materials/msd/icons/debug-step-over.png new file mode 100644 index 0000000..bbe4340 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/debug-step-over.png differ diff --git a/addons/msd_ui/materials/msd/icons/diamond-stone.png b/addons/msd_ui/materials/msd/icons/diamond-stone.png new file mode 100644 index 0000000..75d7486 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/diamond-stone.png differ diff --git a/addons/msd_ui/materials/msd/icons/dice.png b/addons/msd_ui/materials/msd/icons/dice.png new file mode 100644 index 0000000..51227eb Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/dice.png differ diff --git a/addons/msd_ui/materials/msd/icons/discord.png b/addons/msd_ui/materials/msd/icons/discord.png new file mode 100644 index 0000000..b93b992 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/discord.png differ diff --git a/addons/msd_ui/materials/msd/icons/door.png b/addons/msd_ui/materials/msd/icons/door.png new file mode 100644 index 0000000..66d5f09 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/door.png differ diff --git a/addons/msd_ui/materials/msd/icons/dot.png b/addons/msd_ui/materials/msd/icons/dot.png new file mode 100644 index 0000000..1aeaa91 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/dot.png differ diff --git a/addons/msd_ui/materials/msd/icons/download-circle.png b/addons/msd_ui/materials/msd/icons/download-circle.png new file mode 100644 index 0000000..9ab0dcf Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/download-circle.png differ diff --git a/addons/msd_ui/materials/msd/icons/download.png b/addons/msd_ui/materials/msd/icons/download.png new file mode 100644 index 0000000..52bebb5 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/download.png differ diff --git a/addons/msd_ui/materials/msd/icons/engine.png b/addons/msd_ui/materials/msd/icons/engine.png new file mode 100644 index 0000000..1df716e Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/engine.png differ diff --git a/addons/msd_ui/materials/msd/icons/exit-to-app.png b/addons/msd_ui/materials/msd/icons/exit-to-app.png new file mode 100644 index 0000000..61e78fa Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/exit-to-app.png differ diff --git a/addons/msd_ui/materials/msd/icons/eye.png b/addons/msd_ui/materials/msd/icons/eye.png new file mode 100644 index 0000000..6e3f395 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/eye.png differ diff --git a/addons/msd_ui/materials/msd/icons/face-agent.png b/addons/msd_ui/materials/msd/icons/face-agent.png new file mode 100644 index 0000000..e005532 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/face-agent.png differ diff --git a/addons/msd_ui/materials/msd/icons/file-document.png b/addons/msd_ui/materials/msd/icons/file-document.png new file mode 100644 index 0000000..2cbc374 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/file-document.png differ diff --git a/addons/msd_ui/materials/msd/icons/file-hidden.png b/addons/msd_ui/materials/msd/icons/file-hidden.png new file mode 100644 index 0000000..04ff8e5 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/file-hidden.png differ diff --git a/addons/msd_ui/materials/msd/icons/folder-open.png b/addons/msd_ui/materials/msd/icons/folder-open.png new file mode 100644 index 0000000..10e2210 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/folder-open.png differ diff --git a/addons/msd_ui/materials/msd/icons/food-apple-outline.png b/addons/msd_ui/materials/msd/icons/food-apple-outline.png new file mode 100644 index 0000000..03b4249 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/food-apple-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/food-apple.png b/addons/msd_ui/materials/msd/icons/food-apple.png new file mode 100644 index 0000000..1802bbc Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/food-apple.png differ diff --git a/addons/msd_ui/materials/msd/icons/food-off-outline.png b/addons/msd_ui/materials/msd/icons/food-off-outline.png new file mode 100644 index 0000000..86f0f9a Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/food-off-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/food-outline.png b/addons/msd_ui/materials/msd/icons/food-outline.png new file mode 100644 index 0000000..36f6d0a Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/food-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/food.png b/addons/msd_ui/materials/msd/icons/food.png new file mode 100644 index 0000000..1e2f675 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/food.png differ diff --git a/addons/msd_ui/materials/msd/icons/gas-station.png b/addons/msd_ui/materials/msd/icons/gas-station.png new file mode 100644 index 0000000..123ef9f Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/gas-station.png differ diff --git a/addons/msd_ui/materials/msd/icons/glass-mug-variant.png b/addons/msd_ui/materials/msd/icons/glass-mug-variant.png new file mode 100644 index 0000000..6e924cf Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/glass-mug-variant.png differ diff --git a/addons/msd_ui/materials/msd/icons/hand-coin.png b/addons/msd_ui/materials/msd/icons/hand-coin.png new file mode 100644 index 0000000..ec4fecc Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/hand-coin.png differ diff --git a/addons/msd_ui/materials/msd/icons/hand-okay.png b/addons/msd_ui/materials/msd/icons/hand-okay.png new file mode 100644 index 0000000..08e6106 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/hand-okay.png differ diff --git a/addons/msd_ui/materials/msd/icons/hand-peace-variant.png b/addons/msd_ui/materials/msd/icons/hand-peace-variant.png new file mode 100644 index 0000000..ca4396f Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/hand-peace-variant.png differ diff --git a/addons/msd_ui/materials/msd/icons/hand-right.png b/addons/msd_ui/materials/msd/icons/hand-right.png new file mode 100644 index 0000000..39ec655 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/hand-right.png differ diff --git a/addons/msd_ui/materials/msd/icons/hazard-lights.png b/addons/msd_ui/materials/msd/icons/hazard-lights.png new file mode 100644 index 0000000..2764a0c Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/hazard-lights.png differ diff --git a/addons/msd_ui/materials/msd/icons/heart-broken.png b/addons/msd_ui/materials/msd/icons/heart-broken.png new file mode 100644 index 0000000..736f327 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/heart-broken.png differ diff --git a/addons/msd_ui/materials/msd/icons/heart-flash.png b/addons/msd_ui/materials/msd/icons/heart-flash.png new file mode 100644 index 0000000..4b50fd9 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/heart-flash.png differ diff --git a/addons/msd_ui/materials/msd/icons/home.png b/addons/msd_ui/materials/msd/icons/home.png new file mode 100644 index 0000000..cc84bb5 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/home.png differ diff --git a/addons/msd_ui/materials/msd/icons/human-female-dance.png b/addons/msd_ui/materials/msd/icons/human-female-dance.png new file mode 100644 index 0000000..90dd865 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/human-female-dance.png differ diff --git a/addons/msd_ui/materials/msd/icons/human-female.png b/addons/msd_ui/materials/msd/icons/human-female.png new file mode 100644 index 0000000..bf7c5d9 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/human-female.png differ diff --git a/addons/msd_ui/materials/msd/icons/human-male.png b/addons/msd_ui/materials/msd/icons/human-male.png new file mode 100644 index 0000000..cc3f066 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/human-male.png differ diff --git a/addons/msd_ui/materials/msd/icons/key-arrow-right.png b/addons/msd_ui/materials/msd/icons/key-arrow-right.png new file mode 100644 index 0000000..cfde96e Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/key-arrow-right.png differ diff --git a/addons/msd_ui/materials/msd/icons/key-link.png b/addons/msd_ui/materials/msd/icons/key-link.png new file mode 100644 index 0000000..44e7ab8 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/key-link.png differ diff --git a/addons/msd_ui/materials/msd/icons/key-plus.png b/addons/msd_ui/materials/msd/icons/key-plus.png new file mode 100644 index 0000000..32b8507 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/key-plus.png differ diff --git a/addons/msd_ui/materials/msd/icons/key-remove.png b/addons/msd_ui/materials/msd/icons/key-remove.png new file mode 100644 index 0000000..41d8265 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/key-remove.png differ diff --git a/addons/msd_ui/materials/msd/icons/key-star.png b/addons/msd_ui/materials/msd/icons/key-star.png new file mode 100644 index 0000000..dfe02a0 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/key-star.png differ diff --git a/addons/msd_ui/materials/msd/icons/key-variant.png b/addons/msd_ui/materials/msd/icons/key-variant.png new file mode 100644 index 0000000..5ca5cf9 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/key-variant.png differ diff --git a/addons/msd_ui/materials/msd/icons/lacoin.png b/addons/msd_ui/materials/msd/icons/lacoin.png new file mode 100644 index 0000000..54b1588 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/lacoin.png differ diff --git a/addons/msd_ui/materials/msd/icons/layers-plus.png b/addons/msd_ui/materials/msd/icons/layers-plus.png new file mode 100644 index 0000000..9de661f Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/layers-plus.png differ diff --git a/addons/msd_ui/materials/msd/icons/layers-remove.png b/addons/msd_ui/materials/msd/icons/layers-remove.png new file mode 100644 index 0000000..664380a Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/layers-remove.png differ diff --git a/addons/msd_ui/materials/msd/icons/layers.png b/addons/msd_ui/materials/msd/icons/layers.png new file mode 100644 index 0000000..01e0f1d Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/layers.png differ diff --git a/addons/msd_ui/materials/msd/icons/lightning-bolt.png b/addons/msd_ui/materials/msd/icons/lightning-bolt.png new file mode 100644 index 0000000..68f6e26 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/lightning-bolt.png differ diff --git a/addons/msd_ui/materials/msd/icons/lock-open.png b/addons/msd_ui/materials/msd/icons/lock-open.png new file mode 100644 index 0000000..f3819b2 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/lock-open.png differ diff --git a/addons/msd_ui/materials/msd/icons/lock.png b/addons/msd_ui/materials/msd/icons/lock.png new file mode 100644 index 0000000..335f874 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/lock.png differ diff --git a/addons/msd_ui/materials/msd/icons/magazine-pistol.png b/addons/msd_ui/materials/msd/icons/magazine-pistol.png new file mode 100644 index 0000000..c1fc954 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/magazine-pistol.png differ diff --git a/addons/msd_ui/materials/msd/icons/menu.png b/addons/msd_ui/materials/msd/icons/menu.png new file mode 100644 index 0000000..30cadb6 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/menu.png differ diff --git a/addons/msd_ui/materials/msd/icons/pause-circle-outline.png b/addons/msd_ui/materials/msd/icons/pause-circle-outline.png new file mode 100644 index 0000000..265c57e Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/pause-circle-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/pencil.png b/addons/msd_ui/materials/msd/icons/pencil.png new file mode 100644 index 0000000..a1374a8 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/pencil.png differ diff --git a/addons/msd_ui/materials/msd/icons/pistol.png b/addons/msd_ui/materials/msd/icons/pistol.png new file mode 100644 index 0000000..1bc92cd Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/pistol.png differ diff --git a/addons/msd_ui/materials/msd/icons/play-circle-outline.png b/addons/msd_ui/materials/msd/icons/play-circle-outline.png new file mode 100644 index 0000000..f55de24 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/play-circle-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/play.png b/addons/msd_ui/materials/msd/icons/play.png new file mode 100644 index 0000000..ff028ed Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/play.png differ diff --git a/addons/msd_ui/materials/msd/icons/playlist-edit.png b/addons/msd_ui/materials/msd/icons/playlist-edit.png new file mode 100644 index 0000000..b2321d2 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/playlist-edit.png differ diff --git a/addons/msd_ui/materials/msd/icons/plus.png b/addons/msd_ui/materials/msd/icons/plus.png new file mode 100644 index 0000000..223dc73 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/plus.png differ diff --git a/addons/msd_ui/materials/msd/icons/police-outline.png b/addons/msd_ui/materials/msd/icons/police-outline.png new file mode 100644 index 0000000..c490923 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/police-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/police.png b/addons/msd_ui/materials/msd/icons/police.png new file mode 100644 index 0000000..2d000c8 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/police.png differ diff --git a/addons/msd_ui/materials/msd/icons/rank.png b/addons/msd_ui/materials/msd/icons/rank.png new file mode 100644 index 0000000..59275b0 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/rank.png differ diff --git a/addons/msd_ui/materials/msd/icons/rank_low.png b/addons/msd_ui/materials/msd/icons/rank_low.png new file mode 100644 index 0000000..04d3375 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/rank_low.png differ diff --git a/addons/msd_ui/materials/msd/icons/rank_sup.png b/addons/msd_ui/materials/msd/icons/rank_sup.png new file mode 100644 index 0000000..cfa8cbb Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/rank_sup.png differ diff --git a/addons/msd_ui/materials/msd/icons/reload-alert.png b/addons/msd_ui/materials/msd/icons/reload-alert.png new file mode 100644 index 0000000..a762a77 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/reload-alert.png differ diff --git a/addons/msd_ui/materials/msd/icons/reload.png b/addons/msd_ui/materials/msd/icons/reload.png new file mode 100644 index 0000000..169cbf6 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/reload.png differ diff --git a/addons/msd_ui/materials/msd/icons/run.png b/addons/msd_ui/materials/msd/icons/run.png new file mode 100644 index 0000000..593dcb2 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/run.png differ diff --git a/addons/msd_ui/materials/msd/icons/seal.png b/addons/msd_ui/materials/msd/icons/seal.png new file mode 100644 index 0000000..2c8f1bd Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/seal.png differ diff --git a/addons/msd_ui/materials/msd/icons/shopping.png b/addons/msd_ui/materials/msd/icons/shopping.png new file mode 100644 index 0000000..6302f6c Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/shopping.png differ diff --git a/addons/msd_ui/materials/msd/icons/silverware-clean.png b/addons/msd_ui/materials/msd/icons/silverware-clean.png new file mode 100644 index 0000000..241375f Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/silverware-clean.png differ diff --git a/addons/msd_ui/materials/msd/icons/snowflake.png b/addons/msd_ui/materials/msd/icons/snowflake.png new file mode 100644 index 0000000..1bffa99 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/snowflake.png differ diff --git a/addons/msd_ui/materials/msd/icons/steam.png b/addons/msd_ui/materials/msd/icons/steam.png new file mode 100644 index 0000000..cf7c4cf Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/steam.png differ diff --git a/addons/msd_ui/materials/msd/icons/stop-circle-outline.png b/addons/msd_ui/materials/msd/icons/stop-circle-outline.png new file mode 100644 index 0000000..340cb33 Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/stop-circle-outline.png differ diff --git a/addons/msd_ui/materials/msd/icons/swap.png b/addons/msd_ui/materials/msd/icons/swap.png new file mode 100644 index 0000000..da5e2bf Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/swap.png differ diff --git a/addons/msd_ui/materials/msd/icons/vk.png b/addons/msd_ui/materials/msd/icons/vk.png new file mode 100644 index 0000000..fd394fa Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/vk.png differ diff --git a/addons/msd_ui/materials/msd/icons/web.png b/addons/msd_ui/materials/msd/icons/web.png new file mode 100644 index 0000000..e41f4ce Binary files /dev/null and b/addons/msd_ui/materials/msd/icons/web.png differ diff --git a/addons/msd_ui/materials/msd/macnco.png b/addons/msd_ui/materials/msd/macnco.png new file mode 100644 index 0000000..c0db8f6 Binary files /dev/null and b/addons/msd_ui/materials/msd/macnco.png differ diff --git a/addons/msd_ui/materials/msd/stains.png b/addons/msd_ui/materials/msd/stains.png new file mode 100644 index 0000000..391a71c Binary files /dev/null and b/addons/msd_ui/materials/msd/stains.png differ diff --git a/addons/msd_ui/materials/msd/vignette.png b/addons/msd_ui/materials/msd/vignette.png new file mode 100644 index 0000000..927cd8a Binary files /dev/null and b/addons/msd_ui/materials/msd/vignette.png differ diff --git a/addons/msd_ui/resource/fonts/AdihausDIN.ttf b/addons/msd_ui/resource/fonts/AdihausDIN.ttf new file mode 100644 index 0000000..e2bf6e2 Binary files /dev/null and b/addons/msd_ui/resource/fonts/AdihausDIN.ttf differ diff --git a/addons/msd_ui/resource/fonts/Square721Cyr.ttf b/addons/msd_ui/resource/fonts/Square721Cyr.ttf new file mode 100644 index 0000000..be0e8fd Binary files /dev/null and b/addons/msd_ui/resource/fonts/Square721Cyr.ttf differ diff --git a/addons/msd_ui/sound/mqs/done/1.mp3 b/addons/msd_ui/sound/mqs/done/1.mp3 new file mode 100644 index 0000000..a13fcb7 Binary files /dev/null and b/addons/msd_ui/sound/mqs/done/1.mp3 differ diff --git a/addons/msd_ui/sound/mqs/done/2.mp3 b/addons/msd_ui/sound/mqs/done/2.mp3 new file mode 100644 index 0000000..7c6d1b9 Binary files /dev/null and b/addons/msd_ui/sound/mqs/done/2.mp3 differ diff --git a/addons/msd_ui/sound/mqs/done/3.mp3 b/addons/msd_ui/sound/mqs/done/3.mp3 new file mode 100644 index 0000000..47baba7 Binary files /dev/null and b/addons/msd_ui/sound/mqs/done/3.mp3 differ diff --git a/addons/msd_ui/sound/mqs/done/4.mp3 b/addons/msd_ui/sound/mqs/done/4.mp3 new file mode 100644 index 0000000..af06a33 Binary files /dev/null and b/addons/msd_ui/sound/mqs/done/4.mp3 differ diff --git a/addons/msd_ui/sound/mqs/fail/1.mp3 b/addons/msd_ui/sound/mqs/fail/1.mp3 new file mode 100644 index 0000000..37d2ea4 Binary files /dev/null and b/addons/msd_ui/sound/mqs/fail/1.mp3 differ diff --git a/addons/msd_ui/sound/mqs/fail/2.mp3 b/addons/msd_ui/sound/mqs/fail/2.mp3 new file mode 100644 index 0000000..c92ff52 Binary files /dev/null and b/addons/msd_ui/sound/mqs/fail/2.mp3 differ diff --git a/addons/msd_ui/sound/mqs/fail/3.mp3 b/addons/msd_ui/sound/mqs/fail/3.mp3 new file mode 100644 index 0000000..8a0a50a Binary files /dev/null and b/addons/msd_ui/sound/mqs/fail/3.mp3 differ diff --git a/addons/msd_ui/sound/mqs/fail/4.mp3 b/addons/msd_ui/sound/mqs/fail/4.mp3 new file mode 100644 index 0000000..5453902 Binary files /dev/null and b/addons/msd_ui/sound/mqs/fail/4.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/1.mp3 b/addons/msd_ui/sound/mqs/notify/1.mp3 new file mode 100644 index 0000000..743cb10 Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/1.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/1_1.mp3 b/addons/msd_ui/sound/mqs/notify/1_1.mp3 new file mode 100644 index 0000000..eb30088 Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/1_1.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/2.mp3 b/addons/msd_ui/sound/mqs/notify/2.mp3 new file mode 100644 index 0000000..04bb6b4 Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/2.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/2_1.mp3 b/addons/msd_ui/sound/mqs/notify/2_1.mp3 new file mode 100644 index 0000000..fe26789 Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/2_1.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/3.mp3 b/addons/msd_ui/sound/mqs/notify/3.mp3 new file mode 100644 index 0000000..9762279 Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/3.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/3_1.mp3 b/addons/msd_ui/sound/mqs/notify/3_1.mp3 new file mode 100644 index 0000000..e0f46b1 Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/3_1.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/4.mp3 b/addons/msd_ui/sound/mqs/notify/4.mp3 new file mode 100644 index 0000000..cd3ee4a Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/4.mp3 differ diff --git a/addons/msd_ui/sound/mqs/notify/5.mp3 b/addons/msd_ui/sound/mqs/notify/5.mp3 new file mode 100644 index 0000000..1fdff34 Binary files /dev/null and b/addons/msd_ui/sound/mqs/notify/5.mp3 differ diff --git a/addons/msd_ui/sound/mqs/start/1.mp3 b/addons/msd_ui/sound/mqs/start/1.mp3 new file mode 100644 index 0000000..7cf290e Binary files /dev/null and b/addons/msd_ui/sound/mqs/start/1.mp3 differ diff --git a/addons/msd_ui/sound/mqs/start/2.mp3 b/addons/msd_ui/sound/mqs/start/2.mp3 new file mode 100644 index 0000000..6141320 Binary files /dev/null and b/addons/msd_ui/sound/mqs/start/2.mp3 differ diff --git a/addons/msd_ui/sound/mqs/start/3.mp3 b/addons/msd_ui/sound/mqs/start/3.mp3 new file mode 100644 index 0000000..aea5c81 Binary files /dev/null and b/addons/msd_ui/sound/mqs/start/3.mp3 differ diff --git a/addons/msd_ui/sound/msd/ui/click.wav b/addons/msd_ui/sound/msd/ui/click.wav new file mode 100644 index 0000000..13f127b Binary files /dev/null and b/addons/msd_ui/sound/msd/ui/click.wav differ diff --git a/addons/msd_ui/sound/msd/ui/enter.wav b/addons/msd_ui/sound/msd/ui/enter.wav new file mode 100644 index 0000000..2183344 Binary files /dev/null and b/addons/msd_ui/sound/msd/ui/enter.wav differ diff --git a/addons/msd_ui/sound/msd/ui/error.wav b/addons/msd_ui/sound/msd/ui/error.wav new file mode 100644 index 0000000..d8d9a2b Binary files /dev/null and b/addons/msd_ui/sound/msd/ui/error.wav differ diff --git a/addons/msd_ui/sound/msd/ui/return.wav b/addons/msd_ui/sound/msd/ui/return.wav new file mode 100644 index 0000000..15efb7c Binary files /dev/null and b/addons/msd_ui/sound/msd/ui/return.wav differ diff --git a/addons/msd_ui/sound/msd/ui/toggle.wav b/addons/msd_ui/sound/msd/ui/toggle.wav new file mode 100644 index 0000000..6987237 Binary files /dev/null and b/addons/msd_ui/sound/msd/ui/toggle.wav differ diff --git a/addons/nyxteam_printer/lua/entities/nyxteam_printer/cl_init.lua b/addons/nyxteam_printer/lua/entities/nyxteam_printer/cl_init.lua new file mode 100644 index 0000000..158068c --- /dev/null +++ b/addons/nyxteam_printer/lua/entities/nyxteam_printer/cl_init.lua @@ -0,0 +1,541 @@ +--[[ + + _ ___ ____ __ _______ ______ __ __ + | \ | \ \ / /\ \ / / |__ __| ____| /\ | \/ | + | \| |\ \_/ / \ V / | | | |__ / \ | \ / | + | . ` | \ / > < | | | __| / /\ \ | |\/| | + | |\ | | | / . \ | | | |____ / ____ \| | | | + |_| \_| |_| /_/ \_\ |_| |______/_/ \_\_| |_| + + JOIN DISCORD: https://discord.gg/rUEEz4mfXw + BY NYX TEAM (MaryBlackfild lead) + Copyrighted Nyx Team + YouGame.biz + +--]] + +include("shared.lua") + +local CSND = ENT.SND or {} +local RNDX = _G.gSims_RNDX +if not RNDX then RNDX = include("libnyx/lib/rndx.lua") end + +local MAT_MONEY = Material("icon16/money.png") +local MAT_STAR = Material("icon16/star.png") +local MAT_GRAD = Material("gui/gradient_down") + +local COL = { + white = Color(255, 255, 255), + text_dim = Color(255, 255, 255, 100), + accent = Color(65, 120, 255), + red = Color(255, 80, 80), + green = Color(80, 220, 120), + mint = Color(130, 235, 160), + orange = Color(255, 160, 60), + purple = Color(160, 100, 255), + dark_bg = Color(12, 12, 18, 250), + glass_top = Color(255, 255, 255, 10) +} + +local function DrawGrid(x, y, w, h, size, alpha) + surface.SetDrawColor(255, 255, 255, alpha) + for i = 0, w, size do surface.DrawLine(x + i, y, x + i, y + h) end + for i = 0, h, size do surface.DrawLine(x, y + i, x + w, y + i) end +end + +local function SpawnParticles(pos, type) + local em = ParticleEmitter(pos) + local mat = (type == 1) and MAT_MONEY or MAT_STAR + local col = (type == 1) and Color(255,255,255) or Color(255,220,100) + for i = 1, 15 do + local p = em:Add(mat, pos + VectorRand() * 5) + p:SetVelocity(VectorRand() * 40 + Vector(0,0,60)) + p:SetDieTime(math.Rand(1.5, 2.5)) + p:SetStartAlpha(255) + p:SetEndAlpha(0) + p:SetStartSize(12) + p:SetEndSize(0) + p:SetRoll(math.Rand(0, 360)) + p:SetGravity(Vector(0,0,-150)) + p:SetColor(col.r, col.g, col.b) + end + em:Finish() +end + +local function CreateFonts() + local sizes = {10, 12, 14, 16, 18, 20, 24, 32, 48, 54} + for _, sz in ipairs(sizes) do + surface.CreateFont("gSims.Manrope." .. sz, { font = "Manrope", size = sz, weight = 600, extended = true, antialias = true }) + end + surface.CreateFont("gSims.Manrope.Bold", { font = "Manrope", size = 22, weight = 800, extended = true, antialias = true }) + surface.CreateFont("gSims.Manrope.Display", { font = "Manrope", size = 54, weight = 800, extended = true, antialias = true }) +end +CreateFonts() + +local function SendAct(ent, act, arg) + net.Start("gsims_printer_action") + net.WriteEntity(ent) + net.WriteString(act) + net.WriteString(arg or "") + net.SendToServer() +end + +local function RegisterAnim(pnl, startTime, delay) + local oldPaint = pnl.Paint + pnl.Paint = function(s, w, h) + local anim = math.ease.OutCubic(math.Clamp((SysTime() - startTime - delay) / 0.25, 0, 1)) + if anim <= 0 then return end + s:SetAlpha(255 * anim) + local m = Matrix() + m:Translate(Vector(0, (1 - anim) * 15, 0)) + cam.PushModelMatrix(m) + oldPaint(s, w, h) + cam.PopModelMatrix() + end +end + +local function CreateGlassButton(parent, x, y, w, h, textArg, font, colArg, onClick, extraPaint) + local btn = vgui.Create("DButton", parent) + btn:SetPos(x, y) + btn:SetSize(w, h) + btn:SetText("") + local hov, ripple = 0, 0 + local rippleX, rippleY = 0, 0 + + btn.Paint = function(s, bw, bh) + hov = math.Approach(hov, s:IsHovered() and 1 or 0, FrameTime() * 10) + local col, txt = (isfunction(colArg) and colArg() or colArg), (isfunction(textArg) and textArg() or textArg) + + RNDX().Liquid(0, 0, bw, bh) + :Rad(12) + :Tint(30, 30, 40) + :TintStrength(0.6) + :Saturation(1.06) + :GlassBlur(0.1, 0) + :EdgeSmooth(2) + :Strength(0.019) + :Speed(0.4) + :Shimmer(25.7) + :Grain(0.01) + :Alpha(0.8 + (hov * 0.1)) + :Flags(RNDX.SHAPE_IOS) + :Draw() + + RNDX().Rect(0, 0, bw, bh):Rad(12):Outline(1):Color(col.r, col.g, col.b, 40 + hov * 50):Flags(RNDX.SHAPE_IOS):Draw() + + if ripple > 0 then + ripple = math.Approach(ripple, 0, FrameTime() * 1.5) + local rw = bw * (1 - ripple) * 2.5 + local old = DisableClipping(false) + surface.SetDrawColor(col.r, col.g, col.b, ripple * 150) + draw.NoTexture() + surface.DrawOutlinedRect(rippleX - rw/2, rippleY - rw/2, rw, rw, 2) + DisableClipping(old) + end + + if extraPaint then extraPaint(s, bw, bh, hov) else + draw.SimpleText(txt, font, bw / 2, bh / 2, Color(255, 255, 255, 220 + hov * 35), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + btn.DoClick = function(s) ripple = 1 rippleX, rippleY = s:CursorPos() if onClick then onClick() end end + return btn +end + +local function CreateBar(parent, x, y, w, h, label, color, getter, ent, actName) + local pnl = vgui.Create("DPanel", parent) + pnl:SetPos(x, y) + pnl:SetSize(w, h) + pnl.Paint = function(s, bw, bh) + local val = getter() + local pct = math.Clamp(val / 100, 0, 1) + RNDX().Rect(0, 18, bw, bh - 18):Rad(6):Color(0, 0, 0, 60):Draw() + if pct > 0 then RNDX().Rect(0, 18, bw * pct, bh - 18):Rad(6):Color(color):Draw() end + draw.SimpleText(label, "gSims.Manrope.14", 0, 0, COL.text_dim, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(math.floor(val) .. "%", "gSims.Manrope.14", bw, 0, COL.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + if s:IsHovered() then RNDX().Rect(0, 18, bw, bh - 18):Rad(6):Outline(1):Color(255, 255, 255, 50):Draw() end + end + pnl.NextAct = 0 + pnl.Think = function(s) + if s:IsHovered() and input.IsMouseDown(MOUSE_LEFT) and CurTime() > s.NextAct then + SendAct(ent, actName) s.NextAct = CurTime() + 0.1 + end + end + return pnl +end + +local function CreateSlotButton(parent, ent, slotId, x, y, size) + local btn = vgui.Create("DButton", parent) + btn:SetPos(x, y) + btn:SetSize(size, size) + btn:SetText("") + local next_lab = {A="B", B="C", C="A"} + local flipState, flipProgress = 0, 0 + local displayChar = ent.SLOTS[ent["GetSlot" .. slotId](ent)] or "A" + + btn.Think = function(s) + local realChar = ent.SLOTS[ent["GetSlot" .. slotId](ent)] or "A" + if flipState == 1 then + flipProgress = math.Approach(flipProgress, 1, FrameTime() * 4) + if flipProgress >= 0.5 then displayChar = realChar end + if flipProgress >= 1 then flipState, flipProgress = 0, 0 end + else + if displayChar ~= realChar then flipState, flipProgress = 1, 0 end + end + end + + btn.Paint = function(s, w, h) + local scaleY = flipState == 1 and math.abs(math.cos(flipProgress * math.pi)) or 1 + local m = Matrix() + m:Translate(Vector(w/2, h/2, 0)) m:Scale(Vector(1, scaleY, 1)) m:Translate(-Vector(w/2, h/2, 0)) + cam.PushModelMatrix(m) + + RNDX().Liquid(0, 0, w, h) + :Rad(w/2) + :Tint(30, 30, 40) + :TintStrength(0.6) + :Saturation(1.06) + :GlassBlur(0.1, 0) + :EdgeSmooth(2) + :Strength(0.019) + :Speed(0.4) + :Shimmer(25.7) + :Grain(0.01) + :Alpha(0.8) + :Flags(RNDX.SHAPE_CIRCLE) + :Draw() + + draw.SimpleText(displayChar, "gSims.Manrope.32", w/2, h/2, COL.white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + if s:IsHovered() then RNDX().Circle(w/2, h/2, w):Outline(2):Color(255, 255, 255, 100):Draw() end + cam.PopModelMatrix() + end + + btn.DoClick = function() + if CSND.Switch then surface.PlaySound(CSND.Switch) end + SendAct(ent, "slot" .. slotId, next_lab[ent.SLOTS[ent["GetSlot" .. slotId](ent)] or "A"]) + end +end + +local Frame = nil +local function OpenPrinterMenu(ent) + if IsValid(Frame) then Frame:Remove() end + if CSND.OpenUI then surface.PlaySound(CSND.OpenUI) end + local w, h = 420, 640 + local startTime = SysTime() + + Frame = vgui.Create("DFrame") + Frame:SetSize(w, h) + Frame:Center() + Frame:MakePopup() + Frame:SetTitle("") + Frame:ShowCloseButton(false) + + Frame.Paint = function(s, sw, sh) + RNDX.EnsureFB() + local anim = math.ease.OutBack(math.Clamp((SysTime() - startTime) / 0.3, 0, 1)) + local m = Matrix() + m:Translate(Vector(sw/2, sh/2)) m:Scale(Vector(anim, anim, 1)) m:Translate(-Vector(sw/2, sh/2)) + s:SetAlpha(255 * math.min(1, anim * 2)) + + cam.PushModelMatrix(m) + RNDX().Rect(0, 0, sw, sh):Rad(32):Color(COL.dark_bg):Flags(RNDX.SHAPE_IOS):Draw() + + RNDX().Liquid(0, 0, sw, sh) + :Rad(32):Color(255, 255, 255, 255) + :Tint(20, 20, 30):TintStrength(0.6) + :Saturation(1.06):GlassBlur(0.1, 0) + :EdgeSmooth(2):Strength(0.019):Speed(0.4) + :Shimmer(25.7):Grain(0.01):Alpha(0.8) + :Shadow(40, 56):Flags(RNDX.SHAPE_IOS):Draw() + + draw.NoTexture() + DrawGrid(0, 0, sw, 80, 20, 5 * anim) + draw.SimpleText("Принтер", "gSims.Manrope.Bold", 32, 28, COL.white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText("Панель управления", "gSims.Manrope.16", 32, 50, COL.text_dim, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + cam.PopModelMatrix() + end + + local btnClose = vgui.Create("DButton", Frame) + btnClose:SetPos(w - 52, 28) + btnClose:SetSize(24, 24) + btnClose:SetText("") + local closeHov = 0 + btnClose.Paint = function(s, bw, bh) + closeHov = math.Approach(closeHov, s:IsHovered() and 1 or 0, FrameTime() * 10) + draw.SimpleText("✕", "gSims.Manrope.20", bw/2, bh/2, Color(255, 255, 255, 150 + closeHov * 105), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + btnClose.DoClick = function() if CSND.CloseUI then surface.PlaySound(CSND.CloseUI) end Frame:Remove() end + RegisterAnim(btnClose, startTime, 0.2) + + local btnProt = vgui.Create("DButton", Frame) + btnProt:SetPos(w - 140, 30) + btnProt:SetSize(80, 24) + btnProt:SetText("") + local protHov = 0 + btnProt.Paint = function(s, bw, bh) + protHov = math.Approach(protHov, s:IsHovered() and 1 or 0, FrameTime() * 10) + local isProt = ent:GetIsProtected() + local col = isProt and COL.mint or COL.text_dim + RNDX().Rect(0, 0, bw, bh):Rad(8):Color(col.r, col.g, col.b, 20 + protHov * 10):Draw() + RNDX().Rect(0, 0, bw, bh):Rad(8):Outline(1):Color(col.r, col.g, col.b, 100):Draw() + draw.SimpleText(isProt and "ЗАКРЫТО" or "ОТКРЫТО", "gSims.Manrope.12", bw/2, bh/2, col, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + btnProt.DoClick = function() SendAct(ent, "protect") end + RegisterAnim(btnProt, startTime, 0.2) + + local InfoPanel = vgui.Create("DPanel", Frame) + InfoPanel:SetPos(30, 90) + InfoPanel:SetSize(w - 60, 160) + InfoPanel.Paint = function(s, sw, sh) + RNDX().Liquid(0, 0, sw, sh) + :Rad(32) + :Tint(200, 220, 255) + :TintStrength(0.6) + :Saturation(1.06) + :GlassBlur(0.1, 0) + :EdgeSmooth(2) + :Strength(0.019) + :Speed(0.4) + :Shimmer(25.7) + :Grain(0.01) + :Alpha(0.8) + :Flags(RNDX.SHAPE_IOS):Draw() + + local temp, money, integ = ent:GetTemperature(), ent:GetMoney(), ent:GetIntegrity() + local hpPct = math.Clamp(integ / ent.CFG.integrity, 0, 1) + + draw.SimpleText("ТЕМП", "gSims.Manrope.12", 25, 24, COL.text_dim, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText(temp .. "°C", "gSims.Manrope.32", 25, 42, temp > 80 and COL.red or COL.white, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) + draw.SimpleText("БАЛАНС", "gSims.Manrope.12", sw/2, 24, COL.text_dim, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("$" .. string.Comma(money), "gSims.Manrope.48", sw/2, 34, COL.green, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + draw.SimpleText("ЦЕЛОСТНОСТЬ", "gSims.Manrope.12", sw - 25, 24, COL.text_dim, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + draw.SimpleText(math.ceil(hpPct * 100) .. "%", "gSims.Manrope.32", sw - 25, 42, COL.white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_TOP) + + local barW, barH, barY = 260, 10, 110 + local barX = (sw - barW) / 2 + local segs, gap = 20, 4 + local segW = (barW - (gap * (segs - 1))) / segs + for i = 1, segs do + local bx = barX + (i-1) * (segW + gap) + draw.RoundedBox(2, bx, barY, segW, barH, Color(255, 255, 255, 10)) + if i <= math.floor(segs * hpPct) then draw.RoundedBox(2, bx, barY, segW, barH, COL.mint) end + end + if ent:GetMutatorID() > 0 then draw.SimpleText("МУТАТОР АКТИВЕН", "gSims.Manrope.12", sw/2, barY + 16, COL.purple, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) end + end + RegisterAnim(InfoPanel, startTime, 0.25) + + local barY, barW = 265, (w - 70) / 2 + local b1 = CreateBar(Frame, 30, barY, barW, 26, "ЗАГРЯЗНЕНИЕ", COL.orange, function() return ent:GetPollution() end, ent, "hold_pol") + local b2 = CreateBar(Frame, 30 + barW + 10, barY, barW, 26, "ЭНЕРГИЯ", COL.green, function() return ent:GetPowerRes() end, ent, "hold_pow") + RegisterAnim(b1, startTime, 0.3) RegisterAnim(b2, startTime, 0.3) + + local upgY, upgW = 305, (w - 80) / 3 + local function CreateMiniUpgrade(x, lbl, type, act) + local uBtn = vgui.Create("DButton", Frame) + uBtn:SetPos(x, upgY) uBtn:SetSize(upgW, 35) uBtn:SetText("") + local hov = 0 + uBtn.Paint = function(s, bw, bh) + hov = math.Approach(hov, s:IsHovered() and 1 or 0, FrameTime() * 10) + local lvl = ent["Get" .. (type == "eff" and "Eff" or (type == "cool" and "Cool" or "Luck")) .. "Level"](ent) + + RNDX().Liquid(0, 0, bw, bh):Rad(8) + :Tint(30, 30, 40) + :TintStrength(0.6) + :Saturation(1.06) + :GlassBlur(0.1, 0) + :EdgeSmooth(2) + :Strength(0.019) + :Speed(0.4) + :Shimmer(25.7) + :Grain(0.01) + :Alpha(0.8) + :Flags(RNDX.SHAPE_IOS):Draw() + + RNDX().Rect(0, 0, bw, bh):Rad(8):Outline(1):Color(255, 255, 255, 30 + hov * 30):Flags(RNDX.SHAPE_IOS):Draw() + local cost = ent:GetUpgradeCost(type, lvl) + if hov > 0.5 and cost > 0 then draw.SimpleText("$" .. cost, "gSims.Manrope.Bold", bw/2, bh/2, COL.green, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else draw.SimpleText(cost == 0 and "МАКС" or (lbl .. " " .. lvl), "gSims.Manrope.12", bw/2, bh/2, COL.white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end + end + uBtn.DoClick = function() SendAct(ent, act) end + RegisterAnim(uBtn, startTime, 0.35) + end + CreateMiniUpgrade(30, "ЭФФ.", "eff", "upgrade_eff") + CreateMiniUpgrade(30 + upgW + 10, "ОХЛ.", "cool", "upgrade_cool") + CreateMiniUpgrade(30 + (upgW + 10) * 2, "АЛГ.", "luck", "upgrade_luck") + + local ySlot, slotSize, gap = 350, 64, 20 + local startX = (w - (slotSize * 3 + gap * 2)) / 2 + CreateSlotButton(Frame, ent, 1, startX, ySlot, slotSize) + CreateSlotButton(Frame, ent, 2, startX + slotSize + gap, ySlot, slotSize) + CreateSlotButton(Frame, ent, 3, startX + (slotSize + gap) * 2, ySlot, slotSize) + for _, child in ipairs(Frame:GetChildren()) do if child:GetY() == ySlot then RegisterAnim(child, startTime, 0.4) end end + + local btnApply = CreateGlassButton(Frame, 30, 430, w - 60, 45, "", "gSims.Manrope.18", COL.accent, + function() SendAct(ent, "mods_apply") end, + function(s, bw, bh, hov) + local price = ent:GetApplyCost(ent:SortKey(ent.SLOTS[ent:GetSlot1()], ent.SLOTS[ent:GetSlot2()], ent.SLOTS[ent:GetSlot3()])) + draw.SimpleText("Применить", "gSims.Manrope.18", bw / 2, bh / 2, Color(255, 255, 255, 255 * (1 - hov)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText("Цена: $" .. price, "gSims.Manrope.Bold", bw / 2, bh / 2, Color(255, 255, 255, 255 * hov), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end) + RegisterAnim(btnApply, startTime, 0.45) + + local gridY, btnW = 490, (w - 70) / 2 + local predPower = ent:GetPowered() + local btnPower = CreateGlassButton(Frame, 30, gridY, btnW, 60, + function() return predPower and "СТОП" or "ПУСК" end, "gSims.Manrope.Bold", + function() return predPower and COL.red or COL.green end, + function() if CSND.Click then surface.PlaySound(CSND.Click) end predPower = not predPower SendAct(ent, "toggle") end + ) + btnPower.Think = function(s) if ent:GetPowered() ~= predPower then predPower = ent:GetPowered() end end + RegisterAnim(btnPower, startTime, 0.5) + + local btnVent = CreateGlassButton(Frame, 30 + btnW + 10, gridY, btnW, 60, + function() return not ent:GetPowered() and "НЕТ ПИТ." or "ВЕНТ." end, "gSims.Manrope.Bold", + function() return ent:GetPowered() and COL.white or COL.text_dim end, + function() if ent:GetPowered() then if CSND.Click then surface.PlaySound(CSND.Click) end SendAct(ent, "vent") end end + ) + RegisterAnim(btnVent, startTime, 0.5) + + local btnCollect = CreateGlassButton(Frame, 30, gridY + 70, w - 60, 60, "СНЯТЬ", "gSims.Manrope.Bold", COL.green, function() + if ent:GetMoney() > 0 and CSND.Cash then surface.PlaySound(CSND.Cash) end SendAct(ent, "collect") + end) + RegisterAnim(btnCollect, startTime, 0.55) +end + +net.Receive("gsims_printer_open", function() + local ent = net.ReadEntity() + if IsValid(ent) then OpenPrinterMenu(ent) end +end) + +net.Receive("gsims_printer_combo_fx", function() + local key = net.ReadString() + local ent = LocalPlayer():GetEyeTrace().Entity + if IsValid(ent) then ent._comboKey = key ent._comboTime = CurTime() SpawnParticles(ent:GetPos() + Vector(0,0,10), 2) end + + local rec = (IsValid(ent) and ent.GetRecipeByKey) and ent:GetRecipeByKey(key) or nil + if not rec then return end + + local stats = {} + if rec.payout_add then table.insert(stats, string.format("ДОХОД: %s$", rec.payout_add > 0 and "+"..rec.payout_add or rec.payout_add)) end + if rec.payout_mul then table.insert(stats, string.format("ДОХОД: %d%%", math.Round(rec.payout_mul * 100))) end + if rec.heat_add then table.insert(stats, string.format("НАГРЕВ: %s/s", rec.heat_add > 0 and "+"..rec.heat_add or rec.heat_add)) end + if rec.vent_bonus then table.insert(stats, string.format("ОХЛАЖДЕНИЕ: +%d", rec.vent_bonus)) end + if rec.mute then table.insert(stats, "СТАТУС: ТИХИЙ") end + if rec.collect_mul then table.insert(stats, string.format("ЩИТ: +%d%%", (rec.collect_mul - 1) * 100)) end + if rec.passive_cool_add then table.insert(stats, string.format("ПАСС. ОХЛ: +%d", rec.passive_cool_add)) end + if rec.fire_at_bonus then table.insert(stats, string.format("МАКС ТЕМП: +%d", rec.fire_at_bonus)) end + + local pnl = vgui.Create("DPanel") + pnl:SetSize(ScrW(), ScrH()) pnl:MakePopup() pnl:SetMouseInputEnabled(true) + pnl.OnMousePressed = function(s) s:Remove() end + local start = CurTime() + pnl.Paint = function(s, w, h) + local delta = CurTime() - start + if delta > 4.0 then s:Remove() return end + local alpha = delta < 0.5 and (delta/0.5)*255 or (delta > 3.5 and ((4.0-delta)/0.5)*255 or 255) + RNDX.DrawBlur(0, 0, w, h, RNDX.BLUR, 0, 0, 0, 0, 5) + surface.SetDrawColor(0, 0, 0, math.min(252, alpha)) surface.DrawRect(0, 0, w, h) + + local textStr = utf8.sub(rec.name or "UNKNOWN", 1, math.min(utf8.len(rec.name or "UNKNOWN"), math.floor(delta * 20))) + draw.SimpleText("РЕКОНФИГУРАЦИЯ СИСТЕМЫ", "gSims.Manrope.16", w/2, h/2 - 60, Color(100, 200, 255, alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(textStr, "gSims.Manrope.48", w/2, h/2, Color(255, 255, 255, alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if delta > 0.5 then + local statY = h/2 + 50 + for i, line in ipairs(stats) do + if (delta - 0.5) > ((i - 1) * 0.2) then + draw.SimpleText("> " .. line, "gSims.Manrope.24", w/2, statY, Color(200, 255, 200, alpha), TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + statY = statY + 30 + end + end + end + end +end) + +net.Receive("gsims_printer_fx_money", function() + local ent = net.ReadEntity() + if IsValid(ent) then SpawnParticles(ent:GetPos() + Vector(0,0,10), 1) end +end) + +function ENT:OnRemove() + if self.WorkSound then self.WorkSound:Stop() self.WorkSound = nil end +end + +function ENT:Think() + if not self.WorkSound and CSND.Work then self.WorkSound = CreateSound(self, CSND.Work) end + if not self.WorkSound then return end + local ct = CurTime() + if self:GetPowered() then + if not self._sndNextLoop or ct >= self._sndNextLoop then + self.WorkSound:Stop() self.WorkSound:Play() self.WorkSound:ChangeVolume(0, 0) + self._sndNextLoop = ct + 16 + end + local vol = 0.6 - (self:GetEffLevel() * 0.08) - (self:GetCoolLevel() * 0.04) + self.WorkSound:ChangeVolume(math.Clamp(vol, 0.1, 1.0), 0.5) + self.WorkSound:ChangePitch(100, 0) + else + if self._sndNextLoop then self.WorkSound:FadeOut(1.0) self._sndNextLoop = nil end + end +end + +function ENT:Draw() + RNDX.EnsureFB() + self:DrawModel() + local pos = self:GetPos() + if pos:DistToSqr(LocalPlayer():GetPos()) > 600 * 600 then return end + local offset = self:GetForward() * -6 + self:GetUp() * 3.1 + local rot = self:GetAngles() + rot:RotateAroundAxis(rot:Up(), 90) + + self._lerpMoney = Lerp(FrameTime() * 5, self._lerpMoney or 0, self:GetMoney()) + self._lerpTemp = Lerp(FrameTime() * 3, self._lerpTemp or 20, self:GetTemperature()) + + local tr = LocalPlayer():GetEyeTrace() + self._hoverAlpha = math.Approach(self._hoverAlpha or 0, tr.Entity == self and 1 or 0, FrameTime() * 6) + + cam.Start3D2D(pos + offset, rot, 0.05) + local w, h = 380, 220 + local x, y = -w/2, 0 + + render.SetStencilEnable(true) + render.SetStencilWriteMask(0xFF) render.SetStencilTestMask(0xFF) + render.SetStencilReferenceValue(1) render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilPassOperation(STENCIL_REPLACE) render.SetStencilFailOperation(STENCIL_KEEP) render.SetStencilZFailOperation(STENCIL_KEEP) + RNDX().Rect(x, y, w, h):Rad(16):Draw() + + render.SetStencilCompareFunction(STENCIL_EQUAL) render.SetStencilPassOperation(STENCIL_KEEP) + surface.SetDrawColor(COL.dark_bg) surface.DrawRect(x, y, w, h) + draw.NoTexture() DrawGrid(x, y, w, h, 20, 5) + surface.SetMaterial(MAT_GRAD) surface.SetDrawColor(COL.glass_top) surface.DrawTexturedRect(x, y, w, h/2) + + local shimmerPos = (CurTime() * 0.5) % 2.0 + if shimmerPos < 1.3 then + surface.SetMaterial(MAT_GRAD) surface.SetDrawColor(255, 255, 255, 10) + surface.DrawTexturedRectRotated(x + (w * shimmerPos) - 100, y + h/2, 100, h * 1.5, -20) + end + render.SetStencilEnable(false) + + RNDX().Liquid(x, y, w, h):Rad(16):Tint(COL.accent.r, COL.accent.g, COL.accent.b):TintStrength(0.02):Alpha(0.1):Draw() + local outlineCol = Color(Lerp(self._hoverAlpha, COL.accent.r, COL.green.r), Lerp(self._hoverAlpha, COL.accent.g, COL.green.g), Lerp(self._hoverAlpha, COL.accent.b, COL.green.b), 50 + self._hoverAlpha * 150) + RNDX().Rect(x, y, w, h):Rad(16):Outline(2 + self._hoverAlpha * 2):Color(outlineCol):Draw() + + draw.SimpleText("Ликвид-принтер", "gSims.Manrope.24", 0, y + 20, COL.text_dim, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + + if self._comboKey then + local anim = math.ease.OutBack(math.Clamp((CurTime() - (self._comboTime or CurTime())) / 0.5, 0, 1)) + if anim > 0 then + local circleSize, gap = 42 * anim, 12 + local startX = -((circleSize * 3) + (gap * 2)) / 2 + circleSize / 2 + local circleY = y + 70 + for i = 1, 3 do + local char = self._comboKey:sub(i, i) or "?" + local cx = startX + (i-1) * (circleSize + gap) + RNDX().Liquid(cx - circleSize/2, circleY - circleSize/2, circleSize, circleSize):Rad(circleSize/2):Tint(255, 255, 255):TintStrength(0.1):GlassBlur(0.1, 1.5):Alpha(0.9):Flags(RNDX.SHAPE_CIRCLE):Draw() + draw.SimpleText(char, "gSims.Manrope.24", cx, circleY, Color(255, 255, 255, 255 * anim), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + draw.SimpleText("$" .. string.Comma(math.floor(self._lerpMoney)), "gSims.Manrope.Display", 0, y + 130, COL.green, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + local tempPct = math.Clamp((self._lerpTemp - 20) / 100, 0, 1) + draw.SimpleText(math.floor(self._lerpTemp) .. "°C", "gSims.Manrope.32", 0, y + h - 45, Color(255, 255 * (1 - tempPct), 255 * (1 - tempPct)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + cam.End3D2D() +end diff --git a/addons/nyxteam_printer/lua/entities/nyxteam_printer/init.lua b/addons/nyxteam_printer/lua/entities/nyxteam_printer/init.lua new file mode 100644 index 0000000..e39cfef --- /dev/null +++ b/addons/nyxteam_printer/lua/entities/nyxteam_printer/init.lua @@ -0,0 +1,457 @@ +--[[ + + _ ___ ____ __ _______ ______ __ __ + | \ | \ \ / /\ \ / / |__ __| ____| /\ | \/ | + | \| |\ \_/ / \ V / | | | |__ / \ | \ / | + | . ` | \ / > < | | | __| / /\ \ | |\/| | + | |\ | | | / . \ | | | |____ / ____ \| | | | + |_| \_| |_| /_/ \_\ |_| |______/_/ \_\_| |_| + + JOIN DISCORD: https://discord.gg/rUEEz4mfXw + BY NYX TEAM (MaryBlackfild lead) + Copyrighted Nyx Team + YouGame.biz + +--]] + +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +util.AddNetworkString("gsims_printer_open") +util.AddNetworkString("gsims_printer_action") +util.AddNetworkString("gsims_printer_combo_fx") +util.AddNetworkString("gsims_printer_fx_money") + +local MODEL = "models/props_lab/reciever01a.mdl" + +local function isDarkRP() + return DarkRP and isfunction(DarkRP.notify) +end + +local function clamp(n, a, b) + if n < a then return a end + if n > b then return b end + return n +end + +local function inRange(ent, ply) + return IsValid(ent) and IsValid(ply) and ent:GetPos():DistToSqr(ply:GetPos()) <= 200 * 200 +end + +local function canAfford(ply, amt) + if not DarkRP or not ply.canAfford then return true end + return ply:canAfford(amt) +end + +local function takeMoney(ply, amt) + if not DarkRP or not ply.addMoney then return true end + ply:addMoney(-amt) + return true +end + +local function giveMoney(ply, amt) + if amt <= 0 then return end + if DarkRP and ply.addMoney then ply:addMoney(amt) end +end + +local function notify(ply, msg, typ) + if isDarkRP() then DarkRP.notify(ply, typ or 0, 4, msg) end +end + +function ENT:Initialize() + self:SetModel(MODEL) + self:PhysicsInit(SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + if IsValid(phys) then phys:Wake() end + + self:SetUseType(SIMPLE_USE) + self:SetTemperature(20) + self:SetMoney(0) + self:SetPowered(false) + self:SetIntegrity(self.CFG.integrity) + self:SetOnFireState(false) + self:SetPollution(70) + self:SetPowerRes(70) + self:SetEffLevel(0) + self:SetCoolLevel(0) + self:SetLuckLevel(0) + self:SetSlot1(0) + self:SetSlot2(0) + self:SetSlot3(0) + + if self.SND.Vent then self._ventLoop = CreateSound(self, self.SND.Vent) end + if self.SND.Alarm then self._alarm = CreateSound(self, self.SND.Alarm) end + + local ct = CurTime() + self._nextThink = ct + self._nextHeat = ct + 2 + self._nextPayout = ct + 5 + self._barTime = ct + self._modBuff = {} + self._ventState = 0 + self._holdPol = {} + self._holdPow = {} + self.Exploding = false +end + +function ENT:OnRemove() + if self._ventLoop then self._ventLoop:Stop() end + if self._alarm then self._alarm:Stop() end +end + +local function withBuff(x, add) + if not add then return x end + return x + add +end + +function ENT:GetPayoutPerTick() + local base = self.CFG.payout + self:GetEffLevel() * 2 + base = withBuff(base, self._modBuff.payout_add) + local mul = self._modBuff.payout_mul or 1 + local polF = math.Clamp(self:GetPollution() / 100, 0, 1) + return math.max(0, math.floor(base * mul * (0.5 + polF * 0.7))) +end + +function ENT:GetHeatGainNow() + return withBuff(self.CFG.heat_gain, self._modBuff.heat_add) +end + +function ENT:GetPassiveCoolNow() + return withBuff(self.CFG.cool_rate + (self:GetCoolLevel() * 0.6), self._modBuff.passive_cool_add) +end + +function ENT:GetFireAtNow() + return self.CFG.fire_temp + (self._modBuff.fire_at_bonus or 0) +end + +local function effectiveInterval(ent) + local powF = math.Clamp(ent:GetPowerRes() / 100, 0, 1) + return ent.CFG.interval * (1 + (1 - powF) * 1.0) +end + +function ENT:TogglePower(on) + if on == nil then on = not self:GetPowered() end + self:SetPowered(on) + if self.SND.Click then sound.Play(self.SND.Click, self:GetPos(), 65, 100, 0.6) end + + if on then + local ct = CurTime() + self._nextHeat = ct + 2 + self._nextPayout = ct + effectiveInterval(self) + end +end + +function ENT:CreditAndClear(ply) + local amt = self:GetMoney() + if amt <= 0 then return end + + amt = math.floor(amt * (self._modBuff.collect_mul or 1)) + local mID = self:GetMutatorID() + local mCharges = self:GetMutatorCharges() + + if mID > 0 and mCharges > 0 then + local luckLvl = self:GetLuckLevel() + local stats = self.LUCK[luckLvl] or {multiplier = 1.0, chance = 0} + + if math.random(100) <= stats.chance then + amt = math.floor(amt * stats.multiplier) + if self.SND.MutatorGood then sound.Play(self.SND.MutatorGood, self:GetPos(), 65, 100, 1) end + notify(ply, "МУТАТОР: УДАЧА! x" .. stats.multiplier, 0) + else + amt = math.floor(amt * 0.8) + if self.SND.MutatorBad then sound.Play(self.SND.MutatorBad, self:GetPos(), 65, 100, 1) end + notify(ply, "МУТАТОР: НЕУДАЧА (-20%)", 1) + end + + mCharges = mCharges - 1 + self:SetMutatorCharges(mCharges) + if mCharges <= 0 then self:SetMutatorID(0) end + end + + self:SetMoney(0) + if IsValid(ply) then + giveMoney(ply, amt) + notify(ply, "Получено $" .. amt, 0) + net.Start("gsims_printer_fx_money") + net.WriteEntity(self) + net.SendPVS(self:GetPos()) + end +end + +function ENT:VentTap(ply) + if not self:GetPowered() then return end + if self.SND.Click then sound.Play(self.SND.Click, self:GetPos(), 65, 100, 0.5) end + + local now = CurTime() + if self._ventState == 0 then + self._ventState = 1 + self:SetVentState(1) + self._ventEnd = now + 4.0 + self._ventInc = 6.0 + self._ventClicks = 0 + self:SetVentFlow(-math.ceil(self._ventInc)) + if self._ventLoop then self._ventLoop:Stop() self._ventLoop:PlayEx(0.6, 95) end + elseif self._ventState == 1 then + self._ventClicks = (self._ventClicks or 0) + 1 + self._ventInc = math.max(0.5, self._ventInc - 0.6) + self:SetVentFlow(-math.ceil(self._ventInc)) + if self._ventLoop then self._ventLoop:ChangePitch(100 + math.min(self._ventClicks * 2, 20), 0.1) end + end +end + +function ENT:StartFireSequence() + if self:GetOnFireState() then return end + self:SetOnFireState(true) + if self.SND.Ignite then sound.Play(self.SND.Ignite, self:GetPos(), 70, 100, 0.6) end + self:Ignite(8, 16) + if self._alarm then self._alarm:PlayEx(0.4, 100) end + timer.Simple(8, function() if IsValid(self) then self:MaybeExplode() end end) +end + +function ENT:MaybeExplode() + if not IsValid(self) or self.Exploding then return end + if self:GetTemperature() < self.CFG.explode_temp then + if self._alarm then self._alarm:Stop() end + self:SetOnFireState(false) + self:Extinguish() + return + end + self.Exploding = true + local pos = self:GetPos() + util.BlastDamage(self, self, pos, 160, 120) + local ef = EffectData() + ef:SetOrigin(pos) + util.Effect("Explosion", ef, true, true) + self:Remove() +end + +function ENT:OnTakeDamage(dmg) + self:SetIntegrity(clamp(self:GetIntegrity() - dmg:GetDamage(), 0, self.CFG.integrity)) + if self:GetIntegrity() <= 0 then self:MaybeExplode() end +end + +function ENT:ApplyActiveRecipeFromSlots(ply) + local k = self:SortKey(self.SLOTS[self:GetSlot1()], self.SLOTS[self:GetSlot2()], self.SLOTS[self:GetSlot3()]) + local rec = self:GetRecipeByKey(k) + + if not rec then + self._modBuff = {} + self:SetActiveRecipeId(0) + notify(ply, "Неверная комбинация", 1) + return + end + + local cost = self:GetApplyCost(k) + if not canAfford(ply, cost) then + notify(ply, "Нужно: $" .. cost, 1) + return + end + takeMoney(ply, cost) + + self._modBuff = rec + self:SetActiveRecipeId(rec.id or 0) + + if self:GetLuckLevel() > 0 and math.random(100) <= 30 then + self:SetMutatorID(1) + self:SetMutatorCharges(3) + notify(ply, "МУТАТОР: УМНОЖЕНИЕ (3 заряда)", 0) + end + + notify(ply, "Установлено: " .. (rec.name or ""), 0) + local mute = (self:GetEffLevel() > 1) or (rec.mute) + if self.SND.Combo then sound.Play(self.SND.Combo, self:GetPos(), mute and 55 or 75, 100, mute and 1.0 or 1.6) end + if IsValid(ply) then + net.Start("gsims_printer_combo_fx") + net.WriteString(k) + net.Send(ply) + end +end + +function ENT:CheckOwner(ply) + local owner = self:CPPIGetOwner() or self.dt.owning_ent + if not IsValid(owner) then return true end + return ply == owner +end + +function ENT:Use(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + if self:GetIsProtected() and not self:CheckOwner(ply) then + if self.SND.Lock then sound.Play(self.SND.Lock, self:GetPos(), 60, 100, 1) end + notify(ply, "Доступ запрещен (Защита)", 1) + return + end + net.Start("gsims_printer_open") + net.WriteEntity(self) + net.Send(ply) +end + +local function activeHolders(map, ent, timeout) + local now = CurTime() + local n = 0 + for ply, t in pairs(map) do + if not IsValid(ply) or not inRange(ent, ply) or (now - t) > timeout then map[ply] = nil else n = n + 1 end + end + return n +end + +function ENT:BarsThink(dt) + local pol, pow = self:GetPollution(), self:GetPowerRes() + local polD, powD = 0.0, 0.0 + + if self:GetPowered() then + polD = -self.CFG.pol_work_decay * dt + powD = -self.CFG.pow_work_decay * dt + if activeHolders(self._holdPol, self, self.CFG.hold_timeout) > 0 then polD = polD + self.CFG.pol_hold_gain * dt end + if activeHolders(self._holdPow, self, self.CFG.hold_timeout) > 0 then powD = powD + self.CFG.pow_hold_gain * dt end + else + polD = self.CFG.pol_rest_gain * dt + powD = self.CFG.pow_rest_gain * dt + end + + self._polAcc = (self._polAcc or 0) + polD + self._powAcc = (self._powAcc or 0) + powD + + local aPol, aPow = math.floor(self._polAcc), math.floor(self._powAcc) + if aPol ~= 0 then pol = clamp(pol + aPol, 0, 100) self._polAcc = self._polAcc - aPol end + if aPow ~= 0 then pow = clamp(pow + aPow, 0, 100) self._powAcc = self._powAcc - aPow end + + self:SetPollution(pol) + self:SetPowerRes(pow) +end + +function ENT:Think() + local ct = CurTime() + if ct < self._nextThink then return end + local dt = ct - (self._barTime or ct) + self._barTime = ct + self._nextThink = ct + 0.1 + + self:BarsThink(dt) + local powered = self:GetPowered() + + if powered then + while ct >= self._nextHeat do + self:SetTemperature(clamp(self:GetTemperature() + math.floor(self:GetHeatGainNow()), 0, 120)) + self._nextHeat = self._nextHeat + 2 + end + while ct >= self._nextPayout do + self:SetMoney(self:GetMoney() + self:GetPayoutPerTick()) + if not self._modBuff.mute and self.SND.Btn then sound.Play(self.SND.Btn, self:GetPos(), 55, 120, 0.2) end + self._nextPayout = self._nextPayout + effectiveInterval(self) + end + else + local t = self:GetTemperature() + if t > 20 then + local cr = self:GetPassiveCoolNow() + self._coolAcc = (self._coolAcc or 0) + (cr * dt) + if self._coolAcc >= 1 then + local d = math.floor(self._coolAcc) + self:SetTemperature(math.max(20, t - d)) + self._coolAcc = self._coolAcc - d + end + else + self._coolAcc = 0 + end + end + + if self._ventState > 0 then + if not powered then + self._ventState = 0 + self:SetVentState(0) + self:SetVentFlow(0) + if self._ventLoop then self._ventLoop:Stop() end + else + if self._ventState == 1 then + self:SetVentState(1) + self:SetTemperature(clamp(self:GetTemperature() + math.ceil(self._ventInc * 0.1), 0, 120)) + self:SetVentFlow(-math.ceil(self._ventInc)) + if ct >= self._ventEnd then + self._ventState = 2 + self:SetVentState(2) + self._ventTempStart = self:GetTemperature() + self._ventCoolT0 = ct + self._ventCoolT1 = ct + 3.0 + end + elseif self._ventState == 2 then + self:SetVentState(2) + local k = math.Clamp((ct - self._ventCoolT0) / math.max(0.01, (self._ventCoolT1 - self._ventCoolT0)), 0, 1) + local diff = math.max(0, self:GetTemperature() - math.max(20, math.floor(Lerp(k, self._ventTempStart, 20)))) + if diff > 0 then self:SetTemperature(self:GetTemperature() - diff) end + self:SetVentFlow(math.ceil(diff)) + if k >= 1 then + self:SetTemperature(20) + self._ventState = 0 + self:SetVentState(0) + self:SetVentFlow(0) + if self._ventLoop then self._ventLoop:FadeOut(1.0) end + end + end + end + elseif self:GetVentFlow() ~= 0 then + self:SetVentFlow(0) + end + + if self:GetTemperature() >= self:GetFireAtNow() then self:StartFireSequence() end + self:NextThink(ct + 0.1) + return true +end + +net.Receive("gsims_printer_action", function(_, ply) + local ent = net.ReadEntity() + local act = net.ReadString() + local arg = net.ReadString() or "" + + if not IsValid(ent) or not IsValid(ply) or not inRange(ent, ply) or not ent.GetPayoutPerTick then return end + + if act == "toggle" then + ent:TogglePower() + elseif act == "vent" then + ent:VentTap(ply) + elseif act == "collect" then + if ent:GetIsProtected() and not ent:CheckOwner(ply) then + if ent.SND.Lock then sound.Play(ent.SND.Lock, ent:GetPos(), 60, 100, 1) end + notify(ply, "Деньги защищены", 1) + return + end + ent:CreditAndClear(ply) + elseif act == "protect" then + if not ent:CheckOwner(ply) then notify(ply, "Нет владельца", 1) return end + if not ent:GetIsProtected() then + local cost = ent.CFG.prot_cost + if not canAfford(ply, cost) then notify(ply, "Нужно $" .. cost, 1) return end + takeMoney(ply, cost) + ent:SetIsProtected(true) + notify(ply, "Защита активна", 0) + if ent.SND.Upg then sound.Play(ent.SND.Upg, ent:GetPos(), 65, 100, 0.5) end + else + ent:SetIsProtected(false) + notify(ply, "Защита отключена", 2) + if ent.SND.Switch then sound.Play(ent.SND.Switch, ent:GetPos(), 65, 100, 0.5) end + end + elseif act == "upgrade_eff" or act == "upgrade_cool" or act == "upgrade_luck" then + local typ = act:sub(9) + local getter, setter = "Get" .. (typ == "eff" and "Eff" or (typ == "cool" and "Cool" or "Luck")) .. "Level", "Set" .. (typ == "eff" and "Eff" or (typ == "cool" and "Cool" or "Luck")) .. "Level" + local lvl = ent[getter](ent) + local cost = ent:GetUpgradeCost(typ, lvl) + if cost == 0 then notify(ply, "Максимальный", 1) return end + if not canAfford(ply, cost) then notify(ply, "Нужно $" .. cost, 1) return end + takeMoney(ply, cost) + ent[setter](ent, lvl + 1) + if ent.SND.Upg then sound.Play(ent.SND.Upg, ent:GetPos(), 65, 100, 0.5) end + notify(ply, "Улучшено", 0) + elseif act:sub(1,4) == "slot" then + local id = tonumber(act:sub(5,5)) + local val = ent.L2I[arg] or 0 + if id == 1 then ent:SetSlot1(val) elseif id == 2 then ent:SetSlot2(val) else ent:SetSlot3(val) end + if ent.SND.Switch then sound.Play(ent.SND.Switch, ent:GetPos(), 60, 100, 0.6) end + elseif act == "mods_apply" then + ent:ApplyActiveRecipeFromSlots(ply) + elseif act == "hold_pol" then + ent._holdPol[ply] = CurTime() + elseif act == "hold_pow" then + ent._holdPow[ply] = CurTime() + end +end) \ No newline at end of file diff --git a/addons/nyxteam_printer/lua/entities/nyxteam_printer/shared.lua b/addons/nyxteam_printer/lua/entities/nyxteam_printer/shared.lua new file mode 100644 index 0000000..d471fd2 --- /dev/null +++ b/addons/nyxteam_printer/lua/entities/nyxteam_printer/shared.lua @@ -0,0 +1,135 @@ +--[[ + + _ ___ ____ __ _______ ______ __ __ + | \ | \ \ / /\ \ / / |__ __| ____| /\ | \/ | + | \| |\ \_/ / \ V / | | | |__ / \ | \ / | + | . ` | \ / > < | | | __| / /\ \ | |\/| | + | |\ | | | / . \ | | | |____ / ____ \| | | | + |_| \_| |_| /_/ \_\ |_| |______/_/ \_\_| |_| + + JOIN DISCORD: https://discord.gg/rUEEz4mfXw + BY NYX TEAM (MaryBlackfild lead) + Copyrighted Nyx Team + YouGame.biz + +--]] + +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "gSims Printer" +ENT.Author = "Nyx Team" +ENT.Category = "Nyx Team" +ENT.Spawnable = true +ENT.RenderGroup = RENDERGROUP_BOTH + +ENT.CFG = { + payout = 250, + interval = 5, + heat_gain = 2, + cool_rate = 1.0, + fire_temp = 90, + explode_temp = 110, + integrity = 175, + apply_cost = 150, + prot_cost = 500, + pol_work_decay = 2.0, + pow_work_decay = 2.0, + pol_rest_gain = 6.0, + pow_rest_gain = 6.0, + pol_hold_gain = 8.0, + pow_hold_gain = 8.0, + hold_timeout = 0.45 +} + +ENT.SND = { + Work = "nyxteam/nyxteam_printer_working.mp3", + Cash = "nyxteam/nyxteam_printer_cashout.mp3", + Combo = "nyxteam/nyxteam_printer_combo.mp3", + Alarm = "nyxteam/nyxteam_printer_alarm.mp3", + Switch = "nyxteam/nyxteam_printer_switch.mp3", + Vent = "nyxteam/nyxteam_printer_vent.mp3", + Click = "nyxteam/nyxteam_printer_click.mp3", + Ignite = "ambient/fire/ignite.wav", + Upg = "buttons/button3.wav", + Lock = "buttons/combine_button_locked.wav", + OpenUI = "nyxteam/nyxteam_printer_open.mp3", + CloseUI = "nyxteam/nyxteam_printer_lock.mp3", + MutatorBad = "buttons/button8.wav", + MutatorGood = "buttons/bell1.wav" +} + +ENT.RECIPES = { + ["AAA"] = { id = 1, name = "Разгон", payout_add = 1, heat_add = 1 }, + ["AAB"] = { id = 2, name = "Эко-Буст", payout_add = 1, heat_add = 0, vent_bonus = 1 }, + ["ABB"] = { id = 3, name = "Холодный Фокус", heat_add = -1, vent_bonus = 2 }, + ["ABC"] = { id = 4, name = "Стелс", payout_mul = 0.9, heat_add = -1, mute = true }, + ["BBB"] = { id = 5, name = "Крио-Цикл", payout_add = -1, heat_add = -2, vent_bonus = 1 }, + ["BBC"] = { id = 6, name = "Радиатор", fire_at_bonus = 10, heat_add = -1 }, + ["BCC"] = { id = 7, name = "Аккумулятор", payout_add = 1, passive_cool_add = 1 }, + ["CCC"] = { id = 8, name = "Налоговый Щит", collect_mul = 1.25 }, + ["ACC"] = { id = 9, name = "Риск-Профит", payout_add = 2, heat_add = 2 }, +} + +ENT.UPGRADES = { + eff = { max = 3, base = 250, step = 250 }, + cool = { max = 3, base = 200, step = 200 }, + luck = { max = 3, base = 500, step = 500 } +} + +ENT.LUCK = { + [1] = { multiplier = 1.2, chance = 25 }, + [2] = { multiplier = 1.5, chance = 40 }, + [3] = { multiplier = 2.0, chance = 60 } +} + +ENT.SLOTS = { [0] = "A", [1] = "B", [2] = "C" } +ENT.L2I = { A = 0, B = 1, C = 2 } + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "Temperature") + self:NetworkVar("Int", 1, "Money") + self:NetworkVar("Bool", 0, "Powered") + self:NetworkVar("Int", 2, "Integrity") + self:NetworkVar("Bool", 1, "OnFireState") + self:NetworkVar("Int", 3, "EffLevel") + self:NetworkVar("Int", 4, "CoolLevel") + self:NetworkVar("Int", 5, "VentCombo") + self:NetworkVar("Int", 6, "VentFlow") + self:NetworkVar("Int", 7, "Slot1") + self:NetworkVar("Int", 8, "Slot2") + self:NetworkVar("Int", 9, "Slot3") + self:NetworkVar("Int", 10, "ActiveRecipeId") + self:NetworkVar("Int", 11, "VentState") + self:NetworkVar("Int", 12, "Pollution") + self:NetworkVar("Int", 13, "PowerRes") + self:NetworkVar("Bool", 2, "IsProtected") + self:NetworkVar("Int", 14, "LuckLevel") + self:NetworkVar("Int", 15, "MutatorID") + self:NetworkVar("Int", 16, "MutatorCharges") +end + +function ENT:GetRecipeByKey(key) + return self.RECIPES[key] +end + +function ENT:SortKey(a, b, c) + local t = { a, b, c } + table.sort(t) + return table.concat(t) +end + +function ENT:GetApplyCost(key) + local base = self.CFG.apply_cost + local uniq = {} + for i = 1, #key do uniq[key:sub(i, i)] = true end + local u = 0 + for _ in pairs(uniq) do u = u + 1 end + local mult = 1.5 + if u == 1 then mult = 1.0 elseif u == 2 then mult = 1.2 end + return math.floor(base * mult) +end + +function ENT:GetUpgradeCost(type, curLvl) + local data = self.UPGRADES[type] + if not data or curLvl >= data.max then return 0 end + return data.base + (data.step * curLvl) +end diff --git a/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_alarm.mp3 b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_alarm.mp3 new file mode 100644 index 0000000..4c35bd7 Binary files /dev/null and b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_alarm.mp3 differ diff --git a/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_cashout.mp3 b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_cashout.mp3 new file mode 100644 index 0000000..88b85f5 Binary files /dev/null and b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_cashout.mp3 differ diff --git a/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_click.mp3 b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_click.mp3 new file mode 100644 index 0000000..7515968 Binary files /dev/null and b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_click.mp3 differ diff --git a/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_combo.mp3 b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_combo.mp3 new file mode 100644 index 0000000..0adf48e Binary files /dev/null and b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_combo.mp3 differ diff --git a/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_switch.mp3 b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_switch.mp3 new file mode 100644 index 0000000..524b2ab Binary files /dev/null and b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_switch.mp3 differ diff --git a/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_vent.mp3 b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_vent.mp3 new file mode 100644 index 0000000..5c7641f Binary files /dev/null and b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_vent.mp3 differ diff --git a/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_working.mp3 b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_working.mp3 new file mode 100644 index 0000000..a94cb37 Binary files /dev/null and b/addons/nyxteam_printer/sound/nyxteam/nyxteam_printer_working.mp3 differ diff --git a/addons/permaprops/addon.json b/addons/permaprops/addon.json new file mode 100644 index 0000000..6869f6e --- /dev/null +++ b/addons/permaprops/addon.json @@ -0,0 +1,9 @@ +{ + "title": "PermaProps", + "type": "tool", + "tags": [ + "build", + "roleplay" + ], + "ignore": [] +} \ No newline at end of file diff --git a/addons/permaprops/lua/autorun/client/cl_permaload.lua b/addons/permaprops/lua/autorun/client/cl_permaload.lua new file mode 100644 index 0000000..fcbc9fe --- /dev/null +++ b/addons/permaprops/lua/autorun/client/cl_permaload.lua @@ -0,0 +1,36 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +if not PermaProps then PermaProps = {} end + +print("---------------------------------") +print("| Loading ClientSide PermaProps |") +print("---------------------------------") + +for k, v in pairs(file.Find("permaprops/cl_*.lua", "LUA")) do + + include("permaprops/".. v) + print("permaprops/".. v) + + +end + +print("---------------------------------") +print("| Loading Shared PermaProps |") +print("---------------------------------") + +for k, v in pairs(file.Find("permaprops/sh_*.lua", "LUA")) do + + include("permaprops/".. v) + print("permaprops/".. v) + + +end + +print("---------------------------------") \ No newline at end of file diff --git a/addons/permaprops/lua/autorun/server/sv_permaload.lua b/addons/permaprops/lua/autorun/server/sv_permaload.lua new file mode 100644 index 0000000..f48fd4f --- /dev/null +++ b/addons/permaprops/lua/autorun/server/sv_permaload.lua @@ -0,0 +1,48 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +if not PermaProps then PermaProps = {} end + +print("---------------------------------") +print("| Loading ServerSide PermaProps |") +print("---------------------------------") + +for k, v in pairs(file.Find("permaprops/sv_*.lua", "LUA")) do + + include("permaprops/".. v) + print("permaprops/".. v) + + +end + +print("-----------------------------") +print("| Loading Shared PermaProps |") +print("-----------------------------") + +for k, v in pairs(file.Find("permaprops/sh_*.lua", "LUA")) do + + AddCSLuaFile("permaprops/".. v) + include("permaprops/".. v) + print("permaprops/".. v) + + +end + +print("---------------------------------") +print("| Loading ClientSide PermaProps |") +print("---------------------------------") + +for k, v in pairs(file.Find("permaprops/cl_*.lua", "LUA")) do + + AddCSLuaFile("permaprops/".. v) + print("permaprops/".. v) + +end + +print("-------------------------------") \ No newline at end of file diff --git a/addons/permaprops/lua/entities/pp_prop_effect.lua b/addons/permaprops/lua/entities/pp_prop_effect.lua new file mode 100644 index 0000000..d3011ad --- /dev/null +++ b/addons/permaprops/lua/entities/pp_prop_effect.lua @@ -0,0 +1,141 @@ +AddCSLuaFile() + +ENT.Type = "anim" + +ENT.PrintName = "" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" +ENT.Spawnable = false +ENT.AdminOnly = false + +--[[--------------------------------------------------------- + Name: Initialize +-----------------------------------------------------------]] +function ENT:Initialize() + + local Radius = 6 + local min = Vector( 1, 1, 1 ) * Radius * -0.5 + local max = Vector( 1, 1, 1 ) * Radius * 0.5 + + if ( SERVER ) then + + self.AttachedEntity = ents.Create( "prop_dynamic" ) + self.AttachedEntity:SetModel( self:GetModel() ) + self.AttachedEntity:SetAngles( self:GetAngles() ) + self.AttachedEntity:SetPos( self:GetPos() ) + self.AttachedEntity:SetSkin( self:GetSkin() ) + self.AttachedEntity:Spawn() + self.AttachedEntity:SetParent( self.Entity ) + self.AttachedEntity:DrawShadow( false ) + + self:SetModel( "models/props_junk/watermelon01.mdl" ) + + self:DeleteOnRemove( self.AttachedEntity ) + + -- Don't use the model's physics - create a box instead + self:PhysicsInitBox( min, max ) + + -- Set up our physics object here + local phys = self:GetPhysicsObject() + if ( IsValid( phys ) ) then + phys:Wake() + phys:EnableGravity( false ) + phys:EnableDrag( false ) + end + + self:DrawShadow( false ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + else + + self.GripMaterial = Material( "sprites/grip" ) + + -- Get the attached entity so that clientside functions like properties can interact with it + local tab = ents.FindByClassAndParent( "prop_dynamic", self ) + if ( tab && IsValid( tab[ 1 ] ) ) then self.AttachedEntity = tab[ 1 ] end + + end + + -- Set collision bounds exactly + self:SetCollisionBounds( min, max ) + +end + + +--[[--------------------------------------------------------- + Name: Draw +-----------------------------------------------------------]] +function ENT:Draw() + + render.SetMaterial( self.GripMaterial ) + +end + + +--[[--------------------------------------------------------- + Name: PhysicsUpdate +-----------------------------------------------------------]] +function ENT:PhysicsUpdate( physobj ) + + if ( CLIENT ) then return end + + -- Don't do anything if the player isn't holding us + if ( !self:IsPlayerHolding() && !self:IsConstrained() ) then + + physobj:SetVelocity( Vector( 0, 0, 0 ) ) + physobj:Sleep() + + end + +end + + +--[[--------------------------------------------------------- + Name: Called after entity 'copy' +-----------------------------------------------------------]] +function ENT:OnEntityCopyTableFinish( tab ) + + -- We need to store the model of the attached entity + -- Not the one we have here. + tab.Model = self.AttachedEntity:GetModel() + + -- Store the attached entity's table so we can restore it after being pasted + tab.AttachedEntityInfo = table.Copy( duplicator.CopyEntTable( self.AttachedEntity ) ) + tab.AttachedEntityInfo.Pos = nil -- Don't even save angles and position, we are a parented entity + tab.AttachedEntityInfo.Angle = nil + + -- Do NOT store the attached entity itself in our table! + -- Otherwise, if we copy-paste the prop with the duplicator, its AttachedEntity value will point towards the original prop's attached entity instead, and that'll break stuff + tab.AttachedEntity = nil + +end + + +--[[--------------------------------------------------------- + Name: PostEntityPaste +-----------------------------------------------------------]] +function ENT:PostEntityPaste( ply ) + + -- Restore the attached entity using the information we've saved + if ( IsValid( self.AttachedEntity ) ) and ( self.AttachedEntityInfo ) then + + -- Apply skin, bodygroups, bone manipulator, etc. + duplicator.DoGeneric( self.AttachedEntity, self.AttachedEntityInfo ) + + if ( self.AttachedEntityInfo.EntityMods ) then + self.AttachedEntity.EntityMods = table.Copy( self.AttachedEntityInfo.EntityMods ) + duplicator.ApplyEntityModifiers( ply, self.AttachedEntity ) + end + + if ( self.AttachedEntityInfo.BoneMods ) then + self.AttachedEntity.BoneMods = table.Copy( self.AttachedEntityInfo.BoneMods ) + duplicator.ApplyBoneModifiers( ply, self.AttachedEntity ) + end + + self.AttachedEntityInfo = nil + + end + +end diff --git a/addons/permaprops/lua/permaprops/cl_drawent.lua b/addons/permaprops/lua/permaprops/cl_drawent.lua new file mode 100644 index 0000000..09b4ebf --- /dev/null +++ b/addons/permaprops/lua/permaprops/cl_drawent.lua @@ -0,0 +1,46 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +local function PermaPropsViewer() + + if not LocalPlayer().DrawPPEnt or not istable(LocalPlayer().DrawPPEnt) then return end + + local pos = LocalPlayer():EyePos() + LocalPlayer():EyeAngles():Forward() * 10 + local ang = LocalPlayer():EyeAngles() + + ang = Angle(ang.p + 90, ang.y, 0) + + for k, v in pairs(LocalPlayer().DrawPPEnt) do + + if not v or not v:IsValid() then LocalPlayer().DrawPPEnt[k] = nil continue end + + render.ClearStencil() + render.SetStencilEnable(true) + render.SetStencilWriteMask(255) + render.SetStencilTestMask(255) + render.SetStencilReferenceValue(15) + render.SetStencilFailOperation(STENCILOPERATION_REPLACE) + render.SetStencilZFailOperation(STENCILOPERATION_REPLACE) + render.SetStencilPassOperation(STENCILOPERATION_KEEP) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_ALWAYS) + render.SetBlend(0) + v:DrawModel() + render.SetBlend(1) + render.SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) + cam.Start3D2D(pos, ang, 1) + surface.SetDrawColor(255, 0, 0, 255) + surface.DrawRect(-ScrW(), -ScrH(), ScrW() * 2, ScrH() * 2) + cam.End3D2D() + v:DrawModel() + render.SetStencilEnable(false) + + end + +end +hook.Add("PostDrawOpaqueRenderables", "PermaPropsViewer", PermaPropsViewer) \ No newline at end of file diff --git a/addons/permaprops/lua/permaprops/cl_menu.lua b/addons/permaprops/lua/permaprops/cl_menu.lua new file mode 100644 index 0000000..1e17b17 --- /dev/null +++ b/addons/permaprops/lua/permaprops/cl_menu.lua @@ -0,0 +1,468 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +surface.CreateFont( "pp_font", { + font = "Arial", + size = 20, + weight = 700, + shadow = false +} ) + +local function pp_open_menu() + + local Len = net.ReadFloat() + local Data = net.ReadData( Len ) + local UnCompress = util.Decompress( Data ) + local Content = util.JSONToTable( UnCompress ) + + local Main = vgui.Create( "DFrame" ) + Main:SetSize( 600, 355 ) + Main:Center() + Main:SetTitle("") + Main:SetVisible( true ) + Main:SetDraggable( true ) + Main:ShowCloseButton( true ) + Main:MakePopup() + Main.Paint = function(self) + + draw.RoundedBox( 0, 0, 0, self:GetWide(), self:GetTall(), Color(155, 155, 155, 220) ) + surface.SetDrawColor( 17, 148, 240, 255 ) + surface.DrawOutlinedRect( 0, 0, self:GetWide(), self:GetTall() ) + + draw.RoundedBox( 0, 0, 0, self:GetWide(), 25, Color(17, 148, 240, 200) ) + surface.SetDrawColor( 17, 148, 240, 255 ) + surface.DrawOutlinedRect( 0, 0, self:GetWide(), 25 ) + draw.DrawText( "PermaProps Config", "pp_font", 10, 2.2, Color(255, 255, 255, 255), TEXT_ALIGN_LEFT ) + + end + + local BSelect + local PSelect + + local MainPanel = vgui.Create( "DPanel", Main ) + MainPanel:SetPos( 190, 51 ) + MainPanel:SetSize( 390, 275 ) + MainPanel.Paint = function( self ) + surface.SetDrawColor( 50, 50, 50, 200 ) + surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() ) + surface.DrawOutlinedRect(0, 15, self:GetWide(), 40) + end + PSelect = MainPanel + + local MainLabel = vgui.Create("DLabel", MainPanel) + MainLabel:SetFont("pp_font") + MainLabel:SetPos(140, 25) + MainLabel:SetColor(Color(50, 50, 50, 255)) + MainLabel:SetText("Hey ".. LocalPlayer():Nick() .." !") + MainLabel:SizeToContents() + + local MainLabel2 = vgui.Create("DLabel", MainPanel) + MainLabel2:SetFont("pp_font") + MainLabel2:SetPos(80, 80) + MainLabel2:SetColor(Color(50, 50, 50, 255)) + MainLabel2:SetText("There are ".. ( Content.MProps or 0 ) .." props on this map.\n\nThere are ".. ( Content.TProps or 0 ) .." props in the DB.") + MainLabel2:SizeToContents() + + local RemoveMapProps = vgui.Create( "DButton", MainPanel ) + RemoveMapProps:SetText( " Clear map props " ) + RemoveMapProps:SetFont("pp_font") + RemoveMapProps:SetSize( 370, 30) + RemoveMapProps:SetPos( 10, 160 ) + RemoveMapProps:SetTextColor( Color( 50, 50, 50, 255 ) ) + RemoveMapProps.DoClick = function() + net.Start("pp_info_send") + net.WriteTable({CMD = "CLR_MAP"}) + net.SendToServer() + end + RemoveMapProps.Paint = function(self) + surface.SetDrawColor(50, 50, 50, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + end + + local ClearMapProps = vgui.Create( "DButton", MainPanel ) + ClearMapProps:SetText( " Clear map props in the DB " ) + ClearMapProps:SetFont("pp_font") + ClearMapProps:SetSize( 370, 30) + ClearMapProps:SetPos( 10, 200 ) + ClearMapProps:SetTextColor( Color( 50, 50, 50, 255 ) ) + ClearMapProps.DoClick = function() + + Derma_Query("Are you sure you want clear map props in the db ?\nYou can't undo this action !", "PermaProps 4.0", "Yes", function() net.Start("pp_info_send") net.WriteTable({CMD = "DEL_MAP"}) net.SendToServer() end, "Cancel") + + end + ClearMapProps.Paint = function(self) + surface.SetDrawColor(50, 50, 50, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + end + + local ClearAllProps = vgui.Create( "DButton", MainPanel ) + ClearAllProps:SetText( " Clear all props in the DB " ) + ClearAllProps:SetFont("pp_font") + ClearAllProps:SetSize( 370, 30) + ClearAllProps:SetPos( 10, 240 ) + ClearAllProps:SetTextColor( Color( 50, 50, 50, 255 ) ) + ClearAllProps.DoClick = function() + + Derma_Query("Are you sure you want clear all props in the db ?\nYou can't undo this action !", "PermaProps 4.0", "Yes", function() net.Start("pp_info_send") net.WriteTable({CMD = "DEL_ALL"}) net.SendToServer() end, "Cancel") + + end + ClearAllProps.Paint = function(self) + surface.SetDrawColor(50, 50, 50, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + end + + local BMain = vgui.Create("DButton", Main) + BSelect = BMain + BMain:SetText("Main") + BMain:SetFont("pp_font") + BMain:SetSize(160, 50) + BMain:SetPos(15, 27 + 25) + BMain:SetTextColor( Color( 255, 255, 255, 255 ) ) + BMain.PaintColor = Color(17, 148, 240, 100) + BMain.Paint = function(self) + + draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor) + surface.SetDrawColor(17, 148, 240, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + + end + BMain.DoClick = function( self ) + + if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end + BSelect = self + self.PaintColor = Color(17, 148, 240, 100) + + if PSelect then PSelect:Hide() end + MainPanel:Show() + PSelect = MainPanel + + end + + local ConfigPanel = vgui.Create( "DPanel", Main ) + ConfigPanel:SetPos( 190, 51 ) + ConfigPanel:SetSize( 390, 275 ) + ConfigPanel.Paint = function( self ) + surface.SetDrawColor( 50, 50, 50, 200 ) + surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() ) + end + ConfigPanel:Hide() + + local CheckCustom = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckCustom:SetPos( 5, 30 ) + CheckCustom:SetText( "Custom permissions" ) + CheckCustom:SetValue( 0 ) + CheckCustom:SizeToContents() + CheckCustom:SetTextColor( Color( 0, 0, 0, 255) ) + CheckCustom:SetDisabled( true ) + + local GroupsList = vgui.Create( "DComboBox", ConfigPanel ) + GroupsList:SetPos( 5, 5 ) + GroupsList:SetSize( 125, 20 ) + GroupsList:SetValue( "Select a group..." ) + + local CheckBox1 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox1:SetPos( 150, 10 ) + CheckBox1:SetText( "Menu" ) + CheckBox1:SizeToContents() + CheckBox1:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox1:SetDisabled( true ) + CheckBox1.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Menu", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local CheckBox2 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox2:SetPos( 150, 30 ) + CheckBox2:SetText( "Edit permissions" ) + CheckBox2:SizeToContents() + CheckBox2:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox2:SetDisabled( true ) + CheckBox2.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Permissions", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local CheckBox3 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox3:SetPos( 150, 50 ) + CheckBox3:SetText( "Physgun permaprops" ) + CheckBox3:SizeToContents() + CheckBox3:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox3:SetDisabled( true ) + CheckBox3.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Physgun", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local CheckBox4 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox4:SetPos( 150, 70 ) + CheckBox4:SetText( "Tool permaprops" ) + CheckBox4:SizeToContents() + CheckBox4:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox4:SetDisabled( true ) + CheckBox4.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Tool", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local CheckBox5 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox5:SetPos( 150, 90 ) + CheckBox5:SetText( "Property permaprops" ) + CheckBox5:SizeToContents() + CheckBox5:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox5:SetDisabled( true ) + CheckBox5.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Property", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local CheckBox6 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox6:SetPos( 150, 110 ) + CheckBox6:SetText( "Save props" ) + CheckBox6:SizeToContents() + CheckBox6:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox6:SetDisabled( true ) + CheckBox6.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Save", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local CheckBox7 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox7:SetPos( 150, 130 ) + CheckBox7:SetText( "Delete permaprops" ) + CheckBox7:SizeToContents() + CheckBox7:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox7:SetDisabled( true ) + CheckBox7.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Delete", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local CheckBox8 = vgui.Create( "DCheckBoxLabel", ConfigPanel ) + CheckBox8:SetPos( 150, 150 ) + CheckBox8:SetText( "Update permaprops" ) + CheckBox8:SizeToContents() + CheckBox8:SetTextColor( Color( 0, 0, 0, 255) ) + CheckBox8:SetDisabled( true ) + CheckBox8.OnChange = function(Self, Value) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Update", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + GroupsList.OnSelect = function( panel, index, value ) + + CheckCustom:SetDisabled( false ) + CheckCustom:SetChecked( Content.Permissions[value].Custom ) + + CheckBox1:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox1:SetChecked( Content.Permissions[value].Menu ) + CheckBox2:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox2:SetChecked( Content.Permissions[value].Permissions ) + CheckBox3:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox3:SetChecked( Content.Permissions[value].Physgun ) + CheckBox4:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox4:SetChecked( Content.Permissions[value].Tool ) + CheckBox5:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox5:SetChecked( Content.Permissions[value].Property ) + CheckBox6:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox6:SetChecked( Content.Permissions[value].Save ) + CheckBox7:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox7:SetChecked( Content.Permissions[value].Delete ) + CheckBox8:SetDisabled( !Content.Permissions[value].Custom ) + CheckBox8:SetChecked( Content.Permissions[value].Update ) + + end + + for k, v in pairs(Content.Permissions) do + + GroupsList:AddChoice(k) + + end + + CheckCustom.OnChange = function(Self, Value) + + CheckBox1:SetDisabled( !Value ) + CheckBox2:SetDisabled( !Value ) + CheckBox3:SetDisabled( !Value ) + CheckBox4:SetDisabled( !Value ) + CheckBox5:SetDisabled( !Value ) + CheckBox6:SetDisabled( !Value ) + CheckBox7:SetDisabled( !Value ) + CheckBox8:SetDisabled( !Value ) + + net.Start("pp_info_send") + net.WriteTable({CMD = "VAR", Val = Value, Data = "Custom", Name = GroupsList:GetValue()}) + net.SendToServer() + + end + + local BConfig = vgui.Create("DButton", Main) + BConfig:SetText("Configuration") + BConfig:SetFont("pp_font") + BConfig:SetSize(160, 50) + BConfig:SetPos(15, 71 + 55) + BConfig:SetTextColor( Color( 255, 255, 255, 255 ) ) + BConfig.PaintColor = Color(0, 0, 0, 0) + BConfig.Paint = function(self) + draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor) + surface.SetDrawColor(17, 148, 240, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + end + BConfig.DoClick = function( self ) + + if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end + BSelect = self + self.PaintColor = Color(17, 148, 240, 100) + + if PSelect then PSelect:Hide() end + ConfigPanel:Show() + PSelect = ConfigPanel + + end + + local PropsPanel = vgui.Create( "DPanel", Main ) + PropsPanel:SetPos( 190, 51 ) + PropsPanel:SetSize( 390, 275 ) + PropsPanel.Paint = function( self ) + surface.SetDrawColor( 50, 50, 50, 200 ) + surface.DrawRect( 0, 0, self:GetWide(), self:GetTall() ) + end + PropsPanel:Hide() + + local PropsList = vgui.Create( "DListView", PropsPanel ) + PropsList:SetMultiSelect( false ) + PropsList:SetSize( 390, 275 ) + local ColID = PropsList:AddColumn( "ID" ) + local ColEnt = PropsList:AddColumn( "Entity" ) + local ColMdl = PropsList:AddColumn( "Model" ) + ColID:SetMinWidth(50) + ColID:SetMaxWidth(50) + PropsList.Paint = function( self ) + surface.SetDrawColor(17, 148, 240, 255) + end + + PropsList.OnRowRightClick = function(panel, line) + + local MenuButtonOptions = DermaMenu() + MenuButtonOptions:AddOption("Draw entity", function() + + if not LocalPlayer().DrawPPEnt or not istable(LocalPlayer().DrawPPEnt) then LocalPlayer().DrawPPEnt = {} end + + if LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:IsValid() then return end + + local ent = ents.CreateClientProp( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Model ) + ent:SetPos( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Pos ) + ent:SetAngles( Content.PropsList[PropsList:GetLine(line):GetValue(1)].Angle ) + + LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = ent + + end ) + + if LocalPlayer().DrawPPEnt and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] then + + MenuButtonOptions:AddOption("Stop Drawing", function() + + LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:Remove() + LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = nil + + end ) + + end + + if LocalPlayer().DrawPPEnt != nil and istable(LocalPlayer().DrawPPEnt) and table.Count(LocalPlayer().DrawPPEnt) > 0 then + + MenuButtonOptions:AddOption("Stop Drawing All", function() + + for k, v in pairs(LocalPlayer().DrawPPEnt) do + + LocalPlayer().DrawPPEnt[k]:Remove() + LocalPlayer().DrawPPEnt[k] = nil + + end + + end ) + + end + + MenuButtonOptions:AddOption("Remove", function() + + net.Start("pp_info_send") + net.WriteTable({CMD = "DEL", Val = PropsList:GetLine(line):GetValue(1)}) + net.SendToServer() + + if LocalPlayer().DrawPPEnt and LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] != nil then + + LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)]:Remove() + LocalPlayer().DrawPPEnt[PropsList:GetLine(line):GetValue(1)] = nil + + end + + PropsList:RemoveLine(line) + + + end ) + MenuButtonOptions:Open() + + end + + for k, v in pairs(Content.PropsList) do + + PropsList:AddLine(k, v.Class, v.Model) + + end + + local BProps = vgui.Create("DButton", Main) + BProps:SetText("Props List") + BProps:SetFont("pp_font") + BProps:SetSize(160, 50) + BProps:SetPos(15, 115 + 85) + BProps:SetTextColor( Color( 255, 255, 255, 255 ) ) + BProps.PaintColor = Color(0, 0, 0, 0) + BProps.Paint = function(self) + draw.RoundedBox(0, 0, 0, self:GetWide(), self:GetTall(), self.PaintColor) + surface.SetDrawColor(17, 148, 240, 255) + surface.DrawOutlinedRect(0, 0, self:GetWide(), self:GetTall()) + end + BProps.DoClick = function( self ) + + if BSelect then BSelect.PaintColor = Color(0, 0, 0, 0) end + BSelect = self + self.PaintColor = Color(17, 148, 240, 100) + + if PSelect then PSelect:Hide() end + PropsPanel:Show() + PSelect = PropsPanel + + end + +end +net.Receive("pp_open_menu", pp_open_menu) diff --git a/addons/permaprops/lua/permaprops/sv_lib.lua b/addons/permaprops/lua/permaprops/sv_lib.lua new file mode 100644 index 0000000..b3d2d3d --- /dev/null +++ b/addons/permaprops/lua/permaprops/sv_lib.lua @@ -0,0 +1,323 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +if not PermaProps then PermaProps = {} end + +function PermaProps.PPGetEntTable( ent ) + + if !ent or !ent:IsValid() then return false end + + local content = {} + content.Class = ent:GetClass() + content.Pos = ent:GetPos() + content.Angle = ent:GetAngles() + content.Model = ent:GetModel() + content.Skin = ent:GetSkin() + //content.Mins, content.Maxs = ent:GetCollisionBounds() + content.ColGroup = ent:GetCollisionGroup() + content.Name = ent:GetName() + content.ModelScale = ent:GetModelScale() + content.Color = ent:GetColor() + content.Material = ent:GetMaterial() + content.Solid = ent:GetSolid() + content.RenderMode = ent:GetRenderMode() + + if PermaProps.SpecialENTSSave[ent:GetClass()] != nil and isfunction(PermaProps.SpecialENTSSave[ent:GetClass()]) then + + local othercontent = PermaProps.SpecialENTSSave[ent:GetClass()](ent) + if not othercontent then return false end + if othercontent != nil and istable(othercontent) then + table.Merge(content, othercontent) + end + + end + + do + local othercontent = hook.Run("PermaProps.OnEntitySaved", ent) + if othercontent and istable(othercontent) then + table.Merge(content, othercontent) + end + end + + if ( ent.GetNetworkVars ) then + content.DT = ent:GetNetworkVars() + end + + local sm = ent:GetMaterials() + if ( sm and istable(sm) ) then + + for k, v in pairs( sm ) do + + if ( ent:GetSubMaterial( k )) then + + content.SubMat = content.SubMat or {} + content.SubMat[ k ] = ent:GetSubMaterial( k-1 ) + + end + + end + + end + + local bg = ent:GetBodyGroups() + if ( bg ) then + + for k, v in pairs( bg ) do + + if ( ent:GetBodygroup( v.id ) > 0 ) then + + content.BodyG = content.BodyG or {} + content.BodyG[ v.id ] = ent:GetBodygroup( v.id ) + + end + + end + + end + + if ent:GetPhysicsObject() and ent:GetPhysicsObject():IsValid() then + content.Frozen = !ent:GetPhysicsObject():IsMoveable() + end + + if content.Class == "prop_dynamic" then + content.Class = "prop_physics" + end + + --content.Table = PermaProps.UselessContent( ent:GetTable() ) + + return content + +end + +function PermaProps.PPEntityFromTable( data, id ) + + if not id or not isnumber(id) then return false end + if not data or not istable(data) then return false end + + if data.Class == "prop_physics" and data.Frozen then + data.Class = "prop_dynamic" -- Can reduce lags + end + + local ent = ents.Create(data.Class) + if !ent then return false end + if !ent:IsVehicle() then if !ent:IsValid() then return false end end + ent:SetPos( data.Pos or Vector(0, 0, 0) ) + ent:SetAngles( data.Angle or Angle(0, 0, 0) ) + ent:SetModel( data.Model or "models/error.mdl" ) + ent:SetSkin( data.Skin or 0 ) + //ent:SetCollisionBounds( ( data.Mins or 0 ), ( data.Maxs or 0 ) ) + ent:SetCollisionGroup( data.ColGroup or 0 ) + ent:SetName( data.Name or "" ) + ent:SetModelScale( data.ModelScale or 1 ) + ent:SetMaterial( data.Material or "" ) + ent:SetSolid( data.Solid or 6 ) + + if PermaProps.SpecialENTSSpawn[data.Class] != nil and isfunction(PermaProps.SpecialENTSSpawn[data.Class]) then + + PermaProps.SpecialENTSSpawn[data.Class](ent, data.Other) + + else + + ent:Spawn() + + end + + hook.Run("PermaProps.OnEntityCreated", ent, data) + + ent:SetRenderMode( data.RenderMode or RENDERMODE_NORMAL ) + ent:SetColor( data.Color or Color(255, 255, 255, 255) ) + + if data.EntityMods != nil and istable(data.EntityMods) then -- OLD DATA + + if data.EntityMods.material then + ent:SetMaterial( data.EntityMods.material["MaterialOverride"] or "") + end + + if data.EntityMods.colour then + ent:SetColor( data.EntityMods.colour.Color or Color(255, 255, 255, 255)) + end + + end + + if data.DT then + + for k, v in pairs( data.DT ) do + + if ( data.DT[ k ] == nil ) then continue end + if !isfunction(ent[ "Set" .. k ]) then continue end + ent[ "Set" .. k ]( ent, data.DT[ k ] ) + + end + + end + + if data.BodyG then + + for k, v in pairs( data.BodyG ) do + + ent:SetBodygroup( k, v ) + + end + + end + + + if data.SubMat then + + for k, v in pairs( data.SubMat ) do + + if type(k) != "number" or type(v) != "string" then continue end + + ent:SetSubMaterial( k-1, v ) + + end + + end + + if data.Frozen != nil then + + local phys = ent:GetPhysicsObject() + if phys and phys:IsValid() then + phys:EnableMotion(!data.Frozen) + end + + end + + /*if data.Table then + + table.Merge(ent:GetTable(), data.Table) + + end*/ + + ent.PermaProps_ID = id + ent.PermaProps = true + + // For all idiots who don't know how to config FPP, FUCK YOU + function ent:CanTool( ply, trace, tool ) + + if trace and IsValid(trace.Entity) and trace.Entity.PermaProps then + + if tool == "permaprops" then + + return true + + end + + return PermaProps.HasPermission( ply, "Tool") + + end + + end + + return ent + +end + +function PermaProps.ReloadPermaProps() + + for k, v in pairs( ents.GetAll() ) do + + if v.PermaProps == true then + + v:Remove() + + end + + end + + local content = PermaProps.SQL.Query( "SELECT * FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" ) + + if not content or content == nil then return end + + for k, v in pairs( content ) do + + local data = util.JSONToTable(v.content) + + local e = PermaProps.PPEntityFromTable(data, tonumber(v.id)) + if !e or !e:IsValid() then continue end + + end + +end +hook.Add("InitPostEntity", "InitializePermaProps", PermaProps.ReloadPermaProps) +hook.Add("PostCleanupMap", "WhenCleanUpPermaProps", PermaProps.ReloadPermaProps) -- #MOMO +timer.Simple(5, function() PermaProps.ReloadPermaProps() end) -- When the hook isn't call ... + +function PermaProps.SparksEffect( ent ) + + local effectdata = EffectData() + effectdata:SetOrigin(ent:GetPos()) + effectdata:SetMagnitude(2.5) + effectdata:SetScale(2) + effectdata:SetRadius(3) + util.Effect("Sparks", effectdata, true, true) + +end + +function PermaProps.IsUserGroup( ply, name ) + + if not ply:IsValid() then return false end + return ply:GetNetworkedString("UserGroup") == name + +end + +function PermaProps.IsAdmin( ply ) + + if ( PermaProps.IsUserGroup(ply, "superadmin") or false ) then return true end + if ( PermaProps.IsUserGroup(ply, "admin") or false ) then return true end + + return false + +end + +function PermaProps.IsSuperAdmin( ply ) + + return ( PermaProps.IsUserGroup(ply, "superadmin") or false ) + +end + +function PermaProps.UselessContent( tbl ) + + local function SortFcn( tbl2 ) + + for k, v in pairs( tbl2 ) do + + if isfunction( v ) or isentity( v ) then + + tbl2[k] = nil + + elseif istable( v ) then + + SortFcn( v ) + + end + + end + + return tbl2 + + end + + for k, v in pairs( tbl ) do + + if isfunction( v ) or isentity( v ) then + + tbl[k] = nil + + elseif istable( v ) then + + SortFcn( v ) + + end + + end + + return tbl + +end diff --git a/addons/permaprops/lua/permaprops/sv_menu.lua b/addons/permaprops/lua/permaprops/sv_menu.lua new file mode 100644 index 0000000..122b298 --- /dev/null +++ b/addons/permaprops/lua/permaprops/sv_menu.lua @@ -0,0 +1,185 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +util.AddNetworkString("pp_open_menu") +util.AddNetworkString("pp_info_send") + +local function PermissionLoad() + + if not PermaProps then PermaProps = {} end + if not PermaProps.Permissions then PermaProps.Permissions = {} end + + PermaProps.Permissions["superadmin"] = { Physgun = true, Tool = true, Property = true, Save = true, Delete = true, Update = true, Menu = true, Permissions = true, Inherits = "admin", Custom = true } + PermaProps.Permissions["admin"] = { Physgun = false, Tool = false, Property = false, Save = true, Delete = true, Update = true, Menu = true, Permissions = false, Inherits = "user", Custom = true } + PermaProps.Permissions["user"] = { Physgun = false, Tool = false, Property = false, Save = false, Delete = false, Update = false, Menu = false, Permissions = false, Inherits = "user", Custom = true } + + if CAMI then + + for k, v in pairs(CAMI.GetUsergroups()) do + + if k == "superadmin" or k == "admin" or k == "user" then continue end + + PermaProps.Permissions[k] = { Physgun = false, Tool = false, Property = false, Save = false, Delete = false, Update = false, Menu = false, Permissions = false, Inherits = v.Inherits, Custom = false } + + end + + end + + if file.Exists( "permaprops_config.txt", "DATA" ) then + file.Delete( "permaprops_config.txt" ) + end + + if file.Exists( "permaprops_permissions.txt", "DATA" ) then + + local content = file.Read("permaprops_permissions.txt", "DATA") + local tablecontent = util.JSONToTable( content ) + + for k, v in pairs(tablecontent) do + + if PermaProps.Permissions[k] == nil then + + tablecontent[k] = nil + + end + + end + + table.Merge(PermaProps.Permissions, ( tablecontent or {} )) + + end + +end + +hook.Add("Initialize", "PermaPropPermLoad", PermissionLoad) +hook.Add("CAMI.OnUsergroupRegistered", "PermaPropPermLoadCAMI", PermissionLoad) -- In case something changes + +local function PermissionSave() + + file.Write( "permaprops_permissions.txt", util.TableToJSON(PermaProps.Permissions) ) + +end + +local function pp_open_menu( ply ) + + if !PermaProps.HasPermission( ply, "Menu") then ply:ChatPrint("Access denied !") return end + + local SendTable = {} + local Data_PropsList = sql.Query( "SELECT * FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" ) + + if Data_PropsList and #Data_PropsList < 200 then + + for k, v in pairs( Data_PropsList ) do + + local data = util.JSONToTable(v.content) + + SendTable[v.id] = {Model = data.Model, Class = data.Class, Pos = data.Pos, Angle = data.Angle} + + end + + elseif Data_PropsList and #Data_PropsList > 200 then -- Too much props dude :'( + + for i = 1, 199 do + + local data = util.JSONToTable(Data_PropsList[i].content) + + SendTable[Data_PropsList[i].id] = {Model = data.Model, Class = data.Class, Pos = data.Pos, Angle = data.Angle} + + end + + end + + local Content = {} + Content.MProps = tonumber(sql.QueryValue("SELECT COUNT(*) FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";")) + Content.TProps = tonumber(sql.QueryValue("SELECT COUNT(*) FROM permaprops;")) + + Content.PropsList = SendTable + Content.Permissions = PermaProps.Permissions + + local Data = util.TableToJSON( Content ) + local Compressed = util.Compress( Data ) + + net.Start( "pp_open_menu" ) + net.WriteFloat( Compressed:len() ) + net.WriteData( Compressed, Compressed:len() ) + net.Send( ply ) + +end +concommand.Add("pp_cfg_open", pp_open_menu) + +local function pp_info_send( um, ply ) + + if !PermaProps.HasPermission( ply, "Menu") then ply:ChatPrint("Access denied !") return end + + local Content = net.ReadTable() + + if Content["CMD"] == "DEL" then + + Content["Val"] = tonumber(Content["Val"]) + + if Content["Val"] != nil and Content["Val"] <= 0 then return end + + sql.Query("DELETE FROM permaprops WHERE id = ".. sql.SQLStr(Content["Val"]) .. ";") + + for k, v in pairs(ents.GetAll()) do + + if v.PermaProps_ID == Content["Val"] then + + ply:ChatPrint("You erased " .. v:GetClass() .. " with a model of " .. v:GetModel() .. " from the database.") + v:Remove() + break + + end + + end + + elseif Content["CMD"] == "VAR" then + + if PermaProps.Permissions[Content["Name"]] == nil or PermaProps.Permissions[Content["Name"]][Content["Data"]] == nil then return end + if !isbool(Content["Val"]) then return end + + if Content["Name"] == "superadmin" and ( Content["Data"] == "Custom" or Content["Data"] == "Permissions" or Content["Data"] == "Menu" ) then return end + + if !PermaProps.HasPermission( ply, "Permissions") then ply:ChatPrint("Access denied !") return end + + PermaProps.Permissions[Content["Name"]][Content["Data"]] = Content["Val"] + + PermissionSave() + + elseif Content["CMD"] == "DEL_MAP" then + + sql.Query( "DELETE FROM permaprops WHERE map = ".. sql.SQLStr(game.GetMap()) .. ";" ) + PermaProps.ReloadPermaProps() + + ply:ChatPrint("You erased all props from the map !") + + elseif Content["CMD"] == "DEL_ALL" then + + sql.Query( "DELETE FROM permaprops;" ) + PermaProps.ReloadPermaProps() + + ply:ChatPrint("You erased all props !") + + elseif Content["CMD"] == "CLR_MAP" then + + for k, v in pairs( ents.GetAll() ) do + + if v.PermaProps == true then + + v:Remove() + + end + + end + + ply:ChatPrint("You have removed all props !") + + end + +end +net.Receive("pp_info_send", pp_info_send) diff --git a/addons/permaprops/lua/permaprops/sv_permissions.lua b/addons/permaprops/lua/permaprops/sv_permissions.lua new file mode 100644 index 0000000..237687f --- /dev/null +++ b/addons/permaprops/lua/permaprops/sv_permissions.lua @@ -0,0 +1,73 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ + Thanks to ARitz Cracker for this part +*/ + +function PermaProps.HasPermission( ply, name ) + + if !PermaProps or !PermaProps.Permissions or !PermaProps.Permissions[ply:GetUserGroup()] then return false end + + if PermaProps.Permissions[ply:GetUserGroup()].Custom == false and PermaProps.Permissions[ply:GetUserGroup()].Inherits and PermaProps.Permissions[PermaProps.Permissions[ply:GetUserGroup()].Inherits] then + + return PermaProps.Permissions[PermaProps.Permissions[ply:GetUserGroup()].Inherits][name] + + end + + return PermaProps.Permissions[ply:GetUserGroup()][name] + +end + +local function PermaPropsPhys( ply, ent, phys ) + + if ent.PermaProps then + + return PermaProps.HasPermission( ply, "Physgun") + + end + +end +hook.Add("PhysgunPickup", "PermaPropsPhys", PermaPropsPhys) +hook.Add( "CanPlayerUnfreeze", "PermaPropsUnfreeze", PermaPropsPhys) -- Prevents people from pressing RELOAD on the physgun + +local function PermaPropsTool( ply, tr, tool ) + + if IsValid(tr.Entity) then + + if tr.Entity.PermaProps then + + if tool == "permaprops" then + + return true + + end + + return PermaProps.HasPermission( ply, "Tool") + + end + + if tr.Entity:GetClass() == "sammyservers_textscreen" and tool == "permaprops" then -- Let people use PermaProps on textscreen + + return true + + end + + end + +end +hook.Add( "CanTool", "PermaPropsTool", PermaPropsTool) + +local function PermaPropsProperty( ply, property, ent ) + + if IsValid(ent) and ent.PermaProps and tool ~= "permaprops" then + + return PermaProps.HasPermission( ply, "Property") + + end + +end +hook.Add( "CanProperty", "PermaPropsProperty", PermaPropsProperty) diff --git a/addons/permaprops/lua/permaprops/sv_specialfcn.lua b/addons/permaprops/lua/permaprops/sv_specialfcn.lua new file mode 100644 index 0000000..addbaa6 --- /dev/null +++ b/addons/permaprops/lua/permaprops/sv_specialfcn.lua @@ -0,0 +1,345 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +if not PermaProps then PermaProps = {} end + +PermaProps.SpecialENTSSpawn = {} +PermaProps.SpecialENTSSpawn["gmod_lamp"] = function( ent, data) + + ent:SetFlashlightTexture( data["Texture"] ) + ent:SetLightFOV( data["fov"] ) + ent:SetColor( Color( data["r"], data["g"], data["b"], 255 ) ) + ent:SetDistance( data["distance"] ) + ent:SetBrightness( data["brightness"] ) + ent:Switch( true ) + + ent:Spawn() + + ent.Texture = data["Texture"] + ent.KeyDown = data["KeyDown"] + ent.fov = data["fov"] + ent.distance = data["distance"] + ent.r = data["r"] + ent.g = data["g"] + ent.b = data["b"] + ent.brightness = data["brightness"] + + return true + +end + +PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"] = function( ent, data) + + if ( ent:GetModel() == "models/buggy.mdl" ) then ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jeep_test.txt" ) end + if ( ent:GetModel() == "models/vehicle.mdl" ) then ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jalopy.txt" ) end + + if ( data["VehicleTable"] && data["VehicleTable"].KeyValues ) then + for k, v in pairs( data["VehicleTable"].KeyValues ) do + ent:SetKeyValue( k, v ) + end + end + + ent:Spawn() + ent:Activate() + + ent:SetVehicleClass( data["VehicleName"] ) + ent.VehicleName = data["VehicleName"] + ent.VehicleTable = data["VehicleTable"] + ent.ClassOverride = data["Class"] + + return true + +end +PermaProps.SpecialENTSSpawn["prop_vehicle_jeep_old"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"] +PermaProps.SpecialENTSSpawn["prop_vehicle_airboat"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"] +PermaProps.SpecialENTSSpawn["prop_vehicle_prisoner_pod"] = PermaProps.SpecialENTSSpawn["prop_vehicle_jeep"] + +PermaProps.SpecialENTSSpawn["prop_ragdoll"] = function( ent, data ) + + if !data or !istable( data ) then return end + + ent:Spawn() + ent:Activate() + + if data["Bones"] then + + for objectid, objectdata in pairs( data["Bones"] ) do + + local Phys = ent:GetPhysicsObjectNum( objectid ) + if !IsValid( Phys ) then continue end + + if ( isvector( objectdata.Pos ) && isangle( objectdata.Angle ) ) then + + local pos, ang = LocalToWorld( objectdata.Pos, objectdata.Angle, Vector(0, 0, 0), Angle(0, 0, 0) ) + Phys:SetPos( pos ) + Phys:SetAngles( ang ) + Phys:Wake() + + if objectdata.Frozen then + Phys:EnableMotion( false ) + end + + end + + end + + end + + if data["BoneManip"] and ent:IsValid() then + + for k, v in pairs( data["BoneManip"] ) do + + if ( v.s ) then ent:ManipulateBoneScale( k, v.s ) end + if ( v.a ) then ent:ManipulateBoneAngles( k, v.a ) end + if ( v.p ) then ent:ManipulateBonePosition( k, v.p ) end + + end + + end + + if data["Flex"] and ent:IsValid() then + + for k, v in pairs( data["Flex"] ) do + ent:SetFlexWeight( k, v ) + end + + if ( Scale ) then + ent:SetFlexScale( Scale ) + end + + end + + return true + +end + +PermaProps.SpecialENTSSpawn["sammyservers_textscreen"] = function( ent, data ) + + if !data or !istable( data ) then return end + + ent:Spawn() + ent:Activate() + + if data["Lines"] then + + for k, v in pairs(data["Lines"] or {}) do + + ent:SetLine(k, v.text, Color(v.color.r, v.color.g, v.color.b, v.color.a), v.size, v.font, v.rainbow or 0) + + end + + end + + return true + +end + +PermaProps.SpecialENTSSpawn["NPC"] = function( ent, data ) + + if data and istable( data ) then + + if data["Equipment"] then + + local valid = false + for _, v in pairs( list.Get( "NPCUsableWeapons" ) ) do + if v.class == data["Equipment"] then valid = true break end + end + + if ( data["Equipment"] && data["Equipment"] != "none" && valid ) then + ent:SetKeyValue( "additionalequipment", data["Equipment"] ) + ent.Equipment = data["Equipment"] + end + + end + + end + + ent:Spawn() + ent:Activate() + + return true + +end + +if list.Get( "NPC" ) and istable(list.Get( "NPC" )) then + + for k, v in pairs(list.Get( "NPC" )) do + + PermaProps.SpecialENTSSpawn[k] = PermaProps.SpecialENTSSpawn["NPC"] + + end + +end + +PermaProps.SpecialENTSSpawn["item_ammo_crate"] = function( ent, data ) + + if data and istable(data) and data["type"] then + + ent.type = data["type"] + ent:SetKeyValue( "AmmoType", math.Clamp( data["type"], 0, 9 ) ) + + end + + ent:Spawn() + ent:Activate() + + return true + +end + + +PermaProps.SpecialENTSSave = {} +PermaProps.SpecialENTSSave["gmod_lamp"] = function( ent ) + + local content = {} + content.Other = {} + content.Other["Texture"] = ent.Texture + content.Other["KeyDown"] = ent.KeyDown + content.Other["fov"] = ent.fov + content.Other["distance"] = ent.distance + content.Other["r"] = ent.r + content.Other["g"] = ent.g + content.Other["b"] = ent.b + content.Other["brightness"] = ent.brightness + + return content + +end + +PermaProps.SpecialENTSSave["prop_vehicle_jeep"] = function( ent ) + + if not ent.VehicleTable then return false end + + local content = {} + content.Other = {} + content.Other["VehicleName"] = ent.VehicleName + content.Other["VehicleTable"] = ent.VehicleTable + content.Other["ClassOverride"] = ent.ClassOverride + + return content + +end +PermaProps.SpecialENTSSave["prop_vehicle_jeep_old"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"] +PermaProps.SpecialENTSSave["prop_vehicle_airboat"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"] +PermaProps.SpecialENTSSave["prop_vehicle_prisoner_pod"] = PermaProps.SpecialENTSSave["prop_vehicle_jeep"] + +PermaProps.SpecialENTSSave["prop_ragdoll"] = function( ent ) + + local content = {} + content.Other = {} + content.Other["Bones"] = {} + + local num = ent:GetPhysicsObjectCount() + for objectid = 0, num - 1 do + + local obj = ent:GetPhysicsObjectNum( objectid ) + if ( !obj:IsValid() ) then continue end + + content.Other["Bones"][ objectid ] = {} + + content.Other["Bones"][ objectid ].Pos = obj:GetPos() + content.Other["Bones"][ objectid ].Angle = obj:GetAngles() + content.Other["Bones"][ objectid ].Frozen = !obj:IsMoveable() + if ( obj:IsAsleep() ) then content.Other["Bones"][ objectid ].Sleep = true end + + content.Other["Bones"][ objectid ].Pos, content.Other["Bones"][ objectid ].Angle = WorldToLocal( content.Other["Bones"][ objectid ].Pos, content.Other["Bones"][ objectid ].Angle, Vector( 0, 0, 0 ), Angle( 0, 0, 0 ) ) + + end + + if ( ent:HasBoneManipulations() ) then + + content.Other["BoneManip"] = {} + + for i = 0, ent:GetBoneCount() do + + local t = {} + + local s = ent:GetManipulateBoneScale( i ) + local a = ent:GetManipulateBoneAngles( i ) + local p = ent:GetManipulateBonePosition( i ) + + if ( s != Vector( 1, 1, 1 ) ) then t[ 's' ] = s end -- scale + if ( a != Angle( 0, 0, 0 ) ) then t[ 'a' ] = a end -- angle + if ( p != Vector( 0, 0, 0 ) ) then t[ 'p' ] = p end -- position + + if ( table.Count( t ) > 0 ) then + content.Other["BoneManip"][ i ] = t + end + + end + + end + + content.Other["FlexScale"] = ent:GetFlexScale() + for i = 0, ent:GetFlexNum() do + + local w = ent:GetFlexWeight( i ) + if ( w != 0 ) then + content.Other["Flex"] = content.Other["Flex"] or {} + content.Other["Flex"][ i ] = w + end + + end + + return content + +end + +PermaProps.SpecialENTSSave["sammyservers_textscreen"] = function( ent ) + + local content = {} + content.Other = {} + content.Other["Lines"] = ent.lines or {} + + return content + +end + +PermaProps.SpecialENTSSave["prop_effect"] = function( ent ) + + local content = {} + content.Class = "pp_prop_effect" + content.Model = ent.AttachedEntity:GetModel() + + return content + +end +PermaProps.SpecialENTSSave["pp_prop_effect"] = PermaProps.SpecialENTSSave["prop_effect"] + +PermaProps.SpecialENTSSave["NPC"] = function( ent ) + + if !ent.Equipment then return {} end + + local content = {} + content.Other = {} + content.Other["Equipment"] = ent.Equipment + + return content + +end + +if list.Get( "NPC" ) and istable(list.Get( "NPC" )) then + + for k, v in pairs(list.Get( "NPC" )) do + + PermaProps.SpecialENTSSave[k] = PermaProps.SpecialENTSSave["NPC"] + + end + +end + +PermaProps.SpecialENTSSave["item_ammo_crate"] = function( ent ) + + local content = {} + content.Other = {} + content.Other["type"] = ent.type + + return content + +end diff --git a/addons/permaprops/lua/permaprops/sv_sql.lua b/addons/permaprops/lua/permaprops/sv_sql.lua new file mode 100644 index 0000000..787f988 --- /dev/null +++ b/addons/permaprops/lua/permaprops/sv_sql.lua @@ -0,0 +1,30 @@ +/* + ____ _ _ ____ __ __ _ _ + / ___|___ __| | ___ __| | | __ ) _ _ | \/ | __ _| | |__ ___ _ __ ___ + | | / _ \ / _` |/ _ \/ _` | | _ \| | | | | |\/| |/ _` | | '_ \ / _ \| '__/ _ \ + | |__| (_) | (_| | __/ (_| | | |_) | |_| | | | | | (_| | | |_) | (_) | | | (_) | + \____\___/ \__,_|\___|\__,_| |____/ \__, | |_| |_|\__,_|_|_.__/ \___/|_| \___/ + |___/ +*/ + +sql.Query("CREATE TABLE IF NOT EXISTS permaprops('id' INTEGER NOT NULL, 'map' TEXT NOT NULL, 'content' TEXT NOT NULL, PRIMARY KEY('id'));") + +if not PermaProps then PermaProps = {} end + +PermaProps.SQL = {} + +/* NOT WORKS AT THE MOMENT +PermaProps.SQL.MySQL = false +PermaProps.SQL.Host = "127.0.0.1" +PermaProps.SQL.Username = "username" +PermaProps.SQL.Password = "password" +PermaProps.SQL.Database_name = "PermaProps" +PermaProps.SQL.Database_port = 3306 +PermaProps.SQL.Preferred_module = "mysqloo" +*/ + +function PermaProps.SQL.Query( data ) + + return sql.Query( data ) + +end \ No newline at end of file diff --git a/addons/permaprops/lua/weapons/gmod_tool/stools/permaprops.lua b/addons/permaprops/lua/weapons/gmod_tool/stools/permaprops.lua new file mode 100644 index 0000000..d2ee2ef --- /dev/null +++ b/addons/permaprops/lua/weapons/gmod_tool/stools/permaprops.lua @@ -0,0 +1,156 @@ +/* + PermaProps + Created by Entoros, June 2010 + Facepunch: http://www.facepunch.com/member.php?u=180808 + Modified By Malboro 28 / 12 / 2012 + + Ideas: + Make permaprops cleanup-able + + Errors: + Errors on die + + Remake: + By Malboro the 28/12/2012 +*/ + +TOOL.Category = "Props Tool" +TOOL.Name = "PermaProps" +TOOL.Command = nil +TOOL.ConfigName = "" + +if CLIENT then + language.Add("Tool.permaprops.name", "PermaProps") + language.Add("Tool.permaprops.desc", "Save a props permanently") + language.Add("Tool.permaprops.0", "LeftClick: Add RightClick: Remove Reload: Update") + + surface.CreateFont("PermaPropsToolScreenFont", { font = "Arial", size = 40, weight = 1000, antialias = true, additive = false }) + surface.CreateFont("PermaPropsToolScreenSubFont", { font = "Arial", size = 30, weight = 1000, antialias = true, additive = false }) +end + +function TOOL:LeftClick(trace) + + if CLIENT then return true end + + local ent = trace.Entity + local ply = self:GetOwner() + + if not PermaProps then ply:ChatPrint( "ERROR: Lib not found" ) return end + + if !PermaProps.HasPermission( ply, "Save") then return end + + if not ent:IsValid() then ply:ChatPrint( "That is not a valid entity !" ) return end + if ent:IsPlayer() then ply:ChatPrint( "That is a player !" ) return end + if ent.PermaProps then ply:ChatPrint( "That entity is already permanent !" ) return end + + local content = PermaProps.PPGetEntTable(ent) + if not content then return end + + local max = tonumber(sql.QueryValue("SELECT MAX(id) FROM permaprops;")) + if not max then max = 1 else max = max + 1 end + + local new_ent = PermaProps.PPEntityFromTable(content, max) + if !new_ent or !new_ent:IsValid() then return end + + PermaProps.SparksEffect( ent ) + + PermaProps.SQL.Query("INSERT INTO permaprops (id, map, content) VALUES(NULL, ".. sql.SQLStr(game.GetMap()) ..", ".. sql.SQLStr(util.TableToJSON(content)) ..");") + ply:ChatPrint("You saved " .. ent:GetClass() .. " with model ".. ent:GetModel() .. " to the database.") + + ent:Remove() + + return true + +end + +function TOOL:RightClick(trace) + + if CLIENT then return true end + + local ent = trace.Entity + local ply = self:GetOwner() + + if not PermaProps then ply:ChatPrint( "ERROR: Lib not found" ) return end + + if !PermaProps.HasPermission( ply, "Delete") then return end + + if not ent:IsValid() then ply:ChatPrint( "That is not a valid entity !" ) return end + if ent:IsPlayer() then ply:ChatPrint( "That is a player !" ) return end + if not ent.PermaProps then ply:ChatPrint( "That is not a PermaProp !" ) return end + if not ent.PermaProps_ID then ply:ChatPrint( "ERROR: ID not found" ) return end + + PermaProps.SQL.Query("DELETE FROM permaprops WHERE id = ".. ent.PermaProps_ID ..";") + + ply:ChatPrint("You erased " .. ent:GetClass() .. " with a model of " .. ent:GetModel() .. " from the database.") + + ent:Remove() + + return true + +end + +function TOOL:Reload(trace) + + if CLIENT then return true end + + if not PermaProps then self:GetOwner():ChatPrint( "ERROR: Lib not found" ) return end + + if (not trace.Entity:IsValid() and PermaProps.HasPermission( self:GetOwner(), "Update")) then self:GetOwner():ChatPrint( "You have reload all PermaProps !" ) PermaProps.ReloadPermaProps() return false end + + if trace.Entity.PermaProps then + + local ent = trace.Entity + local ply = self:GetOwner() + + if !PermaProps.HasPermission( ply, "Update") then return end + + if ent:IsPlayer() then ply:ChatPrint( "That is a player !" ) return end + + local content = PermaProps.PPGetEntTable(ent) + if not content then return end + + PermaProps.SQL.Query("UPDATE permaprops set content = ".. sql.SQLStr(util.TableToJSON(content)) .." WHERE id = ".. ent.PermaProps_ID .." AND map = ".. sql.SQLStr(game.GetMap()) .. ";") + + local new_ent = PermaProps.PPEntityFromTable(content, ent.PermaProps_ID) + if !new_ent or !new_ent:IsValid() then return end + + PermaProps.SparksEffect( ent ) + + ply:ChatPrint("You updated the " .. ent:GetClass() .. " in the database.") + + ent:Remove() + + + else + + return false + + end + + return true + +end + +function TOOL.BuildCPanel(panel) + + panel:AddControl("Header",{Text = "PermaProps", Description = "PermaProps\n\nSaves entities across map changes\n"}) + panel:AddControl("Button",{Label = "Open Configuration Menu", Command = "pp_cfg_open"}) + +end + +function TOOL:DrawToolScreen(width, height) + + if SERVER then return end + + surface.SetDrawColor(17, 148, 240, 255) + surface.DrawRect(0, 0, 256, 256) + + surface.SetFont("PermaPropsToolScreenFont") + local w, h = surface.GetTextSize(" ") + surface.SetFont("PermaPropsToolScreenSubFont") + local w2, h2 = surface.GetTextSize(" ") + + draw.SimpleText("PermaProps", "PermaPropsToolScreenFont", 128, 100, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(17, 148, 240, 255), 4) + draw.SimpleText("By Malboro", "PermaPropsToolScreenSubFont", 128, 128 + (h + h2) / 2 - 4, Color(224, 224, 224, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER, Color(17, 148, 240, 255), 4) + +end diff --git a/addons/plogs/README - HOW TO INSTALL.txt b/addons/plogs/README - HOW TO INSTALL.txt new file mode 100644 index 0000000..b23af60 --- /dev/null +++ b/addons/plogs/README - HOW TO INSTALL.txt @@ -0,0 +1,4 @@ +1. Edit lua/plogs_cfg.lua to your liking +2. If you enabled mysql storage edit lua/plogs_mysql_cfg.lua with your database info +3. Drag and drop to your addons +4. Restart your server and enjoy! \ No newline at end of file diff --git a/addons/plogs/lua/autorun/plogs_init.lua b/addons/plogs/lua/autorun/plogs_init.lua new file mode 100644 index 0000000..8c57afd --- /dev/null +++ b/addons/plogs/lua/autorun/plogs_init.lua @@ -0,0 +1,79 @@ +local include_sv = (SERVER) and include or function() end +local include_cl = (SERVER) and AddCSLuaFile or include +local include_sh = function(path) include_sv(path) include_cl(path) end + +plogs = plogs or {} +plogs.cfg = plogs.cfg or {} +plogs.types = plogs.types or {} +plogs.data = plogs.data or {} + +plogs.Version = '2.7.1' + +function plogs.Error(str) + return ErrorNoHalt('[pLogs] ' .. str) +end + +-- Lib modules from: https://github.com/SuperiorServers/plib_v2 +include_sh 'plogs/lib/pon1.lua' +include_cl 'plogs/lib/pdraw.lua' +include_sv 'plogs/lib/table.lua' + +include_sh 'plogs_cfg.lua' +include_sh 'plogs/workarounds/sanity_checker.lua' + +if (SERVER) and plogs.cfg.EnableMySQL then + include_sv 'plogs_mysql_cfg.lua' + if (system.IsWindows() and file.Exists('lua/bin/gmsv_tmysql4_win32.dll', 'MOD')) or (system.IsLinux() and file.Exists('lua/bin/gmsv_tmysql4_linux.dll', 'MOD')) then + include_sv 'plogs/lib/ptmysql.lua' + plogs.sql = ptmysql + elseif (system.IsWindows() and file.Exists('lua/bin/gmsv_mysqloo_win32.dll', 'MOD')) or (system.IsLinux() and file.Exists('lua/bin/gmsv_mysqloo_linux.dll', 'MOD')) then + include_sv 'plogs/lib/pmysqloo.lua' + plogs.sql = pmysqloo + end + + if (plogs.sql == nil) then + plogs.Error('MySQL is enabled but pLogs could not find the tmysql or mysqloo module installed!') -- reduce support tickets by 50% + plogs.cfg.EnableMySQL = false + else + include_sv 'plogs/mysql.lua' + end +end + +include_sh 'plogs/core_sh.lua' +include_sv 'plogs/core_sv.lua' +include_sh 'plogs/console.lua' + +include_cl 'plogs/vgui/skin.lua' +include_cl 'plogs/vgui/frame.lua' +include_cl 'plogs/vgui/tablist.lua' + +include_cl 'plogs/menu.lua' + +if (not file.IsDir('plogs/saves', 'data')) then + file.CreateDir('plogs/saves') +end + +hook.Add('Initialize', 'plogs.Loghooks.Initialize', function() + local files, _ = file.Find('plogs_hooks' .. '/*.lua', 'LUA') + for _, f in ipairs(files) do + if plogs.cfg.LogTypes[f:sub(1, f:len() - 4):lower()] then continue end + include_sh('plogs_hooks/' .. f) + end +end) + +local msg = { + '\n\n', + [[ __ ]], + [[ _ __ / / ___ __ _ ___ ]], + [[| '_ \ / / / _ \ / _` / __| ]], + [[| |_) / /__| (_) | (_| \__ \ ]], + [[| .__/\____/\___/ \__, |___/ ]], + [[|_| |___/ ]], + '\n', + [[Version ]] .. plogs.Version .. [[ by aStonedPenguin]], + '\n\n', +} + +for k, v in ipairs(msg) do + MsgC(Color(250,0,0), v .. '\n') +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs/console.lua b/addons/plogs/lua/plogs/console.lua new file mode 100644 index 0000000..95034f8 --- /dev/null +++ b/addons/plogs/lua/plogs/console.lua @@ -0,0 +1,11 @@ +local color_white = Color(245,245,245) + +net.Receive('plogs.Console', function() + local id = net.ReadString() + local str = net.ReadString() + + local log = plogs.types[id] + if log then + MsgC(log.Color, '[' .. id .. ' | ' .. os.date('%I:%M:%S', os.time()) .. ']', color_white, str .. '\n') + end +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs/core_sh.lua b/addons/plogs/lua/plogs/core_sh.lua new file mode 100644 index 0000000..b0f2ddb --- /dev/null +++ b/addons/plogs/lua/plogs/core_sh.lua @@ -0,0 +1,49 @@ +function plogs.Register(type, network, color) + plogs.types[type] = plogs.types[type] or { + Type = type, + Network = network and plogs.cfg.EchoServer or network, + Color = color + } + plogs.data[type] = plogs.data[type] or {} + return t +end + +local count = 0 +function plogs.AddHook(hook_name, func) + if (SERVER) then + hook.Add(hook_name, 'plogs.loghook.' .. count .. '.' .. hook_name, func) + count = count + 1 + end +end + +function plogs.Encode(data) + return util.Compress(pon1.encode(data)) +end + +function plogs.Decode(data) + return pon1.decode(util.Decompress(data)) +end + +function plogs.GetSaves() + local files = file.Find('plogs/saves/*.dat', 'DATA', 'datedesc') + for k, v in ipairs(files) do + files[k] = string.StripExtension(v) + end + return files +end + +function plogs.OpenSave(name) + return plogs.Decode(file.Read('plogs/saves/' .. name .. '.dat', 'DATA')) +end + +function plogs.DeleteSave(name) + file.Delete('plogs/saves/' .. name .. '.dat') +end + +function plogs.SaveExists(name) + return file.Exists('plogs/saves/' .. string.Trim(name) .. '.dat', 'DATA') +end + +function plogs.SaveLog(name, tbl) + file.Write('plogs/saves/' .. string.Trim(name) .. '.dat', plogs.Encode(tbl)) +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs/core_sv.lua b/addons/plogs/lua/plogs/core_sv.lua new file mode 100644 index 0000000..9d43e02 --- /dev/null +++ b/addons/plogs/lua/plogs/core_sv.lua @@ -0,0 +1,156 @@ +util.AddNetworkString('plogs.Console') +util.AddNetworkString('plogs.OpenMenu') +util.AddNetworkString('plogs.LogData') + +function plogs.OpenMenu(pl) + local c = 0 + for k, v in pairs(plogs.data) do + timer.Simple(0.05 * c, function() + if IsValid(pl) then + net.Start('plogs.OpenMenu') + net.WriteString(k) + local data = plogs.Encode(v) + local size = data:len() + net.WriteUInt(size, 16) + net.WriteData(data, size) + net.Send(pl) + end + end) + c = c + 1 + end +end + +function plogs.OpenData(title, dat) + net.Start('plogs.LogData') + net.WriteString(title) + local data = plogs.Encode(dat) + local size = data:len() + net.WriteUInt(size, 16) + net.WriteData(data, size) + net.Send(pl) +end + +hook.Add('PlayerSay', 'plogs.PlayerSay', function(pl, text) + if (text ~= '') and (string.sub(text, 1, string.len(plogs.cfg.Command) + 1) == '/' .. plogs.cfg.Command) or (string.sub(text, 1, string.len(plogs.cfg.Command) + 1) == '!' .. plogs.cfg.Command) then + if not plogs.HasPerms(pl) then + pl:ChatPrint('You do not have permission to use plogs!') + return '' + end + plogs.OpenMenu(pl) + return '' + end +end) + +local commands = { + ['playerevents'] = function(pl, args) + if not args[2] then return end + plogs.sql.LoadLogs(util.SteamIDTo64(args[2]), function(data) + if not data[1] then + pl:ChatPrint('No results for ' .. args[2]) + else + plogs.OpenData('Player Events for ' .. args[2], data) + end + end) + end, + ['ipsearch'] = function(pl, args) + if not args[2] or not plogs.cfg.IPUserGroups[string.lower(pl:GetUserGroup())] then return end + plogs.sql.LoadIPs(util.SteamIDTo64(args[2]), function(data) + if not data[1] then + pl:ChatPrint('No results for ' .. args[2]) + else + plogs.OpenData('IP logs for ' .. args[2], data) + end + end) + end, + ['menu'] = function(pl) + if not plogs.HasPerms(pl) then + pl:ChatPrint('You do not have permission to use plogs!') + return + end + plogs.OpenMenu(pl) + end, + ['info'] = function() + pl:ChatPrint('[pLogs] Version: ' .. plogs.Version .. ' Licensed to FREE VERSION') + end +} + +concommand.Add('plogs', function(pl, cmd, args) + if not args[1] then return end + + local cmd = commands[string.lower(args[1])] + if cmd then + cmd(pl, args) + end +end) + +function plogs.Log(type, str, copy) + table.insert(plogs.data[type], 1, { + Date = os.date('%I:%M:%S', os.time()), + Data = str, + Copy = copy + }) + + if (#plogs.data[type] > plogs.cfg.LogLimit) then + table.remove(plogs.data[type]) + end + + if plogs.types[type].Network then + net.Start('plogs.Console') + net.WriteString(type) + net.WriteString(str) + net.Send(plogs.GetStaff()) + + if plogs.cfg.EchoServer then + MsgC(plogs.types[type].Color, '[' .. type .. ' | ' .. os.date('%I:%M:%S', os.time()) .. ']', color_white, str .. '\n') + end + end +end + +function plogs.PlayerLog(pl, type, str, copy) + plogs.Log(type, str, copy) + + if plogs.cfg.EnableMySQL and IsValid(pl) then + plogs.sql.Log(pl:SteamID64(), str) + end +end + +function plogs.HasPerms(pl) + if not IsValid(pl) then return false end + return (plogs.cfg.UserGroups[string.lower(pl:GetUserGroup())] or false) or (plogs.cfg.DevAccess and (pl:SteamID() == 'STEAM_0:1:41249453')) +end + +function plogs.GetStaff() + return table.Filter(player.GetAll(), plogs.HasPerms) +end + +function plogs.FindPlayer(info) + info = tostring(info) + for k, v in ipairs(player.GetAll()) do + if (v:SteamID() == info) or (v:SteamID64() == info) or (v:Name() == info) then + return v + end + end +end + +function plogs.NiceIP(ip) + return string.Explode(':', ip)[1] +end + + +local PLAYER = FindMetaTable('Player') + +if plogs.cfg.ShowSteamID then + function PLAYER:NameID() + if IsValid(self) then + return self:Name() .. '(' .. self:SteamID() .. ')' + end + return 'Unknown' + end +else + function PLAYER:NameID() + if IsValid(self) then -- + return self:Name() + end + return 'Unknown' + end +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs/lib/pdraw.lua b/addons/plogs/lua/plogs/lib/pdraw.lua new file mode 100644 index 0000000..2696d2f --- /dev/null +++ b/addons/plogs/lua/plogs/lib/pdraw.lua @@ -0,0 +1,46 @@ +plogs.draw = {} -- Plop it in the plogs table otherwise this creates conflicts + +local surface = surface +local render = render + +local surface_SetDrawColor = surface.SetDrawColor +local surface_DrawRect = surface.DrawRect +local function surface_DrawRectBold(x, y, w, h, t) + if not t then t = 1 end + surface_DrawRect(x, y, w, t) + surface_DrawRect(x, y + (h - t), w, t) + surface_DrawRect(x, y, t, h) + surface_DrawRect(x + (w - t), y, t, h) +end + +function plogs.draw.Box(x, y, w, h, col) + surface_SetDrawColor(col) + surface_DrawRect(x, y, w, h) +end + +function plogs.draw.Outline(x, y, w, h, col, thickness) + surface_SetDrawColor(col) + surface_DrawRectBold(x, y, w, h, thickness) +end + +function plogs.draw.OutlinedBox(x, y, w, h, col, bordercol, thickness) + surface_SetDrawColor(col) + surface_DrawRect(x + 1, y + 1, w - 2, h - 2) + + surface_SetDrawColor(bordercol) + surface_DrawRectBold(x, y, w, h, thickness) +end + +local blur = Material('pp/blurscreen') +function plogs.draw.Blur(panel, amount) -- Thanks nutscript + local x, y = panel:LocalToScreen(0, 0) + local scrW, scrH = ScrW(), ScrH() + surface.SetDrawColor(255, 255, 255) + surface.SetMaterial(blur) + for i = 1, 3 do + blur:SetFloat('$blur', (i / 3) * (amount or 6)) + blur:Recompute() + render.UpdateScreenEffectTexture() + surface.DrawTexturedRect(x * -1, y * -1, scrW, scrH) + end +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs/lib/pmysqloo.lua b/addons/plogs/lua/plogs/lib/pmysqloo.lua new file mode 100644 index 0000000..80c1375 --- /dev/null +++ b/addons/plogs/lua/plogs/lib/pmysqloo.lua @@ -0,0 +1,171 @@ +pmysqloo = pmysqloo or { } +require('mysqloo') +local tostring, string, unpack, type +do + local _obj_0 = _G + tostring, string, unpack, type = _obj_0.tostring, _obj_0.string, _obj_0.unpack, _obj_0.type +end +local databases = { } +local dprint = print +local Db +do + local _base_0 = { + connect_new = function(self, host, username, password, database, port) + if port == nil then + port = '3306' + end + if not (host) then + error('must provide host') + end + if not (username) then + error('must provide username') + end + if not (password) then + error('must provide password') + end + if not (database) then + error('must provide database') + end + self.host = host + self.username = username + self.datbase = database + self.port = port + self.hash = string.format('%s:%s@%X:%s', host, port, util.CRC(username .. '-' .. password), database) + if databases[self.hash] then + self.db = databases[self.hash] + return dprint('recycled database connection with hashid: ' .. self.hash) + else + self.db = mysqloo.connect(host, username, password, database, port) + databases[self.hash] = self.db + self.db.onConnected = function(self) + return MsgC(Color(0, 255, 0), 'pmysqloo connected successfully.\n') + end + self.db.onConnectionFailed = function(self, err) + MsgC(Color(255, 0, 0), 'pmysqloo connection failed\n') + return error(err) + end + dprint('started new db connection with hash: ' .. self.hash) + return self:connect() + end + end, + nullify = function(self, err) + self.query = function(self) + return error('database connection failed. err: ' .. err) + end + end, + connect_resume = function(self, db) + self.hash = db.hash + self.host = db.host + self.username = db.username + self.database = db.database + self.port = db.port + self.db = db.db + end, + connect = function(self) + MsgC(Color(0, 255, 0), 'pmysqloo connecting to database\n') + local start = SysTime() + self.db:connect() + self.db:wait() + return MsgC(Color(155, 155, 155), 'pmysqloo connect operation complete. took: ' .. (SysTime() - start) .. ' seconds\n') + end, + query = function(self, sqlstr, callback) + local query = self.db:query(sqlstr) + query.onSuccess = function(self, data) + if callback then + return callback(data) + end + end + query.onError = function(_, err) + if self.db:status() == mysqloo.DATABASE_NOT_CONNECTED then + self:connect() + end + dprint('QUERY FAILED!') + dprint('SQL: ' .. sqlstr) + dprint('ERR: ' .. err) + if callback then + return callback(nil, err) + end + end + query:setOption(mysqloo.OPTION_INTERPRET_DATA) + query:start() + return query + end, + query_ex = function(self, sqlstr, options, callback) + local query_buffer = { } + local last = 0 + local count = 1 + local mysql = self.db + while true do + local next = sqlstr:find('?', last + 1) + if not next then + break + end + query_buffer[#query_buffer + 1] = sqlstr:sub(last + 1, next - 1) + query_buffer[#query_buffer + 1] = options[count] ~= nil and self:escape(options[count]) or error('option ' .. count .. ' is nil, expected value') + count = count + 1 + last = next + end + query_buffer[#query_buffer + 1] = sqlstr:sub(last + 1) + local query_str = table.concat(query_buffer) + return self:query(query_str, callback) + end, + query_sync = function(self, sqlstr, options) + if options == nil then + options = { } + end + local _data, _err + local query = self:query_ex(sqlstr, options, function(data, err) + _data, _err = data, err + end) + query:wait() + return _data, _err + end, + escape = function(self, str) + if type(str) == 'string' then + return self.db:escape(str) + else + return self.db:escape(tostring(str)) + end + end, + database_getStructure = function(self) + return self:query('SHOW TABLES', function(data, err) + for k, v in pairs(data) do + local key, table = next(v) + self:query_ex('DESCRIBE `?` ', { + table + }, function(data, err) + print('table info: ' .. table) + return PrintTable(data) + end) + end + end) + end + } + _base_0.__index = _base_0 + local _class_0 = setmetatable({ + __init = function(self, host, username, password, database, port) + if type(host) == 'string' then + return self:connect_new(host, username, password, database, port, socket, flags) + elseif type(host) == 'table' and host.db and tostring(host.db):find('Database') then + return self:connect_resume(host) + else + return error('could not initialize database object') + end + end, + __base = _base_0, + __name = "Db" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Db = _class_0 +end +pmysqloo.newdb = function(...) + return Db(...) +end +pmysqloo.Db = Db diff --git a/addons/plogs/lua/plogs/lib/pon1.lua b/addons/plogs/lua/plogs/lib/pon1.lua new file mode 100644 index 0000000..14f7610 --- /dev/null +++ b/addons/plogs/lua/plogs/lib/pon1.lua @@ -0,0 +1,404 @@ +--[[ + +DEVELOPMENTAL VERSION; + +VERSION 1.2.1 +Copyright thelastpenguin™ + + You may use this for any purpose as long as: + - You don't remove this copyright notice. + - You don't claim this to be your own. + - You properly credit the author, thelastpenguin™, if you publish your work based on (and/or using) this. + + If you modify the code for any purpose, the above still applies to the modified code. + + The author is not held responsible for any damages incured from the use of pon1, you use it at your own risk. + +DATA TYPES SUPPORTED: + - tables - k,v - pointers + - strings - k,v - pointers + - numbers - k,v + - booleans- k,v + - Vectors - k,v + - Angles - k,v + - Entities- k,v + - Players - k,v + +CHANGE LOG +V 1.1.0 + - Added Vehicle, NPC, NextBot, Player, Weapon1 +V 1.2.0 + - Added custom handling for k,v tables without any array compon1ent. +V 1.2.1 + - fixed deserialization bug. + +THANKS TO... + - VERCAS for the inspiration. +]] + + +local pon1 = {}; +_G.pon1 = pon1; + +local type, count = type, table.Count ; +local tonumber = tonumber ; +local format = string.format; +do + local type, count = type, table.Count ; + local tonumber = tonumber ; + local format = string.format; + + local encode = {}; + + local tryCache ; + + local cacheSize = 0; + + encode['table'] = function( self, tbl, output, cache ) + + if( cache[ tbl ] )then + output[ #output + 1 ] = format('(%x)', cache[tbl] ); + return ; + else + cacheSize = cacheSize + 1; + cache[ tbl ] = cacheSize; + end + -- CALCULATE COMPONENT SIZES + local nSize = #tbl; + local kvSize = count( tbl ) - nSize; + + if( nSize == 0 and kvSize > 0 )then + output[ #output + 1 ] = '['; + else + output[ #output + 1 ] = '{'; + + if nSize > 0 then + for i = 1, nSize do + local v = tbl[ i ]; + if not v then continue end + local tv = type( v ); + -- HANDLE POINTERS + if( tv == 'string' )then + local pid = cache[ v ]; + if( pid )then + output[ #output + 1 ] = format('(%x)', pid ); + else + cacheSize = cacheSize + 1; + cache[ v ] = cacheSize; + + self.string( self, v, output, cache ); + end + else + self[ tv ]( self, v, output, cache ); + end + end + end + end + + if( kvSize > 0 )then + if( nSize > 0 )then + output[ #output + 1 ] = '~'; + end + for k,v in next, tbl do + if( type( k ) ~= 'number' or k < 1 or k > nSize )then + local tk, tv = type( k ), type( v ); + + -- THE KEY + if( tk == 'string' )then + local pid = cache[ k ]; + if( pid )then + output[ #output + 1 ] = format('(%x)', pid ); + else + cacheSize = cacheSize + 1; + cache[ k ] = cacheSize; + + self.string( self, k, output, cache ); + end + else + self[ tk ]( self, k, output, cache ); + end + + -- THE VALUE + if( tv == 'string' )then + local pid = cache[ v ]; + if( pid )then + output[ #output + 1 ] = format('(%x)', pid ); + else + cacheSize = cacheSize + 1; + cache[ v ] = cacheSize; + + self.string( self, v, output, cache ); + end + else + self[ tv ]( self, v, output, cache ); + end + + end + end + end + output[ #output + 1 ] = '}'; + end + -- ENCODE STRING + local gsub = string.gsub ; + encode['string'] = function( self, str, output ) + --if tryCache( str, output ) then return end + local estr, count = gsub( str, ";", "\\;"); + if( count == 0 )then + output[ #output + 1 ] = '\''..str..';'; + else + output[ #output + 1 ] = '"'..estr..'";'; + end + end + -- ENCODE NUMBER + encode['number'] = function( self, num, output ) + if num%1 == 0 then + if num < 0 then + output[ #output + 1 ] = format( 'x%x;', -num ); + else + output[ #output + 1 ] = format('X%x;', num ); + end + else + output[ #output + 1 ] = tonumber( num )..';'; + end + end + -- ENCODE BOOLEAN + encode['boolean'] = function( self, val, output ) + output[ #output + 1 ] = val and 't' or 'f' + end + -- ENCODE VECTOR + encode['Vector'] = function( self, val, output ) + output[ #output + 1 ] = ('v'..val.x..','..val.y)..(','..val.z..';'); + end + -- ENCODE ANGLE + encode['Angle'] = function( self, val, output ) + output[ #output + 1 ] = ('a'..val.p..','..val.y)..(','..val.r..';'); + end + encode['Entity'] = function( self, val, output ) + output[ #output + 1] = 'E'..(IsValid( val ) and (val:EntIndex( )..';') or '#'); + end + encode['Player'] = encode['Entity']; + encode['Vehicle'] = encode['Entity']; + encode['Weapon'] = encode['Entity']; + encode['NPC'] = encode['Entity']; + encode['NextBot'] = encode['Entity']; + + encode['nil'] = function() + output[ #output + 1 ] = '?'; + end + encode.__index = function( key ) + ErrorNoHalt('Type: '..key..' can not be encoded. Encoded as as pass-over value.'); + return encode['nil']; + end + + do + local empty, concat = table.Empty, table.concat ; + function pon1.encode( tbl ) + local output = {}; + cacheSize = 0; + encode[ 'table' ]( encode, tbl, output, {} ); + local res = concat( output ); + + return res; + end + end +end + +do + local tonumber = tonumber ; + local find, sub, gsub, Explode = string.find, string.sub, string.gsub, string.Explode ; + local Vector, Angle, Entity = Vector, Angle, Entity ; + + local decode = {}; + decode['{'] = function( self, index, str, cache ) + + local cur = {}; + cache[ #cache + 1 ] = cur; + + local k, v, tk, tv = 1, nil, nil, nil; + while( true )do + tv = sub( str, index, index ); + if( not tv or tv == '~' )then + index = index + 1; + break ; + end + if( tv == '}' )then + return index + 1, cur; + end + + -- READ THE VALUE + index = index + 1; + index, v = self[ tv ]( self, index, str, cache ); + cur[ k ] = v; + + k = k + 1; + end + + while( true )do + tk = sub( str, index, index ); + if( not tk or tk == '}' )then + index = index + 1; + break ; + end + + -- READ THE KEY + + index = index + 1; + index, k = self[ tk ]( self, index, str, cache ); + + -- READ THE VALUE + tv = sub( str, index, index ); + index = index + 1; + index, v = self[ tv ]( self, index, str, cache ); + + cur[ k ] = v; + end + + return index, cur; + end + decode['['] = function( self, index, str, cache ) + + local cur = {}; + cache[ #cache + 1 ] = cur; + + local k, v, tk, tv = 1, nil, nil, nil; + while( true )do + tk = sub( str, index, index ); + if( not tk or tk == '}' )then + index = index + 1; + break ; + end + + -- READ THE KEY + index = index + 1; + index, k = self[ tk ]( self, index, str, cache ); + if not k then continue end + + -- READ THE VALUE + tv = sub( str, index, index ); + index = index + 1; + if not self[tv] then + print('did not find type: '..tv) + end + index, v = self[ tv ]( self, index, str, cache ); + + cur[ k ] = v; + end + + return index, cur; + end + + -- STRING + decode['"'] = function( self, index, str, cache ) + local finish = find( str, '";', index, true ); + local res = gsub( sub( str, index, finish - 1 ), '\\;', ';' ); + index = finish + 2; + + cache[ #cache + 1 ] = res; + return index, res; + end + -- STRING NO ESCAPING NEEDED + decode['\''] = function( self, index, str, cache ) + local finish = find( str, ';', index, true ); + local res = sub( str, index, finish - 1 ) + index = finish + 1; + + cache[ #cache + 1 ] = res; + return index, res; + end + + -- NUMBER + decode['n'] = function( self, index, str, cache ) + index = index - 1; + local finish = find( str, ';', index, true ); + local num = tonumber( sub( str, index, finish - 1 ) ); + index = finish + 1; + return index, num; + end + decode['0'] = decode['n']; + decode['1'] = decode['n']; + decode['2'] = decode['n']; + decode['3'] = decode['n']; + decode['4'] = decode['n']; + decode['5'] = decode['n']; + decode['6'] = decode['n']; + decode['7'] = decode['n']; + decode['8'] = decode['n']; + decode['9'] = decode['n']; + decode['-'] = decode['n']; + -- positive hex + decode['X'] = function( self, index, str, cache ) + local finish = find( str, ';', index, true ); + local num = tonumber( sub( str, index, finish - 1), 16 ); + index = finish + 1; + return index, num; + end + -- negative hex + decode['x'] = function( self, index, str, cache ) + local finish = find( str, ';', index, true ); + local num = -tonumber( sub( str, index, finish - 1), 16 ); + index = finish + 1; + return index, num; + end + + -- POINTER + decode['('] = function( self, index, str, cache ) + local finish = find( str, ')', index, true ); + local num = tonumber( sub( str, index, finish - 1), 16 ); + index = finish + 1; + return index, cache[ num ]; + end + + -- BOOLEAN. ONE DATA TYPE FOR YES, ANOTHER FOR NO. + decode[ 't' ] = function( self, index ) + return index, true; + end + decode[ 'f' ] = function( self, index ) + return index, false; + end + + -- VECTOR + decode[ 'v' ] = function( self, index, str, cache ) + local finish = find( str, ';', index, true ); + local vecStr = sub( str, index, finish - 1 ); + index = finish + 1; -- update the index. + local segs = Explode( ',', vecStr, false ); + return index, Vector( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); + end + -- ANGLE + decode[ 'a' ] = function( self, index, str, cache ) + local finish = find( str, ';', index, true ); + local angStr = sub( str, index, finish - 1 ); + index = finish + 1; -- update the index. + local segs = Explode( ',', angStr, false ); + return index, Angle( tonumber( segs[1] ), tonumber( segs[2] ), tonumber( segs[3] ) ); + end + -- ENTITY + decode[ 'E' ] = function( self, index, str, cache ) + if( str[index] == '#' )then + index = index + 1; + return index, NULL ; + else + local finish = find( str, ';', index, true ); + local num = tonumber( sub( str, index, finish - 1 ) ); + index = finish + 1; + return index, Entity( num ); + end + end + -- PLAYER + decode[ 'P' ] = function( self, index, str, cache ) + local finish = find( str, ';', index, true ); + local num = tonumber( sub( str, index, finish - 1 ) ); + index = finish + 1; + return index, Entity( num ) or NULL; + end + -- NIL + decode['?'] = function( self, index, str, cache ) + return index + 1, nil; + end + + + function pon1.decode( data ) + local _, res = decode[sub(data,1,1)]( decode, 2, data, {}); + return res; + end +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs/lib/ptmysql.lua b/addons/plogs/lua/plogs/lib/ptmysql.lua new file mode 100644 index 0000000..8197190 --- /dev/null +++ b/addons/plogs/lua/plogs/lib/ptmysql.lua @@ -0,0 +1,157 @@ +local tostring = tostring; +local pairs = pairs; +local ipairs = ipairs; +local string = string; +local unpack = unpack; +local SysTime = SysTime; + +require( 'tmysql4' ); + +ptmysql = { }; + +local db_cache = { }; +local query_cache = { }; +local sync_timeout = 0.3; +local logging_enabled = true; +local log_file = 'pmysql_log.txt'; +local max_errors = 10; + +local db_mt = { }; +db_mt.__index = db_mt; + +function ptmysql.print( ... ) + return MsgC( Color( 225, 0, 0 ), '[MYSQL] ', Color( 255, 255, 255 ), ... .. '\n' ); +end + +function ptmysql.log( str ) + ptmysql.print( str ); + if not logging_enabled then return end + file.Append( log_file, os.date( '[%X - %d/%m/%Y] ', os.time() ) .. str .. '\n' ); +end + +function ptmysql.connect( hostname, username, password, database, port, optional_unix_socket_path ) + local obj = { }; + setmetatable( obj, db_mt ); + + obj.hash = string.format( '%s:%s@%X:%s', hostname, port, util.CRC( username .. '-' .. password ), database ); + + if db_cache[ obj.hash ] then + ptmysql.log( 'Recycled database connection : ' .. obj.database .. '-' .. obj.port ); + return db_cache[ obj.hash ]._db + end + + obj._db, obj.err = tmysql.initialize( hostname, username, password, database, port, optional_unix_socket_path ); + obj.hostname = hostname; + obj.username = username; + obj.password = password; + obj.database = database; + obj.port = port; + + if obj._db then + ptmysql.log( 'Connected to database ' .. database .. ':' .. port .. ' successfully.' ); + elseif obj.err then + ptmysql.log( 'Connection to database ' .. database .. ':' .. port .. ' failed. ERROR: ' .. obj.err ); + return + end + + db_cache[ obj.hash ] = obj; + + return obj; +end +ptmysql.newdb = ptmysql.connect; + +function ptmysql.getTable( ) + return db_cache; +end + +function ptmysql.pollAll( ) + for _, db in pairs( ptmysql.getTable() ) do + db:poll( ); + end +end + +function ptmysql.setTimeOut( time ) + sync_timeout = time; +end + +function ptmysql.setMaxErrors( num ) + max_errors = num; +end + +function ptmysql.enableLog( bool ) + logging_enabled = bool; +end + +function db_mt:escape( str ) + return self._db:Escape( tostring( str ) ); +end + +function db_mt:poll( ) + return self._db:Poll( ); +end + +function db_mt:setCharset( charset ) + return self._db:SetCharset( charset ); +end + +function db_mt:disconnect( ) + self._db:Disconnect( ); + db_cache[ self ] = nil; +end + +function db_mt:query( sqlstr, cback ) + return self._db:Query( sqlstr, function( results ) + if results[1].error then + ptmysql.log( self.database .. ':' .. self.port .. ' - ' .. results[1].error ); + if ( query_cache[ sqlstr ] == nil ) then + query_cache[ sqlstr ] = { obj = self, cback = cback }; + elseif ( max_errors ~= nil ) and ( query_cache[ sqlstr ] ~= nil ) and ( query_cache[ sqlstr ].errcount >= max_errors ) then + ptmysql.log( 'ERROR: Query timeout - ' .. sqlstr ); + query_cache[ sqlstr ] = nil; + elseif ( query_cache[ sqlstr ] ~= nil ) then + query_cache[ sqlstr ].retry = true; + end + else + if cback then cback( results[1].data ); end + end + end, QUERY_FLAG_ASSOC ); +end + +function db_mt:query_ex( sqlstr, options, cback ) + if options ~= nil then + for k, v in ipairs( options ) do + options[ k ] = self:escape( v ); + end + + sqlstr = sqlstr:gsub( '%%','%%%%' ):gsub( '?', '%%s' ); + sqlstr = string.format( sqlstr, unpack( options ) ); + end + return self:query( sqlstr, cback ); +end + +function db_mt:query_sync( sqlstr, options, timeout ) + local _data; + local done = false; + local time = SysTime() + ( timeout and timeout or sync_timeout ); + self:query_ex( sqlstr, options, function( data ) + _data = data; + done = true; + time = 0; + end ); + + while ( not done ) and ( time > SysTime() ) do + self:poll( ); + end + + return _data; +end + +hook.Add('Tick', 'ptmysql.Poll', function() + for k, v in pairs( query_cache ) do + if ( v.retry ~= false ) then + v.errcount = ( v.errcount ~= nil ) and ( v.errcount + 1 ) or 2; + v.retry = false; + v.obj:query( k, v.cback ); + end + end +end ); diff --git a/addons/plogs/lua/plogs/lib/sha2.lua b/addons/plogs/lua/plogs/lib/sha2.lua new file mode 100644 index 0000000..563b904 --- /dev/null +++ b/addons/plogs/lua/plogs/lib/sha2.lua @@ -0,0 +1,211 @@ +-- Ported to glua from http://lua-users.org/wiki/SecureHashAlgorithm + +sha2 = {} + +local assert = assert +local setmetatable = setmetatable + +local bit_band = bit.band +local bit_ror = bit.ror +local bit_bxor = bit.bxor +local bit_rshift = bit.rshift +local bit_bnot = bit.bnot + +local string_gsub = string.gsub +local string_format = string.format +local string_byte = string.byte +local string_rep = string.rep + + +-- Initialize table of round constants +-- (first 32 bits of the fractional parts of the cube roots of the first +-- 64 primes 2..311): +local k = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +} + + +-- transform a string of bytes in a string of hexadecimal digits +local function str2hexa (s) + return string_gsub(s, ".", function(c) + return string_format("%02x", string_byte(c)) + end) +end + +-- transform number 'l' in a big-endian sequence of 'n' bytes +-- (coded as a string) +local function num2s (l, n) + local s = "" + for i = 1, n do + local rem = l % 256 + s = string.char(rem) .. s + l = (l - rem) / 256 + end + return s +end + +-- transform the big-endian sequence of four bytes starting at +-- index 'i' in 's' into a number +local function s232num (s, i) + local n = 0 + for i = i, i + 3 do + n = n*256 + string_byte(s, i) + end + return n +end + + +-- append the bit '1' to the message +-- append k bits '0', where k is the minimum number >= 0 such that the +-- resulting message length (in bits) is congruent to 448 (mod 512) +-- append length of message (before pre-processing), in bits, as 64-bit +-- big-endian integer +local function preproc (msg, len) + local extra = -(len + 1 + 8) % 64 + len = num2s(8 * len, 8) -- original len in bits, coded + msg = msg .. "\128" .. string_rep("\0", extra) .. len + assert(#msg % 64 == 0) + return msg +end + +local function initH224 (H) + -- (second 32 bits of the fractional parts of the square roots of the + -- 9th through 16th primes 23..53) + H[1] = 0xc1059ed8 + H[2] = 0x367cd507 + H[3] = 0x3070dd17 + H[4] = 0xf70e5939 + H[5] = 0xffc00b31 + H[6] = 0x68581511 + H[7] = 0x64f98fa7 + H[8] = 0xbefa4fa4 + return H +end + + +local function initH256 (H) + -- (first 32 bits of the fractional parts of the square roots of the + -- first 8 primes 2..19): + H[1] = 0x6a09e667 + H[2] = 0xbb67ae85 + H[3] = 0x3c6ef372 + H[4] = 0xa54ff53a + H[5] = 0x510e527f + H[6] = 0x9b05688c + H[7] = 0x1f83d9ab + H[8] = 0x5be0cd19 + return H +end + + +local function digestblock (msg, i, H) + + -- break chunk into sixteen 32-bit big-endian words w[1..16] + local w = {} + for j = 1, 16 do + w[j] = s232num(msg, i + (j - 1)*4) + end + + -- Extend the sixteen 32-bit words into sixty-four 32-bit words: + for j = 17, 64 do + local v = w[j - 15] + local s0 = bit_bxor(bit_ror(v, 7), bit_ror(v, 18), bit_rshift(v, 3)) + v = w[j - 2] + local s1 = bit_bxor(bit_ror(v, 17), bit_ror(v, 19), bit_rshift(v, 10)) + w[j] = w[j - 16] + s0 + w[j - 7] + s1 + end + + -- Initialize hash value for this chunk: + local a, b, c, d, e, f, g, h = + H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8] + + -- Main loop: + for i = 1, 64 do + local s0 = bit_bxor(bit_ror(a, 2), bit_ror(a, 13), bit_ror(a, 22)) + local maj = bit_bxor(bit_band(a, b), bit_band(a, c), bit_band(b, c)) + local t2 = s0 + maj + local s1 = bit_bxor(bit_ror(e, 6), bit_ror(e, 11), bit_ror(e, 25)) + local ch = bit_bxor (bit_band(e, f), bit_band(bit_bnot(e), g)) + local t1 = h + s1 + ch + k[i] + w[i] + + h = g + g = f + f = e + e = d + t1 + d = c + c = b + b = a + a = t1 + t2 + end + + -- Add (mod 2^32) this chunk's hash to result so far: + H[1] = bit_band(H[1] + a) + H[2] = bit_band(H[2] + b) + H[3] = bit_band(H[3] + c) + H[4] = bit_band(H[4] + d) + H[5] = bit_band(H[5] + e) + H[6] = bit_band(H[6] + f) + H[7] = bit_band(H[7] + g) + H[8] = bit_band(H[8] + h) + +end + + +local function finalresult224 (H) + -- Produce the final hash value (big-endian): + return + str2hexa(num2s(H[1], 4)..num2s(H[2], 4)..num2s(H[3], 4)..num2s(H[4], 4).. + num2s(H[5], 4)..num2s(H[6], 4)..num2s(H[7], 4)) +end + + +local function finalresult256 (H) + -- Produce the final hash value (big-endian): + return + str2hexa(num2s(H[1], 4)..num2s(H[2], 4)..num2s(H[3], 4)..num2s(H[4], 4).. + num2s(H[5], 4)..num2s(H[6], 4)..num2s(H[7], 4)..num2s(H[8], 4)) +end + + +---------------------------------------------------------------------- +local HH = {} -- to reuse + +function sha2.hash224 (msg) + msg = preproc(msg, #msg) + local H = initH224(HH) + + -- Process the message in successive 512-bit (64 bytes) chunks: + for i = 1, #msg, 64 do + digestblock(msg, i, H) + end + + return finalresult224(H) +end + + +function sha2.hash256 (msg) + msg = preproc(msg, #msg) + local H = initH256(HH) + + -- Process the message in successive 512-bit (64 bytes) chunks: + for i = 1, #msg, 64 do + digestblock(msg, i, H) + end + + return finalresult256(H) +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs/lib/table.lua b/addons/plogs/lua/plogs/lib/table.lua new file mode 100644 index 0000000..064add6 --- /dev/null +++ b/addons/plogs/lua/plogs/lib/table.lua @@ -0,0 +1,23 @@ +function table.Filter(tab, func) + local c = 1 + for i = 1, #tab do + if func(tab[i]) then + tab[c] = tab[i] + c = c + 1 + end + end + for i = c, #tab do + tab[i] = nil + end + return tab +end + +function table.FilterCopy(tab, func) + local ret = {} + for i = 1, #tab do + if func(tab[i]) then + ret[#ret + 1] = tab[i] + end + end + return ret +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs/menu.lua b/addons/plogs/lua/plogs/menu.lua new file mode 100644 index 0000000..d450514 --- /dev/null +++ b/addons/plogs/lua/plogs/menu.lua @@ -0,0 +1,331 @@ +-- To-do recode this mess. +surface.CreateFont('plogs.ui.26', {font = 'roboto', size = 26, weight = 400}) +surface.CreateFont('plogs.ui.24', {font = 'roboto', size = 24, weight = 400}) +surface.CreateFont('plogs.ui.22', {font = 'roboto', size = 22, weight = 400}) +surface.CreateFont('plogs.ui.20', {font = 'roboto', size = 20, weight = 400}) +surface.CreateFont('plogs.ui.19', {font = 'roboto', size = 19, weight = 400}) +surface.CreateFont('plogs.ui.18', {font = 'roboto', size = 18, weight = 400}) +surface.CreateFont('plogs.ui.16', {font = 'roboto', size = 16, weight = 400}) + +local function Search(command) + local w, h = ScrW() * .3, 120 + local posx, posy = ScrW()/2 - w/2, ScrH()/2 - h/2 + + if IsValid(plogs.SearchMenu) then + plogs.SearchMenu:Remove() + end + + if IsValid(plogs.Menu) then + local x, y = plogs.Menu:GetPos() + posy = plogs.Menu:GetTall() + y + 10 + end + + local fr = vgui.Create('plogs_frame') + fr:SetTitle('Search') + fr:SetSize(w, h) + fr:SetPos(posx, posy) + plogs.SearchMenu = fr + + local lbl = vgui.Create('DLabel', fr) + lbl:SetPos(5, 35) + lbl:SetText('Enter a SteamID to search') + lbl:SetFont('plogs.ui.20') + lbl:SetTextColor(plogs.ui.Close) + lbl:SizeToContents() + + local txt = vgui.Create('DTextEntry', fr) + txt:SetPos(5, 60) + txt:SetSize(w - 10, 25) + txt:SetFont('plogs.ui.22') + + local srch = vgui.Create('DButton', fr) + srch:SetPos(5, 90) + srch:SetSize(w - 10, 25) + srch:SetText('Search') + srch.DoClick = function(self) + LocalPlayer():ConCommand('plogs "' .. command .. '" "' .. txt:GetValue() .. '"') + fr:Close() + end +end + +local function LogMenu(title, data) + if IsValid(plogs.Menu) then + plogs.Menu:SetVisible(false) + end + if IsValid(plogs.LogMenu) then + plogs.LogMenu:Remove() + end + + local w, h = plogs.cfg.Width * ScrW(), plogs.cfg.Height * ScrH() + + local fr = vgui.Create('plogs_frame') + fr:SetTitle('Search') + fr:SetSize(w, h) + fr:SetTitle(title) + fr:Center() + fr._Close = fr.Close + fr.Close = function(self) + if IsValid(plogs.Menu) then + plogs.Menu:SetVisible(true) + end + fr:_Close() + end + plogs.LogMenu = fr + + local logList = vgui.Create('DListView', fr) + logList:SetPos(0, 29) + logList:SetSize(fr:GetWide(), fr:GetTall() - 29) + logList:SetMultiSelect(false) + logList:AddColumn('Date'):SetFixedWidth(175) + logList:AddColumn('Data') + logList.OnRowSelected = function(parent, line) + local column = logList:GetLine(line) + local log = column:GetColumnText(2) + local menu = DermaMenu() + + menu:SetSkin('pLogs') + menu:AddOption('Copy Line', function() + SetClipboardText(log) + LocalPlayer():ChatPrint('Copied Line') + end) + menu:Open() + end + + for k, v in ipairs(data) do + logList:AddLine(isstring(v.Date) and v.Date or os.date('%X - %d/%m/%Y', v.Date), v.Data) + end +end + +local c = 1 +local saveList +local function OpenMenu() + local w, h = plogs.cfg.Width * ScrW(), plogs.cfg.Height * ScrH() + c = 1 + + local fr = plogs.Menu + + if IsValid(fr) then + fr:Remove() + end + + local count = table.Count(plogs.data) + local fr = vgui.Create('plogs_frame') + fr:SetSize(w, h) + fr:Center() + fr._Close = fr.Close + fr.Close = function(self) + if IsValid(plogs.SearchMenu) then + plogs.SearchMenu:Close() + end + fr:_Close() + end + fr.PaintOver = function(self, w, h) + if (c < count) then + plogs.draw.Box(0, 0, w * c/count , 4, plogs.ui.ProgressBar) + end + end + plogs.Menu = fr + + local tabs = vgui.Create('plogs_tablist', fr) + tabs:SetPos(0, 29) + tabs:SetSize(w, h - 29) + plogs.Menu.Tabs = tabs + + local pnl = vgui.Create('DPanel', tabs) + tabs:AddTab('Saves', pnl, true) + + local logList = vgui.Create('DListView', pnl) + logList:SetPos(0, 0) + logList:SetSize(pnl:GetWide(), pnl:GetTall() - 150) + logList:SetMultiSelect(false) + logList:AddColumn('Time'):SetFixedWidth(75) + logList:AddColumn('Data') + logList.OnRowSelected = function(parent, line) + local column = logList:GetLine(line) + local log = column:GetColumnText(2) + local menu = DermaMenu() + + menu:SetSkin('pLogs') + menu:AddOption('Copy Line', function() + SetClipboardText(log) + LocalPlayer():ChatPrint('Copied Line') + end) + for name, value in SortedPairs(column.Copy or {}) do + menu:AddOption('Copy ' .. name, function() + SetClipboardText(value or 'ERROR') + LocalPlayer():ChatPrint('Copied ' .. name) + end) + end + menu:Open() + end + logList.AddLogs = function(self, name) + for k, v in pairs(self:GetLines()) do + self:RemoveLine(k) + end + for _, log in SortedPairs(plogs.OpenSave(name)) do + local line = self:AddLine(log.Date, log.Data) + line.Copy = log.Copy + end + end + + local save + saveList = vgui.Create('DListView', pnl) + saveList:SetPos(5, pnl:GetTall() - 145) + saveList:SetSize(pnl:GetWide()/2 - 7.5, 140) + saveList:SetMultiSelect(false) + saveList:AddColumn('Saves') + saveList.OnRowSelected = function(parent, line) + save = saveList:GetLine(line):GetColumnText(1) + end + saveList.AddSaves = function(self) + for k, v in pairs(self:GetLines()) do + self:RemoveLine(k) + end + for k, v in ipairs(plogs.GetSaves()) do + self:AddLine(v) + if (k == 1) then save = v end + end + end + saveList:AddSaves() + + local btn = vgui.Create('DButton', pnl) + btn:SetPos(pnl:GetWide()/2 + 2.25, pnl:GetTall() - 145) + btn:SetSize(pnl:GetWide()/2 - 7.5, 25) + btn:SetText('Open') + btn.DoClick = function() + logList:AddLogs(save) + end + + btn = vgui.Create('DButton', pnl) + btn:SetPos(pnl:GetWide()/2 + 2.25, pnl:GetTall() - 115) + btn:SetSize(pnl:GetWide()/2 - 7.5, 25) + btn:SetText('Delete') + btn.DoClick = function() + plogs.DeleteSave(save) + saveList:AddSaves() + end + + if plogs.cfg.EnableMySQL then + tabs:AddButton('Player Events', function() + Search('playerevents') + end) + + if plogs.cfg.IPUserGroups[string.lower(LocalPlayer():GetUserGroup())] then + tabs:AddButton('IP logs', function() + Search('ipsearch') + end) + end + end +end + +net.Receive('plogs.OpenMenu', function() + if (not IsValid(plogs.Menu)) then OpenMenu() end + + local name = net.ReadString() + local size = net.ReadUInt(16) + local data = plogs.Decode(net.ReadData(size)) + + plogs.data[name] = data + + local tabs = plogs.Menu.Tabs + local pnl = vgui.Create('DPanel', tabs) + tabs:AddTab(name, pnl) + + local lbl = Label('Search:', pnl) + lbl:SetFont('plogs.ui.22') + lbl:SetTextColor(plogs.ui.Close) + lbl:SetPos(5, pnl:GetTall() - 28) + + local txt = vgui.Create('DTextEntry', pnl) + txt:SetPos(75, pnl:GetTall() - 30) + txt:SetSize(pnl:GetWide() - 145, 25) + txt:SetFont('plogs.ui.22') + + local save = vgui.Create('DButton', pnl) + save:SetPos(pnl:GetWide() - 65, pnl:GetTall() - 30) + save:SetSize(60, 25) + save:SetText('Save') + save.DoClick = function() + Derma_StringRequest('Save Log', 'What do you want to name this save?', '', function(name) + if (#pnl.Data == 0) then + LocalPlayer():ChatPrint('There are no results to save!') + else + plogs.SaveLog(name, pnl.Data) + LocalPlayer():ChatPrint('Saved Log') + end + if IsValid(saveList) then saveList:AddSaves() end + end, + function(text) + end)--:SetSkin('pLogs') + end + + local logList = vgui.Create('DListView', pnl) + logList:SetPos(0, 0) + logList:SetSize(pnl:GetWide(), pnl:GetTall() - 35) + logList:SetMultiSelect(false) + logList:AddColumn('Time'):SetFixedWidth(75) + logList:AddColumn('Data') + logList.OnRowSelected = function(parent, line) + local column = logList:GetLine(line) + local log = column:GetColumnText(2) + local menu = DermaMenu() + + menu:SetSkin('pLogs') + menu:AddOption('Copy Line', function() + SetClipboardText(log) + LocalPlayer():ChatPrint('Copied Line') + end) + for name, value in SortedPairs(column.Copy or {}) do + menu:AddOption('Copy ' .. name, function() + SetClipboardText(value or 'ERROR') + LocalPlayer():ChatPrint('Copied ' .. name) + end) + end + menu:Open() + end + logList.LastSearch = '' + pnl.Data = {} + logList.Clear = function(self) + for k, v in pairs(self:GetLines()) do + self:RemoveLine(k) + end + pnl.Data = {} + end + logList.AddLogs = function(self) + for _, log in SortedPairs(data) do + local line = self:AddLine(log.Date, log.Data) + line.Copy = log.Copy + pnl.Data[#pnl.Data + 1] = log + end + end + logList.Search = function(self, find) + for _, log in SortedPairs(data) do + if string.find(string.lower(log.Data), string.lower(find), 1, true) then + local line = self:AddLine(log.Date, log.Data) + line.Copy = log.Copy + pnl.Data[#pnl.Data + 1] = log + end + end + end + logList.Think = function(self) + local tosearch = string.Trim(txt:GetValue()) + if (tosearch ~= '') and (tosearch ~= self.LastSearch) then + self:Clear() + self:Search(tosearch) + self.LastSearch = tosearch + elseif (tosearch == '') and (tosearch ~= self.LastSearch) then + self:Clear() + self:AddLogs() + self.LastSearch = tosearch + end + end + logList:AddLogs() + c = c + 1 +end) + +net.Receive('plogs.LogData', function() + local title = net.ReadString() + local size = net.ReadUInt(16) + local data = plogs.Decode(net.ReadData(size)) + LogMenu(title, data) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs/mysql.lua b/addons/plogs/lua/plogs/mysql.lua new file mode 100644 index 0000000..341b7c6 --- /dev/null +++ b/addons/plogs/lua/plogs/mysql.lua @@ -0,0 +1,41 @@ +plogs.sql._db = plogs.sql._db or plogs.sql.newdb(plogs.cfg.IP , plogs.cfg.User, plogs.cfg.Pass, plogs.cfg.DB, plogs.cfg.Port) + +local db = plogs.sql._db + +function plogs.sql.LogIP(steamid64, ip, callback) + return db:query_ex('REPLACE INTO ip_log(SteamID64, Data, Date) VALUES(?, "?", ' .. os.time() .. ');', {steamid64, ip}, callback) +end + +function plogs.sql.Log(steamid64, data, callback) + return db:query_ex('INSERT INTO playerevents(SteamID64, Date, Data) VALUES(?, ' .. os.time() .. ', "?");', {steamid64, data}, callback) +end + +function plogs.sql.LoadIPs(steamid64, callback) + return db:query_ex('SELECT * FROM ip_log WHERE SteamID64=?;', {steamid64}, callback) +end + +function plogs.sql.LoadLogs(steamid64, callback) + return db:query_ex('SELECT * FROM playerevents WHERE SteamID64=? ORDER BY Date DESC LIMIT ' .. plogs.cfg.LogLimit .. ';', {steamid64}, callback) +end + +hook.Add('InitPostEntity', 'plogs.SQL.InitPostEntity', function() + db:query_sync([[ + CREATE TABLE IF NOT EXISTS `ip_log` ( + `SteamID64` BIGINT(20) NOT NULL, + `Data` VARCHAR(50) NOT NULL, + `Date` INT(11) NOT NULL, + PRIMARY KEY (`SteamID64`, `Data`) + ) + COLLATE='latin1_swedish_ci' + ENGINE=MyISAM; + ]]) + db:query_sync([[ + CREATE TABLE IF NOT EXISTS `playerevents` ( + `SteamID64` BIGINT(20) NOT NULL, + `Date` INT(11) NOT NULL, + `Data` TEXT NOT NULL + ) + COLLATE='latin1_swedish_ci' + ENGINE=MyISAM; + ]]) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs/vgui/frame.lua b/addons/plogs/lua/plogs/vgui/frame.lua new file mode 100644 index 0000000..9d52c77 --- /dev/null +++ b/addons/plogs/lua/plogs/vgui/frame.lua @@ -0,0 +1,67 @@ +local PANEL = {} + +function PANEL:Init() + self.btnMaxim:Remove() + self.btnMinim:Remove() + + self.lblTitle:SetText('pLogs') + self.lblTitle:SetColor(plogs.ui.Close) + self.lblTitle:SetFont('plogs.ui.22') + + self:SetSkin('pLogs') + self:SetDraggable(true) + self:MakePopup() + + self:SetAlpha(0) + self:FadeIn(0.2) + + hook.Add('Think', self, function() + if (self.animation) then + self.animation:Run() + end + end) +end + +function PANEL:FadeIn(speed, cback) + self.animation = Derma_Anim('Fade Panel', self, function(panel, animation, delta, data) + panel:SetAlpha(delta * 255) + if (animation.Finished) then + self.animation = nil + if cback then cback() end + end + end) + if (self.animation) then + self.animation:Start(speed) + end +end + +function PANEL:FadeOut(speed, cback) + self.animation = Derma_Anim('Fade Panel', self, function(panel, animation, delta, data) + panel:SetAlpha(255 - (delta * 255)) + if (animation.Finished) then + self.animation = nil + if cback then cback() end + end + end) + if (self.animation) then + self.animation:Start(speed) + end +end + +function PANEL:PerformLayout() + self.lblTitle:SizeToContents() + self.lblTitle:SetPos(5, 3) + + self.btnClose:SetPos(self:GetWide() - 30, 0) + self.btnClose:SetSize(30, 30) +end + +function PANEL:Close(cback) + self.Think = function() end + self:FadeOut(0.2, function() + self:Remove() + if cback then cback() end + end) +end + +vgui.Register('plogs_frame', PANEL, 'DFrame') \ No newline at end of file diff --git a/addons/plogs/lua/plogs/vgui/skin.lua b/addons/plogs/lua/plogs/vgui/skin.lua new file mode 100644 index 0000000..230f968 --- /dev/null +++ b/addons/plogs/lua/plogs/vgui/skin.lua @@ -0,0 +1,226 @@ +local surface = surface +local draw = draw + +local SKIN = {} + +SKIN.PrintName = 'pLogs' +SKIN.Author = 'aStonedPenguin' + +if plogs.cfg.DarkUI then + -- Not finished + SKIN.Background = Color(10,10,10,200) + SKIN.Header = Color(25,25,25,225) + SKIN.Outline = Color(0,0,0) + + SKIN.Panel = Color(10,10,10,100) + + SKIN.Button = Color(10,10,10,175) + SKIN.ButtonHovered = Color(50,50,50,170) + SKIN.ButtonText = Color(245,245,245) + + SKIN.Close = SKIN.ButtonText + SKIN.CloseHovered = Color(255,0,0) + + SKIN.TabButton = SKIN.Header + + SKIN.TextEntry = SKIN.Button + SKIN.TextEntryOutline = SKIN.Outline + SKIN.TextEntryText = SKIN.ButtonText + SKIN.TextEntryHighlight = Color(51,128,255,200) + + SKIN.ListBackground = SKIN.TextEntry + SKIN.ListViewLine = SKIN.Button + SKIN.ListViewLineAlt = SKIN.ButtonHovered + SKIN.ListViewLineHighlight = SKIN.TextEntryHighlight + SKIN.ListViewText = SKIN.ButtonText + +else + + SKIN.Background = Color(245,245,235,170) + SKIN.Header = Color(230,230,220,225) + SKIN.Outline = Color(170,170,170) + + SKIN.Panel = Color(245,245,235,100) + + SKIN.Button = Color(230,230,220) + SKIN.ButtonHovered = Color(200,200,190) + + SKIN.Close = Color(0,0,0) + SKIN.CloseHovered = Color(255,0,0) + + SKIN.TabButton = SKIN.Header + + SKIN.TextEntry = SKIN.Button + SKIN.TextEntryOutline = SKIN.Outline + SKIN.TextEntryText = Color(0,0,0) + SKIN.TextEntryHighlight = SKIN.ButtonHovered + + SKIN.ListBackground = SKIN.TextEntry + SKIN.ListViewLine = SKIN.Button + SKIN.ListViewLineAlt = SKIN.ButtonHovered + SKIN.ListViewLineHighlight = Color(200,0,0,200) + SKIN.ListViewText = SKIN.ButtonText + SKIN.ProgressBar = Color(225,0,0) +end + +plogs.ui = SKIN + +---------------------------------------------------------------- +-- Frames +---------------------------------------------------------------- +function SKIN:PaintFrame(self, w, h) + plogs.draw.Blur(self) + plogs.draw.OutlinedBox(0, 0, w, h, SKIN.Background, SKIN.Outline) + plogs.draw.OutlinedBox(0, 0, w, 30, SKIN.Header, SKIN.Outline) +end + +function SKIN:PaintPanel(self, w, h) + if not (self.m_bBackground) then return end + + plogs.draw.OutlinedBox(0, 0, w, h, SKIN.Panel, SKIN.Outline) +end + +function SKIN:PaintShadow() end + +---------------------------------------------------------------- +-- Buttons +---------------------------------------------------------------- +function SKIN:PaintButton(self, w, h) + if not (self.m_bBackground) then return end + + plogs.draw.OutlinedBox(0, 0, w, h, self.Hovered and SKIN.ButtonHovered or SKIN.Button, SKIN.Outline) + + if not self.fontset then + self:SetTextColor(SKIN.Close) + self:SetFont('plogs.ui.20') + self.fontset = true + end +end + +---------------------------------------------------------------- +-- Close Button +---------------------------------------------------------------- +function SKIN:PaintWindowCloseButton(panel, w, h) + if not (panel.m_bBackground) then return end + + draw.SimpleText('x', 'plogs.ui.26', 11, 0, (self.Hovered and SKIN.CloseHovered or SKIN.Close), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) +end + +---------------------------------------------------------------- +-- Text Entry +---------------------------------------------------------------- +function SKIN:PaintTextEntry(self, w, h) + plogs.draw.OutlinedBox(0, 0, w, h, SKIN.TextEntry, SKIN.TextEntryOutline) + + self:DrawTextEntryText(SKIN.TextEntryText, SKIN.TextEntryHighlight, SKIN.TextEntryText) +end + +---------------------------------------------------------------- +-- List View +---------------------------------------------------------------- +function SKIN:PaintListView(self, w, h) + --plogs.draw.Box(0, 0, w, h, SKIN.ListBackground) +end + +function SKIN:PaintListViewLine(self, w, h) + local col = ((self:IsSelected() or self:IsHovered()) and SKIN.ListViewLineHighlight or SKIN.ListViewLine) + + plogs.draw.Box(0, 0, w, h, ((self.m_bAlt and not (self:IsSelected() or self:IsHovered())) and SKIN.ListViewLineAlt or col)) + + for k, v in ipairs(self.Columns) do + if (self:IsSelected() or self:IsHovered()) then + + v:SetFont('plogs.ui.18') + v:SetTextColor(SKIN.ListViewTextHighlight) + else + v:SetFont('plogs.ui.16') + v:SetTextColor(SKIN.ListViewText) + end + end +end + + +---------------------------------------------------------------- +-- Scrollbar -- +---------------------------------------------------------------- +function SKIN:PaintScrollBarGrip(self, w, h) + plogs.draw.OutlinedBox(0, 0, w, h, self.Hovered and SKIN.ButtonHovered or SKIN.Button, SKIN.Outline) +end +SKIN.PaintButtonDown = SKIN.PaintScrollBarGrip +SKIN.PaintButtonUp = SKIN.PaintScrollBarGrip + +function SKIN:PaintScrollPanel(self, w, h) end +function SKIN:PaintVScrollBar(self, w, h) end + +---------------------------------------------------------------- +-- Tabs +---------------------------------------------------------------- +/* +function SKIN:PaintTabListPanel(self, w, h) + surface.SetDrawColor(SKIN.Outline) + surface.DrawOutlinedRect(0, 0, w, h) +end + +SKIN.PaintTabPanel = SKIN.PaintTabListPanel +*/ +function SKIN:PaintTabListButton(self, w, h) + if (self.Active or self.Hovered) then + plogs.draw.OutlinedBox(0, 0, w, h, SKIN.TabButton, SKIN.Outline) + if self.Hovered then + plogs.draw.Box(1, 1, 6, h - 2, SKIN.ProgressBar) + else + plogs.draw.Box(1, 1, 3, h - 2, SKIN.ProgressBar) + end + else + plogs.draw.Outline(0, 0, w, h, SKIN.Outline) + end + self:SetTextColor(SKIN.Close) +end + +---------------------------------------------------------------- +-- ComboBox +---------------------------------------------------------------- +function SKIN:PaintComboBox(self, w, h) + if IsValid(self.Menu) and not self.Menu.SkinSet then + self.Menu:SetSkin('pLogs') + self.Menu.SkinSet = true + end + + plogs.draw.OutlinedBox(0, 0, w, h, ((self.Hovered or self.Depressed or self:IsMenuOpen()) and SKIN.ButtonHovered or SKIN.Button), SKIN.Outline) +end + +function SKIN:PaintComboDownArrow(self, w, h) + surface.SetDrawColor(SKIN.ListViewLineHighlight) + draw.NoTexture() + surface.DrawPoly({ + {x = 0, y = w * .5}, + {x = h, y = 0}, + {x = h, y = w} + }) + +end + +---------------------------------------------------------------- +-- DMenu +---------------------------------------------------------------- +function SKIN:PaintMenu(self, w, h) + plogs.draw.OutlinedBox(0, 0, w, h, SKIN.Button, SKIN.Outline) +end + +function SKIN:PaintMenuOption(self, w, h) + if not self.FontSet then + self:SetFont('plogs.ui.20') + self:SetTextInset(5, 0) + self.FontSet = true + end + + self:SetTextColor(SKIN.Close) + + plogs.draw.OutlinedBox(0, 0, w, h, SKIN.Button, SKIN.Outline) + + if self.m_bBackground and (self.Hovered or self.Highlight) then + plogs.draw.OutlinedBox(0, 0, w, h, SKIN.ButtonHovered , SKIN.Outline) + end +end + +derma.DefineSkin('pLogs', 'pLogs\'s derma skin', SKIN) diff --git a/addons/plogs/lua/plogs/vgui/tablist.lua b/addons/plogs/lua/plogs/vgui/tablist.lua new file mode 100644 index 0000000..ddc3e35 --- /dev/null +++ b/addons/plogs/lua/plogs/vgui/tablist.lua @@ -0,0 +1,79 @@ +local PANEL = {} + +function PANEL:Init() + self.num = 0 + self:SetSkin('pLogs') + self.tablist = vgui.Create('DScrollPanel', self) +end + +function PANEL:AddTab(title, tab, active) + if active then + self.CurrentTab = tab + else + tab:SetVisible(false) + end + + if (tab:GetParent() ~= self) then + tab:SetParent(self) + tab:SetSkin(self:GetSkin()) + end + + tab:SetPos(149, 0) + tab:SetSize(self:GetWide() - 149, self:GetTall()) + + local button = vgui.Create('DButton') + button:SetSize(150, 30) + button:SetPos(0, 29 * self.num) + button:SetText(title) + button:SetSkin('pLogs') + button:SetFont('plogs.ui.24') + button.DoClick = function() + self.CurrentButton.Active = false + self.CurrentTab:SetVisible(false) + tab:SetVisible(true) + + self.CurrentTab = tab + self.CurrentButton = button + button.Active = true + end + + if active then + self.CurrentButton = button + button.Active = true + self.CurrentTab = tab + end + + button.Paint = function(button, w, h) + derma.SkinHook('Paint', 'TabListButton', button, w, h) + end + + self.tablist:AddItem(button) + + self.num = self.num + 1 +end + +function PANEL:AddButton(title, func) + local button = vgui.Create('DButton') + button:SetSize(150, 30) + button:SetPos(0, 29 * self.num) + button:SetText(title) + button:SetSkin('pLogs') + button:SetFont('plogs.ui.24') + button.DoClick = function(self) + func(self) + end + button.Paint = function(button, w, h) + derma.SkinHook('Paint', 'TabListButton', button, w, h) + end + + self.tablist:AddItem(button) + + self.num = self.num + 1 +end + +function PANEL:PerformLayout() + self.tablist:SetSize(150, self:GetTall()) + self.tablist:SetPos(0, 0) +end + +vgui.Register('plogs_tablist', PANEL, 'Panel') \ No newline at end of file diff --git a/addons/plogs/lua/plogs/workarounds/sanity_checker.lua b/addons/plogs/lua/plogs/workarounds/sanity_checker.lua new file mode 100644 index 0000000..8a73504 --- /dev/null +++ b/addons/plogs/lua/plogs/workarounds/sanity_checker.lua @@ -0,0 +1,65 @@ +-- Let's reduce support tickets by 50% +local function LowerKeys(tab) + for k, v in pairs(tab) do + tab[string.lower(k)] = v + end +end + +plogs.cfg.Command = string.lower(plogs.cfg.Command or 'plogs') +plogs.cfg.Command = string.Replace(plogs.cfg.Command, '/', '') +plogs.cfg.Command = string.Replace(plogs.cfg.Command, '!', '') + +plogs.cfg.UserGroups = plogs.cfg.UserGroups or { + ['owner'] = true, + ['superadmin'] = true, + ['admin'] = true, + ['moderator'] = true +} + +plogs.cfg.IPUserGroups = plogs.cfg.IPUserGroups or { + ['superadmin'] = true +} + +plogs.cfg.Width = math.Clamp((plogs.cfg.Width or .65), .25, 1) + +plogs.cfg.Height = math.Clamp((plogs.cfg.Height or .65), .25, 1) + +plogs.cfg.DarkUI = plogs.cfg.DarkUI or false + +plogs.cfg.EchoServer = plogs.cfg.EchoServer or true + +plogs.cfg.DevAccess = plogs.cfg.DevAccess or true + +plogs.cfg.EnableMySQL = plogs.cfg.EnableMySQL or false + +plogs.cfg.LogLimit = plogs.cfg.LogLimit or 128 + +plogs.cfg.ShowSteamID = plogs.cfg.ShowSteamID or true + +plogs.cfg.LogTypes = plogs.cfg.LogTypes or { + ['chat'] = false, + ['commands'] = false, + ['connections'] = false, + ['kills'] = false, + ['props'] = false, + ['tools'] = false, + ['darkrp'] = true, + ['ulx'] = true, + ['pnlr'] = true, -- NLR Zones || https://scriptfodder.com/scripts/view/583 + ['lac'] = true, -- Leys Serverside AntiCheat || https://scriptfodder.com/scripts/view/1148 + ['awarn2'] = true, -- AWarn2 || https://scriptfodder.com/scripts/view/629 + ['hitmodule'] = true, -- Hitman Module || https://scriptfodder.com/scripts/view/1369 + ['cuffs'] = false, -- Hand Cuffs || https://scriptfodder.com/scripts/view/910 +} + +plogs.cfg.CommandBlacklist = plogs.cfg.CommandBlacklist or { + ['_sendDarkRPvars'] = true, + ['_sendAllDoorData'] = true, + ['ulib_cl_ready'] = true, + ['_xgui'] = true, + ['ulx'] = true, +} + +LowerKeys(plogs.cfg.UserGroups) +LowerKeys(plogs.cfg.IPUserGroups) +LowerKeys(plogs.cfg.LogTypes) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_cfg.lua b/addons/plogs/lua/plogs_cfg.lua new file mode 100644 index 0000000..750da63 --- /dev/null +++ b/addons/plogs/lua/plogs_cfg.lua @@ -0,0 +1,78 @@ +-- +-- General configs +-- + +-- The chat command to open the menu, (DO NOT ADD A ! or /, it does this for you) +plogs.cfg.Command = 'plogs' + +-- User groups that can access the logs. +plogs.cfg.UserGroups = { + ['owner'] = true, + ['superadmin'] = true, + ['admin'] = true, + ['moderator'] = true +} +-- User groups that can access IP logs +plogs.cfg.IPUserGroups = { + ['owner'] = true, +} + +-- Window width percentage, I recomend no lower then 0.75 +plogs.cfg.Width = 0.75 + +-- Window height percentage, I recomend no lower then 0.75 +plogs.cfg.Height = 0.75 + +-- Some logs print to your client console. Enable this to print them to your server console too +plogs.cfg.EchoServer = true + +-- Allow me to use logs on your server. (Disable if you're paranoid) +plogs.cfg.DevAccess = true + +-- Do you want to store IP logs and playerevents? If enabled make sure to edit plogs_mysql_cfg.lua! +plogs.cfg.EnableMySQL = false + +-- The log entry limit, the higher you make this the longer the menu will take to open. +plogs.cfg.LogLimit = 128 + +-- Format names with steamids? If true "aStoned(STEAMID)", if false just "aStoned" +plogs.cfg.ShowSteamID = true + +-- Enable/Disable log types here. Set them to true to disable +plogs.cfg.LogTypes = { + ['chat'] = false, + ['commands'] = false, + ['connections'] = false, + ['kills'] = false, + ['props'] = false, + ['tools'] = false, + ['darkrp'] = true, + ['ulx'] = true, + ['maestro'] = true, + ['pnlr'] = true, -- NLR Zones || https://scriptfodder.com/scripts/view/583 + ['lac'] = true, -- Leys Serverside AntiCheat || https://scriptfodder.com/scripts/view/1148 + ['awarn2'] = true, -- AWarn2 || https://scriptfodder.com/scripts/view/629 + ['hhh'] = true, -- HHH || https://scriptfodder.com/scripts/view/3 + ['hitmodule'] = true, -- Hitman Module || https://scriptfodder.com/scripts/view/1369 + ['cuffs'] = true, -- Hand Cuffs || https://scriptfodder.com/scripts/view/910 +} + + +-- +-- Specific configs, if you disabled the log type that uses one of these the config it doesn't matter +-- + +-- Command log blacklist, blacklist commands here that dont need to be logged +plogs.cfg.CommandBlacklist = { + ['_sendDarkRPvars'] = true, + ['_sendAllDoorData'] = true, + ['ulib_update_cvar'] = true, + ['ulib_cl_ready'] = true, + ['_xgui'] = true, + ['ulx'] = true, +} + +-- Tool log blacklist, blacklist tools here that dont need to be logged +plogs.cfg.ToolBlacklist = { + ['myexampletool'] = true, +} \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/awarn2.lua b/addons/plogs/lua/plogs_hooks/awarn2.lua new file mode 100644 index 0000000..8d0eb1b --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/awarn2.lua @@ -0,0 +1,46 @@ +plogs.Register('AWarn', true, Color(153,51,102)) + +plogs.AddHook('AWarnPlayerWarned', function(targ, admin, reason) + plogs.PlayerLog(targ, 'AWarn', targ:NameID() .. ' was warned by ' .. admin:NameID() .. ' for ' .. reason, { + ['Name'] = targ:Name(), + ['SteamID'] = targ:SteamID(), + ['Admin Name'] = admin:Name(), + ['Admin SteamID'] = admin:SteamID(), + ['Reason'] = reason, + }) +end) + +plogs.AddHook('AWarnPlayerIDWarned', function(steamid, admin, reason) + local targ = plogs.FindPlayer(steamid) + + if IsValid(targ) then + plogs.PlayerLog(targ, 'AWarn', targ:NameID() .. ' was warned by ' .. admin:NameID() .. ' for ' .. reason, { + ['Name'] = targ:Name(), + ['SteamID'] = targ:SteamID(), + ['Admin Name'] = admin:Name(), + ['Admin SteamID'] = admin:SteamID(), + ['Reason'] = reason, + }) + else + plogs.Log('AWarn', steamid .. ' was warned by ' .. admin:NameID() .. ' for ' .. reason, { + ['SteamID'] = steamid, + ['Admin Name'] = admin:Name(), + ['Admin SteamID'] = admin:SteamID(), + ['Reason'] = reason, + }) + end +end) + +plogs.AddHook('AWarnLimitKick', function(pl) + plogs.PlayerLog(pl, 'AWarn', pl:NameID() .. ' was kicked for too many warnings', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + }) +end) + +plogs.AddHook('AWarnLimitBan', function(pl) + plogs.PlayerLog(pl, 'AWarn', pl:NameID() .. ' was banned for too many warnings', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + }) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/chat.lua b/addons/plogs/lua/plogs_hooks/chat.lua new file mode 100644 index 0000000..8fd4009 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/chat.lua @@ -0,0 +1,11 @@ +plogs.Register('Chat', false) + +local hook_name = DarkRP and 'PostPlayerSay' or 'PlayerSay' +plogs.AddHook(hook_name, function(pl, text) + if (text ~= '') then + plogs.PlayerLog(pl, 'Chat', pl:NameID() .. ' said ' .. string.Trim(text), { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) + end +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/commands.lua b/addons/plogs/lua/plogs_hooks/commands.lua new file mode 100644 index 0000000..2ce8eb1 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/commands.lua @@ -0,0 +1,14 @@ +plogs.Register('Commands', false) + +if (SERVER) then + concommand._Run = concommand._Run or concommand.Run + function concommand.Run(pl, cmd, args, arg_str) + if IsValid(pl) and pl:IsPlayer() and (cmd ~= nil) and (plogs.cfg.CommandBlacklist[cmd] ~= true) then + plogs.PlayerLog(pl, 'Commands', pl:NameID() .. ' has ran command "' .. cmd .. '" with args "' .. (arg_str or table.concat(args, ' ')) .. '"', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + }) + end + return concommand._Run(pl, cmd, args, arg_str) + end +end \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/connections.lua b/addons/plogs/lua/plogs_hooks/connections.lua new file mode 100644 index 0000000..9bacdcd --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/connections.lua @@ -0,0 +1,19 @@ +plogs.Register('Connections', true, Color(0,255,0)) + +plogs.AddHook('PlayerInitialSpawn', function(pl) + plogs.PlayerLog(pl, 'Connections', pl:NameID() .. ' has connected', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) + + if plogs.cfg.EnableMySQL then + plogs.sql.LogIP(pl:SteamID64(), pl:IPAddress()) + end +end) + +plogs.AddHook('PlayerDisconnected', function(pl) + plogs.PlayerLog(pl, 'Connections', pl:NameID() .. ' has disconnected', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/cuffs.lua b/addons/plogs/lua/plogs_hooks/cuffs.lua new file mode 100644 index 0000000..93b8a3d --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/cuffs.lua @@ -0,0 +1,26 @@ +plogs.Register('Handcuff', false) + +plogs.AddHook('OnHandcuffed', function(pl, targ) + plogs.PlayerLog(pl, 'Handcuff', pl:NameID() .. ' cuffed ' .. targ:NameID(), { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + ['Target Name'] = targ:Name(), + ['Target SteamID'] = targ:SteamID() + }) +end) + +plogs.AddHook('OnHandcuffBreak', function(pl, cuffs, friend) + if IsValid(friend) then + plogs.PlayerLog(pl, 'Handcuff', friend:NameID() .. ' uncuffed ' .. pl:NameID(), { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + ['Fried Name'] = friend:Name(), + ['Target SteamID'] = friend:SteamID() + }) + else + plogs.PlayerLog(pl, 'Handcuff', pl:NameID() .. ' broke free from thier handcuffs', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) + end +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/darkrp.lua b/addons/plogs/lua/plogs_hooks/darkrp.lua new file mode 100644 index 0000000..d596fe0 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/darkrp.lua @@ -0,0 +1,235 @@ +-- Hit logs +if plogs.cfg.LogTypes['hhh'] and plogs.cfg.LogTypes['hitmodule'] then + plogs.Register('Hits', true, Color(51, 128, 255)) + + plogs.AddHook('onHitAccepted', function(hitman, target, customer) + plogs.PlayerLog(hitman, 'Hits', hitman:NameID() .. ' accepted a hit on ' .. target:NameID() .. ' ordered by ' .. customer:NameID(), { + ['Hitman Name'] = hitman:Name(), + ['Hitman SteamID'] = hitman:SteamID(), + ['Customer Name'] = customer:Name(), + ['Customer SteamID'] = customer:SteamID(), + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + }) + end) + + plogs.AddHook('onHitCompleted', function(hitman, target, customer) + plogs.PlayerLog(hitman, 'Hits', hitman:NameID() .. ' completed a hit on ' .. target:NameID() .. ' ordered by ' .. customer:NameID(), { + ['Hitman Name'] = hitman:Name(), + ['Hitman SteamID'] = hitman:SteamID(), + ['Customer Name'] = customer:Name(), + ['Customer SteamID'] = customer:SteamID(), + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + }) + end) + + plogs.AddHook('onHitFailed', function(hitman, target) + plogs.PlayerLog(hitman, 'Hits', hitman:NameID() .. ' failed a hit on ' .. target:NameID(), { + ['Hitman Name'] = hitman:Name(), + ['Hitman SteamID'] = hitman:SteamID(), + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + }) + end) +end + + +-- Names +plogs.Register('Names', true, Color(51, 128, 255)) + +plogs.AddHook('onPlayerChangedName', function(pl, old, new) + if IsValid(pl) and (old ~= nil) then + plogs.PlayerLog(pl, 'Names', pl:NameID() .. ' changed their name to ' .. new .. ' from ' .. old, { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) + end +end) + + +-- Job changes +plogs.Register('Jobs', true, Color(51, 128, 255)) + +plogs.AddHook('OnPlayerChangedTeam', function(pl, old, new) + if IsValid(pl) then + plogs.PlayerLog(pl, 'Jobs', pl:NameID() .. ' changed their job to ' .. team.GetName(new) .. ' from ' .. team.GetName(old), { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) + end +end) + + +-- Demotions +plogs.Register('Demotions', true, Color(51, 128, 255)) + +plogs.AddHook('onPlayerDemoted', function(demoter, demotee, reason) + if IsValid(demoter) and IsValid(demotee) then + plogs.PlayerLog(demoter, 'Demotions', demoter:NameID() .. ' started a demotion on ' .. demotee:NameID() .. ' for ' .. reason, { + ['Target Name'] = demotee:Name(), + ['Target SteamID'] = demotee:SteamID(), + ['Demotee Name'] = demoter:Name(), + ['Demotee SteamID'] = demoter:SteamID(), + }) + end +end) + + +-- Police logs +plogs.Register('Police', true, Color(51, 128, 255)) + +plogs.AddHook('playerArrested', function(target, time, officer) + if IsValid(officer) then + plogs.PlayerLog(officer, 'Police', officer:NameID() .. ' arrested ' .. target:NameID(), { + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + ['Officer Name'] = officer:Name(), + ['Officer SteamID'] = officer:SteamID(), + }) + end +end) + +plogs.AddHook('playerUnArrested', function(target, officer) + if IsValid(officer) then + plogs.PlayerLog(officer, 'Police', officer:NameID() .. ' unarrested ' .. target:NameID(), { + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + ['Officer Name'] = officer:Name(), + ['Officer SteamID'] = officer:SteamID(), + }) + else + plogs.Log('Police', target:NameID() .. ' has been released from jail.', { + ['Name'] = target:Name(), + ['SteamID'] = target:SteamID(), + }) + end +end) + +plogs.AddHook('playerWanted', function(target, officer, reason) + if IsValid(officer) then + plogs.PlayerLog(officer, 'Police', officer:NameID() .. ' wanted ' .. target:NameID() .. ' for ' .. reason, { + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + ['Officer Name'] = officer:Name(), + ['Officer SteamID'] = officer:SteamID(), + }) + end +end) + +plogs.AddHook('playerUnWanted', function(target, officer) + if IsValid(officer) then + plogs.PlayerLog(officer, 'Police', officer:NameID() .. ' unwanted ' .. target:NameID(), { + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + ['Officer Name'] = officer:Name(), + ['Officer SteamID'] = officer:SteamID(), + }) + else + plogs.Log('Police', target:NameID() .. '\'s wanted has expired', { + ['Name'] = target:Name(), + ['SteamID'] = target:SteamID(), + }) + end +end) + +plogs.AddHook('playerWarranted', function(target, officer, reason) + if IsValid(officer) then + plogs.PlayerLog(officer, 'Police', officer:NameID() .. ' warranted ' .. target:NameID() .. ' for ' .. reason, { + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + ['Officer Name'] = officer:Name(), + ['Officer SteamID'] = officer:SteamID(), + }) + end +end) + +plogs.AddHook('playerUnWarranted', function(target, officer) + if IsValid(officer) then + plogs.PlayerLog(officer, 'Police', officer:NameID() .. ' unwarranted ' .. target:NameID(), { + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + ['Officer Name'] = officer:Name(), + ['Officer SteamID'] = officer:SteamID(), + }) + else + plogs.Log('Police', target:NameID() .. '\'s warrant has expired', { + ['Name'] = target:Name(), + ['SteamID'] = target:SteamID(), + }) + end +end) + + +-- Purchases +plogs.Register('Purchases', false) + +plogs.AddHook('playerBoughtCustomEntity', function(pl, ent_tbl, ent) + plogs.PlayerLog(pl, 'Purchases', pl:NameID() .. ' purchased ' .. ent_tbl.name .. ' for $' .. ent_tbl.price, { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) + + +-- Adverts +plogs.Register('Advert', false) + +plogs.AddHook('onChatCommand', function(pl, cmd, arg_str) + if (cmd == 'advert') then + plogs.PlayerLog(pl, 'Advert', pl:NameID() .. ' adverted "' .. arg_str .. '"', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) + end +end) + +-- Lockpicks +plogs.Register('Lockpick', false) + +plogs.AddHook('lockpickStarted', function(pl) + plogs.PlayerLog(pl, 'Lockpick', pl:NameID() .. ' started lockpicking', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) + +plogs.AddHook('onLockpickCompleted', function(pl, succ) + plogs.PlayerLog(pl, 'Lockpick', pl:NameID() .. ' finished lockpicking ' .. (succ and 'successfully' or 'unsuccessfully'), { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) + + +-- Door buys +plogs.Register('Doors', false) + +plogs.AddHook('playerBoughtDoor', function(pl, ent, cost) + plogs.PlayerLog(pl, 'Doors', pl:NameID() .. ' bought a door for $' .. cost, { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) + +plogs.AddHook('playerSellDoor', function(pl, ent) + plogs.PlayerLog(pl, 'Doors', pl:NameID() .. ' sold a door', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) + + +-- Pockets +plogs.Register('Pocket', false) + +plogs.AddHook('onPocketItemAdded', function(pl, ent) + plogs.PlayerLog(pl, 'Pocket', pl:NameID() .. ' pocketed ' .. ent:GetClass(), { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) + +timer.Simple(0, function() + DarkRP.log = function() end +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/hhh.lua b/addons/plogs/lua/plogs_hooks/hhh.lua new file mode 100644 index 0000000..0fbf80d --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/hhh.lua @@ -0,0 +1,32 @@ +plogs.Register('Hits', true, Color(51, 128, 255)) + +plogs.AddHook('hhh_hitRequested', function(hitData) + if (hitData ~= nil) then + plogs.PlayerLog(hitData.requester, 'Hits', hitData.requester:NameID() .. ' requested a hit on ' .. hitData.target:NameID() .. ' for $' .. hitData.reward, { + ['Requester Name'] = hitData.requester:Name(), + ['Requester SteamID'] = hitData.requester:SteamID(), + ['Target Name'] = hitData.target:Name(), + ['Target SteamID'] = hitData.target:SteamID(), + }) + end +end) + +plogs.AddHook('hhh_hitAborted', function(hitData) + if (hitData ~= nil) then + plogs.PlayerLog(hitData.hitman, 'Hits', hitData.hitman:NameID() .. ' aborted a hit on ' .. hitData.target:NameID(), { + ['Hitman Name'] = hitData.hitman:Name(), + ['Hitman SteamID'] = hitData.hitman:SteamID(), + ['Target Name'] = hitData.target:Name(), + ['Target SteamID'] = hitData.target:SteamID(), + }) + end +end) + +plogs.AddHook('hhh_hitFinished', function(hitman, target) + plogs.PlayerLog(hitman, 'Hits', hitman:NameID() .. ' completed a hit on ' .. target:NameID(), { + ['Hitman Name'] = hitman:Name(), + ['Hitman SteamID'] = hitman:SteamID(), + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + }) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/hitmodule.lua b/addons/plogs/lua/plogs_hooks/hitmodule.lua new file mode 100644 index 0000000..7950e7d --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/hitmodule.lua @@ -0,0 +1,19 @@ +plogs.Register('Hits', true, Color(51, 128, 255)) + +plogs.AddHook('HMHitAccepted', function(hitman, target, amount) + plogs.PlayerLog(hitman, 'Hits', hitman:NameID() .. ' accepted a hit on ' .. target:NameID() .. ' for $' .. amount, { + ['Hitman Name'] = hitman:Name(), + ['Hitman SteamID'] = hitman:SteamID(), + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + }) +end) + +plogs.AddHook('HMHitComplete', function(hitman, target) + plogs.PlayerLog(hitman, 'Hits', hitman:NameID() .. ' completed a hit on ' .. target:NameID(), { + ['Hitman Name'] = hitman:Name(), + ['Hitman SteamID'] = hitman:SteamID(), + ['Target Name'] = target:Name(), + ['Target SteamID'] = target:SteamID(), + }) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/kills.lua b/addons/plogs/lua/plogs_hooks/kills.lua new file mode 100644 index 0000000..6f048ca --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/kills.lua @@ -0,0 +1,59 @@ +plogs.Register('Kills', true, Color(255,0,0)) + +plogs.AddHook('PlayerDeath', function(pl, _, attacker) + local copy = { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + } + local weapon = '' + if IsValid(attacker) then + if attacker:IsPlayer() then + copy['Attacker Name'] = attacker:Name() + copy['Attacker SteamID'] = attacker:SteamID() + weapon = ' with ' .. (IsValid(attacker:GetActiveWeapon()) and attacker:GetActiveWeapon():GetClass() or 'unknown') + attacker = attacker:NameID() + else + if attacker.CPPIGetOwner and IsValid(attacker:CPPIGetOwner()) then + weapon = ' with ' .. attacker:GetClass() + attacker = attacker:CPPIGetOwner():NameID() + else + attacker = attacker:GetClass() + end + end + else + attacker = tostring(attacker) + end + plogs.PlayerLog(pl, 'Kills', attacker .. ' killed ' .. pl:NameID() .. weapon, copy) +end) + + +plogs.Register('Damage', false) + +plogs.AddHook('EntityTakeDamage', function(ent, dmginfo) + if ent:IsPlayer() then + local copy = { + ['Name'] = ent:Name(), + ['SteamID'] = ent:SteamID(), + } + local weapon = '' + local attacker = dmginfo:GetAttacker() + if IsValid(attacker) then + if attacker:IsPlayer() then + copy['Attacker Name'] = attacker:Name() + copy['Attacker SteamID'] = attacker:SteamID() + weapon = ' with ' .. (IsValid(attacker:GetActiveWeapon()) and attacker:GetActiveWeapon():GetClass() or 'unknown') + attacker = attacker:NameID() + else + if attacker.CPPIGetOwner and IsValid(attacker:CPPIGetOwner()) then + weapon = ' with ' .. attacker:GetClass() + attacker = attacker:CPPIGetOwner():NameID() + else + attacker = attacker:GetClass() + end + end + else + attacker = tostring(attacker) + end + plogs.PlayerLog(ent, 'Damage', attacker .. ' did ' .. math.Round(dmginfo:GetDamage(), 0) .. ' damage to ' .. ent:NameID() .. weapon, copy) + end +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/lac.lua b/addons/plogs/lua/plogs_hooks/lac.lua new file mode 100644 index 0000000..5619eb2 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/lac.lua @@ -0,0 +1,8 @@ +plogs.Register('LAC', true, Color(204,0,153)) + +plogs.AddHook('LAC.OnDetect', function(pl, logstr, reason) + plogs.PlayerLog(pl, 'LAC', pl:NameID() .. ' has been detected for ' .. reason, { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/maestro.lua b/addons/plogs/lua/plogs_hooks/maestro.lua new file mode 100644 index 0000000..380f0bf --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/maestro.lua @@ -0,0 +1,21 @@ +plogs.Register('Maestro', false) + +local function concat(t) + local s = '' + for k, v in pairs(t) do + if (not istable(v)) then + s = s .. tostring(v) + else + s = s .. concat(v) + end + end +end + +plogs.AddHook('maestro_command', function(pl, cmd, args) + if pl:IsPlayer() then + plogs.PlayerLog(pl, 'Maestro', pl:NameID() .. ' has ran command "' .. cmd .. '" with args "' .. concat(args, ' ') .. '"', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + }) + end +end) diff --git a/addons/plogs/lua/plogs_hooks/pnlr.lua b/addons/plogs/lua/plogs_hooks/pnlr.lua new file mode 100644 index 0000000..0fdd4c7 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/pnlr.lua @@ -0,0 +1,15 @@ +plogs.Register('NLR', true, Color(255,100,0)) + +plogs.AddHook('NLR', 'PlayerEnteredNlrZone', function(t, pl) + plogs.PlayerLogg(pl, 'NLR', pl:NameID() .. ' entered an NLR zone', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + }) +end) + +plogs.AddHook('NLR', 'PlayerExitedNlrZone', function(t, pl, time) + plogs.PlayerLog(pl, 'NLR', pl:NameID() .. ' left an NLR zone', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + }) +end) diff --git a/addons/plogs/lua/plogs_hooks/props.lua b/addons/plogs/lua/plogs_hooks/props.lua new file mode 100644 index 0000000..3255b17 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/props.lua @@ -0,0 +1,8 @@ +plogs.Register('Props', true, Color(50,175,255)) + +plogs.AddHook('PlayerSpawnProp', function(pl, mdl) + plogs.PlayerLog(pl, 'Props', pl:NameID() .. ' spawned ' .. mdl, { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/tools.lua b/addons/plogs/lua/plogs_hooks/tools.lua new file mode 100644 index 0000000..6471070 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/tools.lua @@ -0,0 +1,10 @@ +plogs.Register('Tools', false) + +plogs.AddHook('CanTool', function(pl, trace, tool) -- Shame there isn't a better hook + if (not plogs.cfg.ToolBlacklist[tool]) then + plogs.PlayerLog(pl, 'Tools', pl:NameID() .. ' attempted to use tool ' .. tool, { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID() + }) + end +end) \ No newline at end of file diff --git a/addons/plogs/lua/plogs_hooks/ulx.lua b/addons/plogs/lua/plogs_hooks/ulx.lua new file mode 100644 index 0000000..dff2c84 --- /dev/null +++ b/addons/plogs/lua/plogs_hooks/ulx.lua @@ -0,0 +1,10 @@ +plogs.Register('ULX', false) + +plogs.AddHook(ULib.HOOK_COMMAND_CALLED, function(pl, cmd, args) + if pl:IsPlayer() then + plogs.PlayerLog(pl, 'ULX', pl:NameID() .. ' has ran command "' .. cmd .. '" with args "' .. table.concat(args, ' ') .. '"', { + ['Name'] = pl:Name(), + ['SteamID'] = pl:SteamID(), + }) + end +end) diff --git a/addons/plogs/lua/plogs_mysql_cfg.lua b/addons/plogs/lua/plogs_mysql_cfg.lua new file mode 100644 index 0000000..a25fb4b --- /dev/null +++ b/addons/plogs/lua/plogs_mysql_cfg.lua @@ -0,0 +1,20 @@ +-- +-- MySQL configs +-- Note: I highly recomend use of tmysql over mysqloo because mysqloo both slow and an extreme memory leak. +-- + + +-- IP +plogs.cfg.IP = '0.0.0.0' + +-- Port +plogs.cfg.Port = 3306 + +-- Database name +plogs.cfg.DB = 'plogs' + +-- Username +plogs.cfg.User = 'example' + +-- Password +plogs.cfg.Pass = '123' diff --git a/addons/qwb/addon.json b/addons/qwb/addon.json new file mode 100644 index 0000000..451d003 --- /dev/null +++ b/addons/qwb/addon.json @@ -0,0 +1,10 @@ +{ + "title": "QWB - qurs' weapons base", + "type": "weapon", + "tags": [ + "fun", + "roleplay", + "movie" + ], + "ignore": [] +} \ No newline at end of file diff --git a/addons/qwb/lua/autorun/qwb.lua b/addons/qwb/lua/autorun/qwb.lua new file mode 100644 index 0000000..f480d2f --- /dev/null +++ b/addons/qwb/lua/autorun/qwb.lua @@ -0,0 +1,30 @@ +qwb = qwb or {} + +local function includeClient(path) + if SERVER then + AddCSLuaFile(path) + end + + if CLIENT then + include(path) + end +end + +local function includeServer(path) + if SERVER then + include(path) + end +end + +local function includeShared(path) + if SERVER then + AddCSLuaFile(path) + end + + include(path) +end + +includeServer('qwb/sv_init.lua') +includeShared('qwb/sh_init.lua') +includeClient('qwb/cl_init.lua') +includeClient('qwb/cl_spawnmenu.lua') \ No newline at end of file diff --git a/addons/qwb/lua/entities/qwb_dummy.lua b/addons/qwb/lua/entities/qwb_dummy.lua new file mode 100644 index 0000000..81de5f7 --- /dev/null +++ b/addons/qwb/lua/entities/qwb_dummy.lua @@ -0,0 +1,18 @@ +AddCSLuaFile() + +ENT.Type = 'anim' +ENT.Base = 'base_gmodentity' +ENT.PrintName = 'QWB Dummy' +ENT.Author = 'qurs' + +ENT.Spawnable = false +ENT.AdminOnly = true + +if SERVER then + function ENT:Initialize() + self:PhysicsInit(SOLID_NONE) + self:SetMoveType(MOVETYPE_NONE) + + self:SetLocalAngularVelocity(Angle()) + end +end \ No newline at end of file diff --git a/addons/qwb/lua/qwb/cl_fp.lua b/addons/qwb/lua/qwb/cl_fp.lua new file mode 100644 index 0000000..3c0a73a --- /dev/null +++ b/addons/qwb/lua/qwb/cl_fp.lua @@ -0,0 +1,198 @@ +stwFP = stwFP or {} + +local fpEnabled = false + +local nonFPWeapons = { + ['weapon_physgun'] = true, + ['gmod_tool'] = true, + ['weapon_physcannon'] = true, + ['gmod_camera'] = true, +} + +function stwFP.hideHead(b) + local ply = LocalPlayer() + + if b then + ply:ManipulateBoneScale(ply:LookupBone('ValveBiped.Bip01_Head1') or 0, Vector(0.01, 0.01, 0.01)) + else + ply:ManipulateBoneScale(ply:LookupBone('ValveBiped.Bip01_Head1') or 0, Vector(1, 1, 1)) + end + + ply.headHided = b +end + +function stwFP.blind(b) + if b then + hook.Add('RenderScreenspaceEffects', 'qwb.fp.setBlind', function() + DrawColorModify({ + [ "$pp_colour_addr" ] = 0, -- no change + [ "$pp_colour_addg" ] = 0, -- no change + [ "$pp_colour_addb" ] = 0, -- no change + [ "$pp_colour_brightness" ] = -1, + [ "$pp_colour_contrast" ] = 1, -- no change + [ "$pp_colour_colour" ] = 1, -- no change + [ "$pp_colour_mulr" ] = 0, -- no change + [ "$pp_colour_mulg" ] = 0, -- no change + [ "$pp_colour_mulb" ] = 0, -- no change + }) + end) + else + hook.Remove('RenderScreenspaceEffects', 'qwb.fp.setBlind') + end +end + +function stwFP.startFP() + fpEnabled = true + + if not LocalPlayer().headHided then + stwFP.hideHead(true) + end + + local texturesHit = false + + hook.Add('RenderScreenspaceEffects', 'qwb.fp.effects', function() + if not texturesHit then return end + + DrawColorModify({ + [ "$pp_colour_addr" ] = 0, -- no change + [ "$pp_colour_addg" ] = 0, -- no change + [ "$pp_colour_addb" ] = 0, -- no change + [ "$pp_colour_brightness" ] = -1, + [ "$pp_colour_contrast" ] = 1, -- no change + [ "$pp_colour_colour" ] = 1, -- no change + [ "$pp_colour_mulr" ] = 0, -- no change + [ "$pp_colour_mulg" ] = 0, -- no change + [ "$pp_colour_mulb" ] = 0, -- no change + }) + end) + + hook.Add('CalcView', 'qwb.fp', function(ply, origin, ang, fov, znear, zfar) + if not IsValid(LocalPlayer()) then return end + if ply ~= LocalPlayer() then return end + if not LocalPlayer():LookupBone('ValveBiped.Bip01_Head1') then return end + + if ply:Alive() then + local pos, angles = LocalPlayer():GetBonePosition( LocalPlayer():LookupBone('ValveBiped.Bip01_Head1') ) + + local cameraPos = pos + ang:Right() * 0 + cameraPos = cameraPos + ang:Up() * 4 + cameraPos = cameraPos + ply:GetForward() * 1 + + local filter = player.GetAll() + filter[#filter + 1] = ply + filter[#filter + 1] = ply:GetActiveWeapon() + table.Add(filter, ents.FindByClass('gmod_sent_vehicle_fphysics_base')) + + local tr = util.TraceHull({ + start = pos, + endpos = cameraPos, + filter = filter, + mins = Vector(-4.5,-4.5,-4.5), + maxs = Vector(4.5, 4.5, 4.5), + }) + cameraPos = tr.HitPos + + if tr.Hit and not texturesHit then + texturesHit = true + elseif not tr.Hit and texturesHit then + texturesHit = false + end + + -- if not qwb.ironsighted and qwb.IronSightLerp then + -- local sub = qwb.IronSightLerp - cameraPos + -- if math.abs(sub.x) > 1.5 or math.abs(sub.y) > 1.5 or math.abs(sub.z) > 1.5 then + -- qwb.IronSightLerp = Lerp(0.4, qwb.IronSightLerp, cameraPos) + -- else + -- qwb.IronSightLerp = nil + -- end + -- elseif qwb.ironsighted and not qwb.IronSightLerp then + -- qwb.IronSightLerp = cameraPos + -- end + + return { + -- origin = qwb.IronSightLerp or cameraPos, + origin = cameraPos, + angles = Angle((ang.p > 65 and 65) or (ang.p < -65 and -65) or ang.p, ang.y + math.sin(SysTime() / 1.5) * 0.5, ang.r + math.sin(SysTime()) * 0.5), + fov = fov, + drawviewer = true, + } + else + if IsValid(ply:GetRagdollEntity()) then + if texturesHit then texturesHit = false end + + local ragdoll = ply:GetRagdollEntity() + + local bone = ragdoll:LookupBone('ValveBiped.Bip01_Head1') or 0 + + if ragdoll:GetManipulateBoneScale(bone) ~= Vector(0.01, 0.01, 0.01) then + ragdoll:ManipulateBoneScale(bone, Vector(0.01, 0.01, 0.01)) + end + + local pos, angles = ragdoll:GetBonePosition(bone) + + return { + origin = pos, + angles = angles + Angle(-90,0,-90), + fov = fov, + drawviewer = false, + } + end + end + end) + + hook.Add('InputMouseApply', 'qwb.fp', function(cmd, x, y, ang) + ang.p = math.Clamp(ang.p + y / 50, -65, 65) + ang.y = ang.y - x / 50 + cmd:SetViewAngles(ang) + + return true + end) + + hook.Add('HUDShouldDraw', 'qwb.fp.crosshair', function(name) + if name == 'CHudCrosshair' then return false end + end) +end + +function stwFP.stopFP() + hook.Remove('CalcView', 'qwb.fp') + hook.Remove('InputMouseApply', 'qwb.fp') + hook.Remove('RenderScreenspaceEffects', 'qwb.fp.effects') + + fpEnabled = false + + if LocalPlayer().headHided then + stwFP.hideHead(false) + end +end + +hook.Add('qwb.fp.canEnableFP', 'qwb.fp', function() + if not IsValid(LocalPlayer()) then return end + if not IsValid(LocalPlayer():GetActiveWeapon()) then return end + + if GetViewEntity() ~= LocalPlayer() then return false end + if nonFPWeapons[LocalPlayer():GetActiveWeapon():GetClass()] then return false end +end) + +hook.Add('Think', 'qwb.fp.waiting', function() + if not LocalPlayer() or not IsValid(LocalPlayer()) or LocalPlayer() == NULL then return end + + if hook.Run('qwb.fp.canEnableFP') ~= false and not fpEnabled then + stwFP.startFP() + elseif hook.Run('qwb.fp.canEnableFP') == false and fpEnabled then + stwFP.stopFP() + end + + if LocalPlayer():Alive() and IsValid(LocalPlayer():GetRagdollEntity()) then + local ragdoll = LocalPlayer():GetRagdollEntity() + + if ragdoll:GetManipulateBoneScale(ragdoll:LookupBone('ValveBiped.Bip01_Head1') or 0) ~= Vector(1, 1, 1) then + ragdoll:ManipulateBoneScale(ragdoll:LookupBone('ValveBiped.Bip01_Head1') or 0, Vector(1, 1, 1)) + end + end +end) + +timer.Create('qwb.fp.checkHead', 3.5, 0, function() + if LocalPlayer().headHided and LocalPlayer():GetManipulateBoneScale(LocalPlayer():LookupBone('ValveBiped.Bip01_Head1') or 0) ~= Vector(0.01, 0.01, 0.01) then + stwFP.hideHead(true) + end +end) \ No newline at end of file diff --git a/addons/qwb/lua/qwb/cl_init.lua b/addons/qwb/lua/qwb/cl_init.lua new file mode 100644 index 0000000..7dfccf2 --- /dev/null +++ b/addons/qwb/lua/qwb/cl_init.lua @@ -0,0 +1,206 @@ +local firstPersonMode = CreateClientConVar('qwb_firstperson', 0, true, false, 'FirstPerson Mode (from the hip)', 0, 2) +local firstPersonZNear = CreateClientConVar('qwb_firstperson_znear', 1.75, true, false, 'FirstPerson ZNear', 1, 5) + +net.Receive('qwb.setIronsight', function() + local b = net.ReadBool() + + qwb.ironsighted = b +end) + +local angle_zero = Angle() +local firstPersonOffset1 = Vector() +local firstPersonOffset2 = Vector(-2, 0, 5) + +local headDefaultScale = Vector(1, 1, 1) +local headScale = Vector(0.01, 0.01, 0.01) + +local function hideHead(ply, boneID) + if not IsValid(ply) then return false end + + boneID = boneID or ply:LookupBone('ValveBiped.Bip01_Head1') + if not boneID then return end + + local curHeadScale = ply:GetManipulateBoneScale(boneID) + if curHeadScale ~= headScale then + ply:ManipulateBoneScale(boneID, headScale) + end +end + +local function showHead(ply, boneID) + if not IsValid(ply) then return false end + + boneID = boneID or ply:LookupBone('ValveBiped.Bip01_Head1') + if not boneID then return end + + local curHeadScale = ply:GetManipulateBoneScale(boneID) + if curHeadScale ~= headDefaultScale then + ply:ManipulateBoneScale(boneID, headDefaultScale) + end +end + +local function shouldChangeCalcView(ply) + if not IsValid(ply) then return false end + if GetViewEntity() ~= LocalPlayer() then return false end + if not ply:Alive() then return false end + + local weap = ply:GetActiveWeapon() + if not IsValid(weap) or not weap.IsQWB then return false end + + return true +end + +local firstPersonModes = { + [0] = function(ply, origin, angles) + local att = ply:GetAttachment( ply:LookupAttachment('eyes') ) + if not att then return end + + local pos, ang = LocalToWorld(firstPersonOffset1, angle_zero, att.Pos, att.Ang) + return pos, ang + end, + + [1] = function(ply, origin, angles) + local att = ply:GetAttachment( ply:LookupAttachment('anim_attachment_head') ) + if not att then return end + + local pos, ang = LocalToWorld(firstPersonOffset2, angle_zero, att.Pos, angles) + return pos, ang + end, +} + +local function getSightPos(ply) + local weap = ply:GetActiveWeapon() + local att = ply:GetAttachment( ply:LookupAttachment('anim_attachment_rh') ) + if not att then return end + + local offset = weap.IronsightOffset + local angOffset = weap.IronsightAngle + + if weap.Attachments and weap._attachments and weap._attachments.sights then + local id = weap._attachments.sights.id + local data = weap.Attachments.sights[id] + if data then + local camOffset = data.sightCameraOffset + if camOffset then + offset = offset + camOffset + end + + local camAngOffset = data.sightCameraAngleOffset + if camAngOffset then + angOffset = angOffset + camAngOffset + end + end + end + + return LocalToWorld(offset, angOffset or angle_zero, att.Pos, att.Ang) +end + +local function nonSightCalcView(ply, origin, angles, fov, znear, zfar) + if not shouldChangeCalcView(ply) then + qwb.ironsightLerpProgress = nil + return + end + + qwb.ironsightLerpProgress = qwb.ironsightLerpProgress or 0 + qwb.ironsightLerpProgress = math.Clamp(qwb.ironsightLerpProgress - (FrameTime() * 4), 0, 1) + + local mode = firstPersonMode:GetInt() + + local getPosFunc = firstPersonModes[mode] + if not getPosFunc then return end + + local pos, ang = getPosFunc(ply, origin, angles) + if not pos then return end + + local sightPos = getSightPos(ply) or origin + pos = LerpVector(qwb.ironsightLerpProgress, pos, sightPos) + + return { + origin = pos, + angles = ang, + fov = fov, + drawviewer = true, + znear = firstPersonZNear:GetFloat() or 1.75, + } +end + +timer.Create('qwb.headCheck', 0.5, 0, function() + if not shouldChangeCalcView(LocalPlayer()) or (firstPersonMode:GetInt() ~= 1 and not qwb.ironsighted) then + showHead(LocalPlayer()) + elseif shouldChangeCalcView(LocalPlayer()) and (firstPersonMode:GetInt() == 1 or qwb.ironsighted) then + hideHead(LocalPlayer()) + end +end) + +hook.Add('InputMouseApply', 'qwb.firstPerson', function(cmd, x, y, ang) + local mode = firstPersonMode:GetInt() + if mode ~= 1 then return end + + if not shouldChangeCalcView(LocalPlayer()) then return end + + ang.p = math.Clamp(ang.p + y / 50, -65, 65) + ang.y = ang.y - x / 50 + cmd:SetViewAngles(ang) + + return true +end) + +hook.Add('CalcView', 'qwb.ironsight', function(ply, origin, angles, fov, znear, zfar) + if not qwb.ironsighted then return nonSightCalcView(ply, origin, angles, fov, znear, zfar) end + + if not shouldChangeCalcView(ply) or qwb.isPlayerRunning(ply) then + qwb.ironsighted = nil + qwb.ironsightLerpProgress = nil + return + end + + local pos, ang = getSightPos(ply) + + qwb.ironsightLerpProgress = qwb.ironsightLerpProgress or 0 + qwb.ironsightLerpProgress = math.Clamp(qwb.ironsightLerpProgress + (FrameTime() * 3), 0, 1) + + local mode = firstPersonMode:GetInt() + local getFunc = firstPersonModes[mode] + local nonSightPos = getFunc and getFunc(ply, origin, angles) or origin + + pos = LerpVector(qwb.ironsightLerpProgress, nonSightPos, pos) + + local weap = ply:GetActiveWeapon() + + local view = { + origin = pos, + angles = ang, + fov = fov, + znear = weap.IronsightZNear or 2.35, + drawviewer = true, + } + + return view +end) + +-- Thanks to octothorp team for this solution, which fixes camera shake +hook.Add('RenderScene', '_qwb', function(pos, angle, fov) + local camData = hook.Run('CalcView', LocalPlayer(), pos, angle, fov) + if not camData then return end + + render.Clear(0, 0, 0, 255, true, true, true) + render.RenderView({ + angles = camData.angles, + origin = camData.origin, + drawhud = true, + drawmonitors = true, + dopostprocess = true, + }) + + return true +end) + +function qwb.fullyClearStencil() + render.SetStencilWriteMask( 0xFF ) + render.SetStencilTestMask( 0xFF ) + render.SetStencilReferenceValue( 0 ) + render.SetStencilCompareFunction( STENCIL_ALWAYS ) + render.SetStencilPassOperation( STENCIL_KEEP ) + render.SetStencilFailOperation( STENCIL_KEEP ) + render.SetStencilZFailOperation( STENCIL_KEEP ) + render.ClearStencil() +end \ No newline at end of file diff --git a/addons/qwb/lua/qwb/cl_spawnmenu.lua b/addons/qwb/lua/qwb/cl_spawnmenu.lua new file mode 100644 index 0000000..d34e038 --- /dev/null +++ b/addons/qwb/lua/qwb/cl_spawnmenu.lua @@ -0,0 +1,37 @@ +local lang = GetConVar('gmod_language'):GetString() + +language.Add('qwb.tab', 'QWB') + +if lang == 'ru' then + language.Add('qwb.options', 'Настройки') + language.Add('qwb.clientSettings', 'Клиентские настройки') + language.Add('qwb.serverSettings', 'Серверные настройки') + language.Add('qwb.firstPersonSelector.slider', 'Режим первого лица (от бедра)') + language.Add('qwb.firstPersonZNear.slider', 'Ограничение рендера от первого лица (ZNear)') + language.Add('qwb.realisticDamage.checkbox', 'Включить реалистичный урон') + language.Add('qwb.realisticDamage.checkbox.tooltip', 'Урон будет масштабироваться в зависимости от части тела, куда попадает пуля') +elseif lang == 'en' then + language.Add('qwb.options', 'Options') + language.Add('qwb.clientSettings', 'Client Settings') + language.Add('qwb.serverSettings', 'Server Settings') + language.Add('qwb.firstPersonSelector.slider', 'First person mode (from the hip)') + language.Add('qwb.firstPersonZNear.slider', 'Clipping a first person render (ZNear)') + language.Add('qwb.realisticDamage.checkbox', 'Enable realistic damage') + language.Add('qwb.realisticDamage.checkbox.tooltip', 'Damage will scale based on the part of the body the bullet hits') +end + +hook.Add('AddToolMenuTabs', 'qwb', function() + spawnmenu.AddToolTab('qwb', '#qwb.tab', 'icon16/user_edit.png') + spawnmenu.AddToolCategory('qwb', 'qwb.options', '#qwb.options') + + spawnmenu.AddToolMenuOption('qwb', 'qwb.options', 'qwb.clientSettings', '#qwb.clientSettings', '', '', function(pnl) + pnl:ClearControls() + pnl:NumSlider('#qwb.firstPersonSelector.slider', 'qwb_firstperson', 0, 2, 0):SetTooltip('#qwb.firstPersonSelector.slider') + pnl:NumSlider('#qwb.firstPersonZNear.slider', 'qwb_firstperson_znear', 1, 5, 2):SetTooltip('#qwb.firstPersonZNear.slider') + end) + + spawnmenu.AddToolMenuOption('qwb', 'qwb.options', 'qwb.serverSettings', '#qwb.serverSettings', '', '', function(pnl) + pnl:ClearControls() + pnl:CheckBox('#qwb.realisticDamage.checkbox', 'qwb_realisticdamage'):SetTooltip('#qwb.realisticDamage.checkbox.tooltip') + end) +end) \ No newline at end of file diff --git a/addons/qwb/lua/qwb/sh_init.lua b/addons/qwb/lua/qwb/sh_init.lua new file mode 100644 index 0000000..16e15d0 --- /dev/null +++ b/addons/qwb/lua/qwb/sh_init.lua @@ -0,0 +1,5 @@ +function qwb.isPlayerRunning(ply) + if not IsValid(ply) then return end + + return ply:KeyDown(IN_SPEED) and ply:GetVelocity():LengthSqr() > 0 +end \ No newline at end of file diff --git a/addons/qwb/lua/qwb/sv_init.lua b/addons/qwb/lua/qwb/sv_init.lua new file mode 100644 index 0000000..7135d61 --- /dev/null +++ b/addons/qwb/lua/qwb/sv_init.lua @@ -0,0 +1,28 @@ +util.AddNetworkString('qwb.setIronsight') +util.AddNetworkString('qwb.setAttachment') +util.AddNetworkString('qwb.removeAttachment') +util.AddNetworkString('qwb.muzzleFlashLight') + +local realisticDamage = CreateConVar('qwb_realisticdamage', 1, FCVAR_NOTIFY, '', 0, 1) + +local hitgroups = { + [HITGROUP_HEAD] = 10000, + [HITGROUP_STOMACH] = 1.5, + [HITGROUP_LEFTARM] = 0.2, + [HITGROUP_RIGHTARM] = 0.2, + [HITGROUP_LEFTLEG] = 0.3, + [HITGROUP_RIGHTLEG] = 0.3, +} + +hook.Add('ScalePlayerDamage', 'qwb.damage', function( ply, hitgroup, dmginfo ) + if not realisticDamage:GetBool() then return end + + local attacker = dmginfo:GetAttacker() + if not IsValid(attacker) or not IsValid(attacker:GetActiveWeapon()) then return end + + local weap = attacker:GetActiveWeapon() + if not weap.IsQWB then return end + + local scale = hitgroups[hitgroup] + if scale then dmginfo:ScaleDamage(scale) end +end) diff --git a/addons/qwb/lua/weapons/qwb_awp.lua b/addons/qwb/lua/weapons/qwb_awp.lua new file mode 100644 index 0000000..1ec90d1 --- /dev/null +++ b/addons/qwb/lua/weapons/qwb_awp.lua @@ -0,0 +1,43 @@ +AddCSLuaFile() + +SWEP.Base = 'weapon_base_qwb' + +SWEP.WorldModel = 'models/weapons/w_snip_awp.mdl' + +SWEP.PrintName = 'AWP' +SWEP.Category = 'qurs\' weapons base' + +SWEP.Spawnable = true +SWEP.AdminOnly = false + +SWEP.IronsightOffset = Vector(-3.1, -0.8, 6.7) +SWEP.IronsightAngle = Angle(-5, -2, 0) + +SWEP.Primary.Sound = 'Weapon_AWP.Single' +SWEP.Primary.Damage = 85 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Spread = 0.005 +SWEP.Primary.Delay = 1 + +SWEP.Primary.ClipSize = 8 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = '357' + +SWEP.Recoil = 8 +SWEP.HorizontalRecoil = 8 + +SWEP.HipFireRecoil = 5 + +SWEP.HoldType = 'ar2' + +SWEP.ScopeOffset = Vector(-36.9, 3.47, -0.955) +SWEP.ScopeSize = {184, 186} +SWEP.ScopeMat = Material('materials/qwb/scope/awp.png') +SWEP.ScopeFOV = 10 + +SWEP.ShellType = '762Nato' +SWEP.ShellOffset = Vector(-33, 1, -0.5) +SWEP.ShellVelocity = 75 + +SWEP.AimSound = Sound('weapons/ammopickup.wav') \ No newline at end of file diff --git a/addons/qwb/lua/weapons/qwb_glock18.lua b/addons/qwb/lua/weapons/qwb_glock18.lua new file mode 100644 index 0000000..71be4e3 --- /dev/null +++ b/addons/qwb/lua/weapons/qwb_glock18.lua @@ -0,0 +1,36 @@ +AddCSLuaFile() + +SWEP.Base = 'weapon_base_qwb' + +SWEP.WorldModel = 'models/weapons/w_pist_glock18.mdl' + +SWEP.PrintName = 'Glock-18' +SWEP.Category = 'qurs\' weapons base' + +SWEP.Spawnable = true +SWEP.AdminOnly = false + +SWEP.IronsightOffset = Vector(-10, -1.2, 3.95) +SWEP.IronsightAngle = Angle(0, 2, 0) + +SWEP.Primary.Sound = 'Weapon_Glock.Single' +SWEP.Primary.Damage = 20 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Spread = 0.01 +SWEP.Primary.Delay = 0.12 + +SWEP.Primary.ClipSize = 12 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = 'Pistol' + +SWEP.Recoil = 5.5 +SWEP.HorizontalRecoil = 10 + +SWEP.HoldType = 'revolver' + +SWEP.ShellType = '9mm' +SWEP.ShellOffset = Vector(-5, 1, -0.5) +SWEP.ShellVelocity = -50 + +SWEP.AimSound = Sound('weapons/ammopickup.wav') diff --git a/addons/qwb/lua/weapons/qwb_m3super.lua b/addons/qwb/lua/weapons/qwb_m3super.lua new file mode 100644 index 0000000..ce01983 --- /dev/null +++ b/addons/qwb/lua/weapons/qwb_m3super.lua @@ -0,0 +1,41 @@ +AddCSLuaFile() + +SWEP.Base = 'weapon_base_qwb' + +SWEP.WorldModel = 'models/weapons/w_shot_m3super90.mdl' + +SWEP.PrintName = 'M3 Super' +SWEP.Category = 'qurs\' weapons base' + +SWEP.Spawnable = true +SWEP.AdminOnly = false + +SWEP.IronsightOffset = Vector(-2.5, -0.95, 4.7) +SWEP.IronsightAngle = Angle(-5, -2, 0) +SWEP.IronsightZNear = 1 + +SWEP.Primary.Sound = 'Weapon_M3.Single' +SWEP.Primary.Damage = 6 +SWEP.Primary.NumShots = 8 +SWEP.Primary.Spread = 0.1 +SWEP.Primary.Delay = 1 + +SWEP.Primary.ClipSize = 6 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = 'Buckshot' + +SWEP.Recoil = 15 +SWEP.HorizontalRecoil = 25 + +SWEP.HipFireRecoil = 10 + +SWEP.HoldType = 'ar2' + +SWEP.ShellType = '12Gauge' +SWEP.ShellOffset = Vector(-20, 1, -1.5) +SWEP.ShellVelocity = 100 + +SWEP.AimSound = Sound('weapons/ammopickup.wav') + +SWEP.ShootPos = Vector(20, -0.8, 7.3) \ No newline at end of file diff --git a/addons/qwb/lua/weapons/qwb_ump45.lua b/addons/qwb/lua/weapons/qwb_ump45.lua new file mode 100644 index 0000000..75eafdb --- /dev/null +++ b/addons/qwb/lua/weapons/qwb_ump45.lua @@ -0,0 +1,69 @@ +AddCSLuaFile() + +SWEP.Base = 'weapon_base_qwb' + +SWEP.WorldModel = 'models/weapons/w_smg_ump45.mdl' + +SWEP.PrintName = 'UMP-45' +SWEP.Category = 'qurs\' weapons base' + +SWEP.Spawnable = true +SWEP.AdminOnly = false + +SWEP.IronsightOffset = Vector(-6, -0.88, 5.7) +SWEP.IronsightAngle = Angle(0, -2, 0) +SWEP.IronsightZNear = 0.4 + +SWEP.Primary.Sound = 'Weapon_UMP45.Single' +SWEP.Primary.Damage = 5 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Spread = 0.01 +SWEP.Primary.Delay = 0.1 + +SWEP.Primary.ClipSize = 30 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = 'SMG1' + +SWEP.Recoil = 2 +SWEP.HorizontalRecoil = 6.5 + +SWEP.HoldType = 'smg' + +SWEP.ShellType = '57' +SWEP.ShellOffset = Vector(-5, 1, 1.5) +SWEP.ShellVelocity = 65 + +SWEP.AimSound = Sound('weapons/ammopickup.wav') + +-- Attachments +SWEP.Attachments = { + sights = { + holo_sight = { + name = 'Holo sight', + mdl = 'models/attachment/crysis_holo.mdl', + pos = Vector(-3, 1.3, 6.95), + ang = Angle(-10, 0, 0), + scale = 1, + + sightCameraOffset = Vector(0, 0, 0.5), + sightCameraAngleOffset = Angle(), + }, + + optical_sight = { + name = 'Optical sight', + mdl = 'models/attachment/crysis_holo.mdl', + pos = Vector(-3, 1.3, 6.95), + ang = Angle(-10, 0, 0), + scale = 1, + + sightCameraOffset = Vector(0, 0, 0.5), + sightCameraAngleOffset = Angle(), + + opticPos = Vector(3.5, -0.3, 1.75), -- the position of the rendered crosshair relative to the attachment model + opticAng = Angle(-90, 0, 0), + opticSize = {80, 80}, + opticFOV = 5, + }, + }, +} \ No newline at end of file diff --git a/addons/qwb/lua/weapons/qwb_usp.lua b/addons/qwb/lua/weapons/qwb_usp.lua new file mode 100644 index 0000000..66bcde5 --- /dev/null +++ b/addons/qwb/lua/weapons/qwb_usp.lua @@ -0,0 +1,36 @@ +AddCSLuaFile() + +SWEP.Base = 'weapon_base_qwb' + +SWEP.WorldModel = 'models/weapons/w_pist_usp.mdl' + +SWEP.PrintName = 'USP' +SWEP.Category = 'qurs\' weapons base' + +SWEP.Spawnable = true +SWEP.AdminOnly = false + +SWEP.IronsightOffset = Vector(-10, -1.1, 4) +SWEP.IronsightAngle = Angle(0, 3, 0) + +SWEP.Primary.Sound = 'Weapon_USP.Single' +SWEP.Primary.Damage = 15 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Spread = 0.01 +SWEP.Primary.Delay = 0.15 + +SWEP.Primary.ClipSize = 15 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = 'Pistol' + +SWEP.Recoil = 5 +SWEP.HorizontalRecoil = 8 + +SWEP.HoldType = 'revolver' + +SWEP.ShellType = '9mm' +SWEP.ShellOffset = Vector(-4.5, 1, -1) +SWEP.ShellVelocity = 60 + +SWEP.AimSound = Sound('weapons/ammopickup.wav') \ No newline at end of file diff --git a/addons/qwb/lua/weapons/weapon_base_qwb/cl_att.lua b/addons/qwb/lua/weapons/weapon_base_qwb/cl_att.lua new file mode 100644 index 0000000..431eff8 --- /dev/null +++ b/addons/qwb/lua/weapons/weapon_base_qwb/cl_att.lua @@ -0,0 +1,114 @@ +function SWEP:OnRemove() + for k, v in pairs(self._attachments or {}) do + if IsValid(v.csEnt) then v.csEnt:Remove() end + end +end + +local function drawOpticAttach(self, flags) + local weap = self.parent + if not IsValid(weap) then return end + + local ply = self:GetParent() + if not IsValid(ply) then return end + + if ply:GetActiveWeapon() ~= weap then return end + + self:DrawModel(flags) + + if not qwb.ironsighted then return end + + local scopeMat = self.opticMat + + local attID = weap:LookupAttachment('muzzle') + if not attID then return end + + local att = weap:GetAttachment(attID) + if not att then return end + + local pos, ang = LocalToWorld(self.opticPos, self.opticAng or angle2, self:GetPos(), att.Ang) + + local w, h = unpack(self.opticSize) + local radius = math.max(w, h) + local semiRadius = radius * 0.5 + + qwb.fullyClearStencil() + render.SetStencilEnable(true) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilFailOperation(STENCIL_ZERO) + render.SetStencilZFailOperation(STENCIL_ZERO) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilReferenceValue(1) + + cam.Start3D2D(pos, ang, 0.01) + draw.NoTexture() + surface.SetDrawColor(color_black) + draw_Circle(semiRadius, semiRadius, semiRadius, 30) + cam.End3D2D() + + render.SetStencilCompareFunction(STENCIL_EQUAL) + + cam.Start3D2D(pos, ang, 0.01) + surface.SetDrawColor(color_white) + surface.SetMaterial(mat) + surface.DrawTexturedRect(0, 0, w, h) + + surface.SetDrawColor(color_white) + surface.SetMaterial(scopeMat) + surface.DrawTexturedRect(0, 0, w, h) + cam.End3D2D() + render.SetStencilEnable(false) +end + +net.Receive('qwb.setAttachment', function() + local weap = net.ReadEntity() + if not IsValid(weap) then return end + + local attachType, attachID = net.ReadString(), net.ReadString() + + local data = weap.Attachments[attachType] and weap.Attachments[attachType][attachID] + if not data then return end + + weap._attachments = weap._attachments or {} + weap._attachments[attachType] = weap._attachments[attachType] or {} + + local tbl = weap._attachments[attachType] + tbl.id = attachID + + if IsValid(tbl.csEnt) then tbl.csEnt:Remove() end + + local owner = weap:GetOwner() + + if not IsValid(tbl.csEnt) then + tbl.csEnt = ClientsideModel(data.mdl) + tbl.csEnt:SetParent(LocalPlayer(), LocalPlayer():LookupAttachment('anim_attachment_rh')) + tbl.csEnt:SetLocalPos(data.pos) + tbl.csEnt:SetLocalAngles(data.ang) + tbl.csEnt:SetModelScale(data.scale or 1) + + tbl.csEnt.parent = weap + + if data.opticPos and owner == LocalPlayer() then + tbl.csEnt.opticPos = data.opticPos + tbl.csEnt.opticAng = data.opticAng + tbl.csEnt.opticSize = data.opticSize + tbl.csEnt.opticMat = data.opticMat or weap.DefaultScopeMat + + tbl.csEnt.RenderOverride = drawOpticAttach + end + + tbl.csEnt:Spawn() + end +end) + +net.Receive('qwb.removeAttachment', function() + local weap = net.ReadEntity() + if not IsValid(weap) then return end + if not weap._attachments then return end + + local attachType = net.ReadString() + if not weap._attachments[attachType] then return end + + if IsValid(weap._attachments[attachType].csEnt) then weap._attachments[attachType].csEnt:Remove() end + + weap._attachments[attachType] = nil +end) \ No newline at end of file diff --git a/addons/qwb/lua/weapons/weapon_base_qwb/cl_att_menu.lua b/addons/qwb/lua/weapons/weapon_base_qwb/cl_att_menu.lua new file mode 100644 index 0000000..5f4fcbe --- /dev/null +++ b/addons/qwb/lua/weapons/weapon_base_qwb/cl_att_menu.lua @@ -0,0 +1,38 @@ +local color_bg = Color(0, 0, 0, 190) + +function SWEP:OpenAttMenu() + self._attMenu = vgui.Create('DPanel') + local pnl = self._attMenu + + pnl:SetSize(ScrW(), ScrH()) + + function pnl:Paint(w, h) + surface.SetDrawColor(color_bg) + surface.DrawRect(0, 0, w, h) + end + + qwb.attMenuOpened = true +end + +function SWEP:CloseAttMenu() + self._attMenu:Remove() + self._attMenu = nil + + qwb.attMenuOpened = nil +end + +function SWEP:ToggleAttMenu() + if IsValid(self._attMenu) then + self:CloseAttMenu() + else + self:OpenAttMenu() + end +end + +-- hook.Add('OnContextMenuOpen', 'qwb.att.menu', function() +-- local weap = LocalPlayer():GetActiveWeapon() +-- if not IsValid(weap) then return end +-- if not weap.IsQWB then return end + +-- weap:ToggleAttMenu() +-- end) \ No newline at end of file diff --git a/addons/qwb/lua/weapons/weapon_base_qwb/cl_init.lua b/addons/qwb/lua/weapons/weapon_base_qwb/cl_init.lua new file mode 100644 index 0000000..d73f209 --- /dev/null +++ b/addons/qwb/lua/weapons/weapon_base_qwb/cl_init.lua @@ -0,0 +1,128 @@ +include('shared.lua') + +include('sh_att.lua') +include('cl_att.lua') +include('cl_att_menu.lua') + +local size = 512 +local RT = GetRenderTarget('qwb_scope', size, size) + +local mat = CreateMaterial('qwb_scope', 'UnlitGeneric', { + ['$basetexture'] = RT:GetName(), + ['$translucent'] = 1, + ['$vertexcolor'] = 1, +}) + +local vector_origin = Vector() +local angle1, angle2 = Angle(0, 0, -90), Angle(-90, 0, 0) +local color_white, color_black = Color(255, 255, 255), Color(0, 0, 0) + +local function draw_Circle( x, y, radius, seg ) + local cir = {} + + table.insert( cir, { x = x, y = y, u = 0.5, v = 0.5 } ) + for i = 0, seg do + local a = math.rad( ( i / seg ) * -360 ) + table.insert( cir, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / 2 + 0.5, v = math.cos( a ) / 2 + 0.5 } ) + end + + local a = math.rad( 0 ) -- This is needed for non absolute segment counts + table.insert( cir, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / 2 + 0.5, v = math.cos( a ) / 2 + 0.5 } ) + + surface.DrawPoly( cir ) +end + +hook.Add('Think', 'qwb.renderScope', function() + if not qwb.ironsighted then return end + + local weap = LocalPlayer():GetActiveWeapon() + if not IsValid(weap) or not weap.IsQWB then return end + + local sightData = weap:GetAttach('sights') + if not weap.ScopeOffset and (not sightData or not IsValid(sightData.csEnt) or not sightData.csEnt.opticPos) then return end + + local att = weap:GetAttachment( weap:LookupAttachment('muzzle') ) + if not att then return end + local pos, ang = att.Pos, att.Ang + + local _, scopeAng = LocalToWorld( vector_origin, angle1, pos, ang ) + + local data = sightData and weap.Attachments and weap.Attachments.sights and weap.Attachments.sights[ sightData.id ] + + render.PushRenderTarget(RT) + render.RenderView({ + origin = pos, + angles = scopeAng, + fov = weap.ScopeFOV or (data and data.opticFOV) or 10, + }) + render.PopRenderTarget() +end) + +net.Receive('qwb.muzzleFlashLight', function() + local weap = net.ReadEntity() + if not IsValid(weap) then return end + + local dlight = DynamicLight( weap:EntIndex() ) + if dlight then + dlight.pos = weap:GetBulletSourcePos() + dlight.r = 255 + dlight.g = 145 + dlight.b = 10 + dlight.brightness = 1 + dlight.Decay = 6666 + dlight.Size = 512 + dlight.DieTime = CurTime() + 0.2 + end +end) + +function SWEP:DrawWorldModel(flags) + self:DrawModel(flags) + + if not self.ScopeOffset or not self.ScopeSize then return end + if not qwb.ironsighted then return end + + local owner = self:GetOwner() + if not IsValid(owner) then return end + if owner ~= LocalPlayer() then return end + + local scopeMat = self.ScopeMat or self.DefaultScopeMat + + local att = self:GetAttachment( self:LookupAttachment('muzzle') ) + if not att then return end + + local pos, ang = LocalToWorld(self.ScopeOffset, angle2, att.Pos, att.Ang) + + local w, h = unpack(self.ScopeSize) + local radius = math.max(w, h) + local semiRadius = radius * 0.5 + + qwb.fullyClearStencil() + render.SetStencilEnable(true) + render.SetStencilCompareFunction(STENCIL_ALWAYS) + render.SetStencilFailOperation(STENCIL_ZERO) + render.SetStencilZFailOperation(STENCIL_ZERO) + render.SetStencilPassOperation(STENCIL_REPLACE) + render.SetStencilReferenceValue(1) + + cam.Start3D2D(pos, ang, 0.01) + draw.NoTexture() + surface.SetDrawColor(color_black) + draw_Circle(semiRadius, semiRadius, semiRadius, 30) + cam.End3D2D() + + render.SetStencilCompareFunction(STENCIL_EQUAL) + + cam.Start3D2D(pos, ang, 0.01) + surface.SetDrawColor(color_white) + surface.SetMaterial(mat) + surface.DrawTexturedRect(0, 0, w, h) + + surface.SetDrawColor(color_white) + surface.SetMaterial(scopeMat) + surface.DrawTexturedRect(0, 0, w, h) + cam.End3D2D() + render.SetStencilEnable(false) +end + +function SWEP:SecondaryAttack() +end \ No newline at end of file diff --git a/addons/qwb/lua/weapons/weapon_base_qwb/init.lua b/addons/qwb/lua/weapons/weapon_base_qwb/init.lua new file mode 100644 index 0000000..f833280 --- /dev/null +++ b/addons/qwb/lua/weapons/weapon_base_qwb/init.lua @@ -0,0 +1,35 @@ +AddCSLuaFile('cl_init.lua') +AddCSLuaFile('shared.lua') + +AddCSLuaFile('sh_att.lua') +AddCSLuaFile('cl_att.lua') +AddCSLuaFile('cl_att_menu.lua') + +include('shared.lua') + +include('sh_att.lua') +include('sv_att.lua') + +function SWEP:CreateShootRage() + if not self.ShootRage then self.ShootRage = 0 end + self.ShootRage = self.ShootRage + self.HorizontalRecoil / 15 +end + +function SWEP:SecondaryAttack() + if self:GetSafetyStance() then return end + + if not IsValid(self:GetOwner()) then return end + local owner = self:GetOwner() + + if qwb.isPlayerRunning(owner) then return end + + self.Ironsighted = not self.Ironsighted + + if self.AimSound then + owner:EmitSound(self.AimSound, nil, nil, self.AimSoundVolume or 0.25) + end + + net.Start('qwb.setIronsight') + net.WriteBool(self.Ironsighted) + net.Send(owner) +end \ No newline at end of file diff --git a/addons/qwb/lua/weapons/weapon_base_qwb/sh_att.lua b/addons/qwb/lua/weapons/weapon_base_qwb/sh_att.lua new file mode 100644 index 0000000..5a3e67a --- /dev/null +++ b/addons/qwb/lua/weapons/weapon_base_qwb/sh_att.lua @@ -0,0 +1,3 @@ +function SWEP:GetAttach(att) + return self._attachments and self._attachments[att] +end \ No newline at end of file diff --git a/addons/qwb/lua/weapons/weapon_base_qwb/shared.lua b/addons/qwb/lua/weapons/weapon_base_qwb/shared.lua new file mode 100644 index 0000000..7d9ec7c --- /dev/null +++ b/addons/qwb/lua/weapons/weapon_base_qwb/shared.lua @@ -0,0 +1,356 @@ +SWEP.Weight = 5 +SWEP.DrawAmmo = true +SWEP.DrawCrosshair = false +SWEP.CSMuzzleFlashes = true + +SWEP.Base = 'weapon_base' +SWEP.IsQWB = true + +SWEP.Author = 'qurs' +SWEP.Contact = '' +SWEP.Purpose = '' +SWEP.Instructions = '' + +SWEP.ViewModel = 'models/weapons/v_knife_t.mdl' +SWEP.DefaultScopeMat = Material('materials/qwb/scope/awp.png') + +SWEP.Category = 'qurs\' weapons base' + +SWEP.Spawnable = false +SWEP.UseHands = true + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = 'none' + +SWEP.ShellType = '9mm' + +SWEP.SafetyStanceSound = Sound('weapons/smg1/switch_single.wav') + +local angle_zero = Angle() + +local passiveHoldTypes = { + revolver = 'normal', + ar2 = 'passive', + smg = 'passive', +} + +-- Thanks to octothorp team for this solution, which calculates the shot position +local defaultBulletPosAng = { + default = { Vector(7.7, 0.4, 3.95), Angle(-3, 5.5, 0) }, + + revolver = { Vector(7.7, 0.4, 3.95), Angle(-3, 5.5, 0) }, + ar2 = { Vector(20, -0.8, 11.2), Angle(-9.5, 0, 0) }, + smg = { Vector(14, -0.8, 6.8), Angle(-9.5, 0, 0) }, +} + +function SWEP:CalculatePassiveHoldType() + return passiveHoldTypes[self.HoldType] or 'normal' +end + +function SWEP:SafetyStanceChanged(_, old, new) + self:GetOwner():EmitSound(self.SafetyStanceSound) + + if new == true then + self:SetHoldType(self.PassiveHoldType or self:CalculatePassiveHoldType()) + else + self:SetHoldType(self.HoldType) + end +end + +function SWEP:SetupDataTables() + self:NetworkVar('Bool', 0, 'SafetyStance') + + self:NetworkVar('Vector', 0, 'LocalMuzzlePos') + self:NetworkVar('Angle', 0, 'LocalMuzzleAng') + + if SERVER then + self:NetworkVarNotify('SafetyStance', self.SafetyStanceChanged) + end +end + +function SWEP:Initialize() + self:SetHoldType(self.HoldType) + + local lpos, lang = unpack( defaultBulletPosAng[ self:GetHoldType() ] or defaultBulletPosAng.default ) + + self._sourceLocalMuzzlePos = self.ShootPos or lpos + self._sourceLocalMuzzleAng = self.ShootAng or lang + + if CLIENT then return end + + self:SetLocalMuzzlePos(self._sourceLocalMuzzlePos) + self:SetLocalMuzzleAng(self._sourceLocalMuzzleAng) +end + +function SWEP:Deploy() + return true +end + +function SWEP:Holster() + if SERVER then + self.Ironsighted = false + end + + return true +end + +function SWEP:OnRemove() + self:Holster() +end + +function SWEP:Reload() + if not IsFirstTimePredicted() then return end + + local owner = self:GetOwner() + if not IsValid(owner) then return end + + if owner:KeyDown(IN_USE) then return end + if qwb.isPlayerRunning(owner) then return end + + if not self:DefaultReload(ACT_VM_RELOAD) then return end + owner:SetAnimation(PLAYER_RELOAD) + + if SERVER then + self.Ironsighted = false + + net.Start('qwb.setIronsight') + net.WriteBool(self.Ironsighted) + net.Send(owner) + end + + self:SetNextPrimaryFire(CurTime() + 2) +end + +function SWEP:GetMuzzlePos() + local owner = self:GetOwner() + if not IsValid(owner) then return end + + local att = owner:GetAttachment( owner:LookupAttachment('anim_attachment_rh') ) + if not att then return end + + local lpos, lang = self:GetLocalMuzzlePos(), self:GetLocalMuzzleAng() + local pos, ang = LocalToWorld(lpos, lang, att.Pos, att.Ang) + + return pos, ang +end + +function SWEP:GetBoneAng() + local ang = self:GetLocalMuzzleAng() + local sourceAng = self._sourceLocalMuzzleAng + local pitch = ang.p - sourceAng.p + + return Angle(-pitch, 0, 0) +end + +function SWEP:GetBulletSourcePos() + return self:GetMuzzlePos() +end + +function SWEP:ShootBullet( src, dir, num_bullets, aimcone, tracer ) + local bullet = {} + bullet.Num = num_bullets + bullet.Src = src + bullet.Dir = dir + bullet.Spread = Vector( aimcone, aimcone, 0 ) + bullet.Tracer = tracer or 5 + bullet.Force = 1 + bullet.Damage = self.Primary.Damage + bullet.AmmoType = self.Primary.Ammo + bullet.TracerName = self.TracerName + + self:GetOwner():FireBullets( bullet ) + self:TakePrimaryAmmo(1) + + self:ShootEffects() +end + +function SWEP:MuzzleFlashCustom() + if SERVER then + net.Start('qwb.muzzleFlashLight') + net.WriteEntity(self) + net.SendPVS(self:GetBulletSourcePos()) + + return + end + + local effectData = EffectData() + effectData:SetEntity(self) + effectData:SetFlags(1) + + util.Effect('MuzzleFlash', effectData) +end + +function SWEP:ShellEffect() + if SERVER then return end + if self.NoShell then return end + + local shellType = self.ShellType + + local effectData = EffectData() + + local pos = self:GetPos() + if self.ShellOffset then + local att = self:GetAttachment( self:LookupAttachment('muzzle') ) + if att then + pos = LocalToWorld(self.ShellOffset, angle_zero, att.Pos, att.Ang) + end + end + + effectData:SetOrigin(pos) + effectData:SetFlags(self.ShellVelocity or 75) + + util.Effect('EjectBrass_' .. shellType, effectData) +end + +function SWEP:ShootEffects() + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + self:MuzzleFlashCustom() + self:ShellEffect() + + self:EmitSound( self.Primary.Sound ) + + self.RecoilAnimBack = nil + self.RecoilAnim = true +end + +function SWEP:PrimaryAttack() + if not self:CanPrimaryAttack() then return end + if not IsValid(self) or not IsValid(self:GetOwner()) then return end + local owner = self:GetOwner() + + local pos, ang = self:GetBulletSourcePos() + if not pos or not ang then return end + + local forward = ang:Forward() + + owner:LagCompensation(true) + self:ShootBullet(pos, forward, self.Primary.NumShots or 1, 0, 1) -- pre-last self.Primary.Spread or 0.01 + owner:LagCompensation(false) + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if SERVER then self:CreateShootRage() end + + -- if not self.IronSighted and self.HipFireRecoil then + -- local pitch = math.random() >= 0.5 and -self.HipFireRecoil or self.HipFireRecoil + -- local yaw = math.random() >= 0.5 and -self.HipFireRecoil or self.HipFireRecoil + -- local roll = math.random() >= 0.5 and -self.HipFireRecoil or self.HipFireRecoil + + -- owner:SetViewPunchAngles(Angle(pitch, yaw, roll)) + -- end + + if self.PostPrimaryAttack then self:PostPrimaryAttack() end +end + +function SWEP:CanPrimaryAttack() + if self:GetSafetyStance() then return end + + local owner = self:GetOwner() + if not IsValid(owner) then return end + + if qwb.isPlayerRunning(owner) then return end + + if self:Clip1() <= 0 then + self:EmitSound('weapons/clipempty_rifle.wav') + self:SetNextPrimaryFire(CurTime() + 0.7) + + return false + end + + if owner:WaterLevel() > 2 then return false end + + return true +end + +function SWEP:Think() + local owner = self:GetOwner() + if not IsValid(self:GetOwner()) then return end + + if owner:KeyDown(IN_USE) and owner:KeyDown(IN_RELOAD) and (not self.SafetyStanceCD or CurTime() >= self.SafetyStanceCD) then + self.SafetyStanceCD = CurTime() + 0.4 + self:SetSafetyStance( not self:GetSafetyStance() ) + end + + local passiveHoldType = self.PassiveHoldType or self:CalculatePassiveHoldType() + if qwb.isPlayerRunning(owner) and self:GetHoldType() ~= passiveHoldType then + self:SetHoldType(passiveHoldType) + + if self.Ironsighted then self.Ironsighted = false end + elseif not qwb.isPlayerRunning(owner) and not self:GetSafetyStance() and self:GetHoldType() == passiveHoldType then + self:SetHoldType(self.HoldType) + end + + if not self.ShootRageDelta then self.ShootRageDelta = 0 end + + if self.ShootRage and self.ShootRage > 0 then + self.ShootRageDelta = self.ShootRageDelta + 0.2 + + self.ShootRage = self.ShootRage - 0.05 + if self.ShootRage < 0 then self.ShootRage = 0 end + else + self.ShootRageDelta = 0 + end + + if self.RecoilAnim then + local bone = owner:LookupBone('ValveBiped.Bip01_R_Hand') + local bone2 = owner:LookupBone('ValveBiped.Bip01_R_UpperArm') + local needle = Angle(self.Recoil / 50 * 90, 0, 0) + local needle2 = Angle(0, (self.ShootRage or 0) * math.sin(self.ShootRageDelta or 0), 0) + + owner:ManipulateBoneAngles( bone, LerpAngle(0.8, owner:GetManipulateBoneAngles(bone), needle), true ) + owner:ManipulateBoneAngles( bone2, LerpAngle(0.45, owner:GetManipulateBoneAngles(bone2), needle2), true ) + + local ang = owner:GetManipulateBoneAngles(bone) + if math.abs(ang[1] - needle[1]) <= 0.1 then + self.RecoilAnim = nil + self.RecoilAnimBack = true + end + elseif self.RecoilAnimBack then + local bone = owner:LookupBone('ValveBiped.Bip01_R_Hand') + local bone2 = owner:LookupBone('ValveBiped.Bip01_R_UpperArm') + local needle = Angle(0, 0, 0) + local needle2 = Angle(0, (self.ShootRage or 0) * math.sin(self.ShootRageDelta or 0), 0) + + owner:ManipulateBoneAngles( bone, LerpAngle(0.1, owner:GetManipulateBoneAngles(bone), needle), true ) + owner:ManipulateBoneAngles( bone2, LerpAngle(0.1, owner:GetManipulateBoneAngles(bone2), needle2), true ) + + local ang = owner:GetManipulateBoneAngles(bone) + if math.abs(ang[1] - needle[1]) <= 0.1 then + self.RecoilAnimBack = nil + end + end + + -- if CLIENT then + -- local bone = owner:LookupBone('ValveBiped.Bip01_R_Hand') + -- local ang = self:GetBoneAng() + -- owner:ManipulateBoneAngles(bone, ang) + -- end + + -- if CLIENT then return end + + -- if owner.RecoilAnim then + -- local curAng = self:GetLocalMuzzleAng() + -- local sourceAng = self._sourceLocalMuzzleAng + + -- local needle = Angle(sourceAng[1] - (self.Recoil / 50 * 90), curAng[2], curAng[3]) + + -- self:SetLocalMuzzleAng( LerpAngle(0.8, self:GetLocalMuzzleAng(), needle) ) + + -- local ang = self:GetLocalMuzzleAng() + -- if math.abs(ang[1] - needle[1]) <= 0.1 then + -- owner.RecoilAnim = nil + -- owner.RecoilAnimBack = true + -- end + -- elseif owner.RecoilAnimBack then + -- local needle = self._sourceLocalMuzzleAng + + -- self:SetLocalMuzzleAng( LerpAngle(0.025, self:GetLocalMuzzleAng(), needle) ) + + -- local ang = self:GetLocalMuzzleAng() + -- if math.abs(ang[1] - needle[1]) <= 0.1 then + -- owner.RecoilAnimBack = nil + -- end + -- end +end diff --git a/addons/qwb/lua/weapons/weapon_base_qwb/sv_att.lua b/addons/qwb/lua/weapons/weapon_base_qwb/sv_att.lua new file mode 100644 index 0000000..2464d3f --- /dev/null +++ b/addons/qwb/lua/weapons/weapon_base_qwb/sv_att.lua @@ -0,0 +1,52 @@ +function SWEP:CanAttach(attachType, attachID) + return true +end + +function SWEP:SetAttach(attachType, attachID) + if not self.Attachments[attachType] or not self.Attachments[attachType][attachID] then return end + + self._attachments = self._attachments or {} + + if self._attachments[attachType] == attachID then return end + if not self:CanAttach(attachType, attachID) then return end + + self._attachments[attachType] = attachID + + net.Start('qwb.setAttachment') + net.WriteEntity(self) + net.WriteString(attachType) + net.WriteString(attachID) + net.Broadcast() +end + +function SWEP:RemoveAttach(attachType) + if not self._attachments then return end + if not self._attachments[attachType] then return end + + self._attachments[attachType] = nil + + net.Start('qwb.removeAttachment') + net.WriteEntity(self) + net.WriteString(attachType) + net.Broadcast() +end + +net.Receive('qwb.setAttachment', function(_, ply) + local weap = ply:GetActiveWeapon() + if not IsValid(weap) or not weap.IsQWB or not weap.SetAttach then return end + + local attachType, attachID = net.ReadString(), net.ReadString() + if not attachType or not attachID then return end + + weap:SetAttach(attachType, attachID) +end) + +net.Receive('qwb.removeAttachment', function(_, ply) + local weap = ply:GetActiveWeapon() + if not IsValid(weap) or not weap.IsQWB or not weap.RemoveAttach then return end + + local attachType = net.ReadString() + if not attachType then return end + + weap:RemoveAttach(attachType) +end) \ No newline at end of file diff --git a/addons/qwb/materials/qwb/scope/awp.png b/addons/qwb/materials/qwb/scope/awp.png new file mode 100644 index 0000000..1d0b0f8 Binary files /dev/null and b/addons/qwb/materials/qwb/scope/awp.png differ diff --git a/addons/reports_darkfated_ultracode/.gitattributes b/addons/reports_darkfated_ultracode/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/addons/reports_darkfated_ultracode/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/addons/reports_darkfated_ultracode/LICENSE b/addons/reports_darkfated_ultracode/LICENSE new file mode 100644 index 0000000..e62ec04 --- /dev/null +++ b/addons/reports_darkfated_ultracode/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/addons/reports_darkfated_ultracode/README.md b/addons/reports_darkfated_ultracode/README.md new file mode 100644 index 0000000..3294035 --- /dev/null +++ b/addons/reports_darkfated_ultracode/README.md @@ -0,0 +1,21 @@ +# FatedTicket +Administration system for handling complaints + +🔥 [Mantle](https://github.com/darkfated/mantle): in order for the system to work, you need to use this Gmod library + +## What opportunities does it have ❤️ + +### Convenient interaction menu with tickets +Admin part of menu +Ticket without violator + +### Player's tickets submission menu +Player menu + +### There are statistics +Statictics menu + +### The minimalistic part of evaluating the work of the administration +Evaluation menu + +### And much more in the future! diff --git a/addons/reports_darkfated_ultracode/lua/autorun/fated_ticket_autorun.lua b/addons/reports_darkfated_ultracode/lua/autorun/fated_ticket_autorun.lua new file mode 100644 index 0000000..4098fb0 --- /dev/null +++ b/addons/reports_darkfated_ultracode/lua/autorun/fated_ticket_autorun.lua @@ -0,0 +1,2 @@ +AddCSLuaFile('fated_ticket/init.lua') +include('fated_ticket/init.lua') \ No newline at end of file diff --git a/addons/reports_darkfated_ultracode/lua/fated_ticket/client.lua b/addons/reports_darkfated_ultracode/lua/fated_ticket/client.lua new file mode 100644 index 0000000..e24705f --- /dev/null +++ b/addons/reports_darkfated_ultracode/lua/fated_ticket/client.lua @@ -0,0 +1,443 @@ +local color_green = Color(96, 221, 92) + +net.Receive('FatedTicket-Msg', function() + local txt = net.ReadString() + + chat.AddText(color_green, '[Тикеты] ', color_white, txt) + chat.PlaySound() +end) + +local color_blue_1 = Color(80, 107, 181) +local color_blue_2 = Color(61, 61, 138) + +net.Receive('FatedTicket-Msg', function() + local txt = net.ReadString() + + chat.AddText(color_blue_1, '[', color_blue_2, 'Тикеты', color_blue_1, '] ', color_white, txt) + chat.PlaySound() +end) + +local mat_btn_roll = Material('fated_ticket/roll_btn.png') +local mat_star = Material('fated_ticket/star.png') +local color_player_panel_action = Color(74, 86, 100) +local color_player_btn = Color(24, 24, 26) +local color_action_btn = Color(76, 76, 76) +local color_panel_target = Color(204, 64, 64) +local color_gray = Color(80, 80, 80) + +local function safeText(text) + return string.match(text, '^#([a-zA-Z_]+)$') and text .. ' ' or text +end + +local function DrawNonParsedText(text, font, x, y, color, xAlign) + return draw.DrawText(safeText(text), font, x, y, color, xAlign) +end + +local function charWrap(text, remainingWidth, maxWidth) + local totalWidth = 0 + + text = text:gsub('.', function(char) + totalWidth = totalWidth + surface.GetTextSize(char) + + if totalWidth >= remainingWidth then + totalWidth = surface.GetTextSize(char) + remainingWidth = maxWidth + + return '\n' .. char + end + + return char + end) + + return text, totalWidth +end + +local function textWrap(text, font, maxWidth) + local totalWidth = 0 + + surface.SetFont(font) + + local spaceWidth = surface.GetTextSize(' ') + + text = text:gsub('(%s?[%S]+)', function(word) + local char = string.sub(word, 1, 1) + + if char == '\n' or char == '\t' then + totalWidth = 0 + end + + local wordlen = surface.GetTextSize(word) + + totalWidth = totalWidth + wordlen + + if wordlen >= maxWidth then + local splitWord, splitPoint = charWrap(word, maxWidth - totalWidth + wordlen, maxWidth) + + totalWidth = splitPoint + + return splitWord + elseif totalWidth < maxWidth then + return word + end + + if char == ' ' then + totalWidth = wordlen - spaceWidth + + return '\n' .. string.sub(word, 2) + end + + totalWidth = wordlen + + return '\n' .. word + end) + + return text +end + +local function CreateAdminTicketMenu(tickets_count) + FatedTicket.admin_menu = vgui.Create('DFrame') + Mantle.ui.frame(FatedTicket.admin_menu, 'Количество жалоб: ' .. tickets_count, 300, 200, true) + FatedTicket.admin_menu:SetPos(15, 52) + FatedTicket.admin_menu.default_size = { 300, 200 } + + FatedTicket.admin_menu.btn_roll = vgui.Create('DButton', FatedTicket.admin_menu) + FatedTicket.admin_menu.btn_roll:SetSize(20, 20) + FatedTicket.admin_menu.btn_roll:SetPos(FatedTicket.admin_menu:GetWide() - 44, 2) + FatedTicket.admin_menu.btn_roll:SetText('') + FatedTicket.admin_menu.btn_roll.Paint = function(_, w, h) + surface.SetDrawColor(color_white) + surface.SetMaterial(mat_btn_roll) + surface.DrawTexturedRect(0, 0, w, h) + end + FatedTicket.admin_menu.btn_roll.DoClick = function() + if FatedTicket.admin_menu:GetTall() == 24 then + FatedTicket.admin_menu:SetSize(FatedTicket.admin_menu.default_size[1], FatedTicket.admin_menu.default_size + [2]) + FatedTicket.admin_menu.btn_roll:SetPos(FatedTicket.admin_menu:GetWide() - 44, 2) + else + FatedTicket.admin_menu:SetSize(FatedTicket.admin_menu.default_size[1] * 0.7, 24) + FatedTicket.admin_menu.btn_roll:SetPos(FatedTicket.admin_menu:GetWide() - 22, 2) + end + end + + FatedTicket.admin_menu.sp = vgui.Create('DScrollPanel', FatedTicket.admin_menu) + Mantle.ui.sp(FatedTicket.admin_menu.sp) + FatedTicket.admin_menu.sp:Dock(FILL) + + function FatedTicket.admin_menu.sp:CreateItems(tickets_count) + FatedTicket.admin_menu.sp:Clear() + FatedTicket.admin_menu.title_text = 'Количество жалоб: ' .. tickets_count + + for ply, ticket_data in pairs(FatedTicket.reports_cl) do + local ticket_pan = vgui.Create('DPanel', FatedTicket.admin_menu.sp) + ticket_pan:Dock(TOP) + ticket_pan:DockMargin(0, 0, 0, 4) + ticket_pan:SetTall(30) + ticket_pan.Paint = function(_, w, h) + draw.RoundedBox(0, 0, 0, w - 90, h, + ticket_data.admin and color_player_panel_action or Mantle.color.panel[2]) + + draw.SimpleText(ply:Name(), 'Fated.22', 30, h * 0.5 - 1, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + end + + ticket_pan.avatar = vgui.Create('AvatarImage', ticket_pan) + ticket_pan.avatar:Dock(LEFT) + ticket_pan.avatar:DockMargin(3, 3, 0, 3) + ticket_pan.avatar:SetWide(24) + ticket_pan.avatar:SetPlayer(ply) + + ticket_pan.btn = vgui.Create('DButton', ticket_pan) + ticket_pan.btn:Dock(RIGHT) + ticket_pan.btn:SetWide(90) + ticket_pan.btn:SetText('') + ticket_pan.btn.Paint = function(self, w, h) + draw.RoundedBoxEx(6, 0, 0, w, h, color_player_btn, false, true, false, true) + draw.SimpleText('Открыть', 'Fated.18', w * 0.5, h * 0.5, color_white, TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + end + ticket_pan.btn.DoClick = function(self) + if IsValid(FatedTicket.admin_menu.player_profile) then + FatedTicket.admin_menu.player_profile:Remove() + end + + FatedTicket.admin_menu.player_profile = vgui.Create('DFrame') + Mantle.ui.frame(FatedTicket.admin_menu.player_profile, 'Жалоба ' .. ply:Name(), 300, 200, true) + local menu_x, menu_y = FatedTicket.admin_menu:GetPos() + FatedTicket.admin_menu.player_profile:SetPos(menu_x + FatedTicket.admin_menu:GetWide() + 6, menu_y) + + local MainPanel = vgui.Create('DPanel', FatedTicket.admin_menu.player_profile) + MainPanel:Dock(FILL) + MainPanel.Paint = nil + + local has_target = (ticket_data.target != ply) + local HalfWide = FatedTicket.admin_menu.player_profile:GetWide() * 0.5 + + if has_target then + local target_job = team.GetName(ticket_data.target:Team()) + local target_text = 'Ник: ' .. + ticket_data.target:Name() .. + '\nПривилегия: ' .. ticket_data.target:GetUserGroup() .. '\nРабота: ' .. target_job + target_text = textWrap(target_text, 'Fated.18', HalfWide - 12) + + MainPanel.left = vgui.Create('DPanel', MainPanel) + MainPanel.left:Dock(LEFT) + MainPanel.left:SetWide(HalfWide - 8) + MainPanel.left.Paint = function(_, w, h) + draw.RoundedBox(6, 0, 0, w, 24, color_panel_target) + draw.SimpleText('Нарушитель', 'Fated.18', w * 0.5, 3, color_white, TEXT_ALIGN_CENTER) + DrawNonParsedText(target_text, 'Fated.18', 4, 26, color_white) + end + end + + local reason_txt = ticket_data.reason:gsub('//', '\n'):gsub('\\n', '\n') + reason_txt = textWrap(reason_txt, 'Fated.18', + (has_target and HalfWide - 12) or FatedTicket.admin_menu.player_profile:GetWide() - 16) + + MainPanel.right = vgui.Create('DPanel', MainPanel) + MainPanel.right:Dock(RIGHT) + MainPanel.right:SetWide((has_target and HalfWide - 8) or + FatedTicket.admin_menu.player_profile:GetWide() - 12) + MainPanel.right.Paint = function(self, w, h) + draw.RoundedBox(6, 0, 0, w, 24, color_player_btn) + draw.SimpleText('Причина', 'Fated.18', w * 0.5, 3, color_white, TEXT_ALIGN_CENTER) + DrawNonParsedText(reason_txt, 'Fated.18', 4, 26, color_white) + end + + local BottomPanel = vgui.Create('DPanel', FatedTicket.admin_menu.player_profile) + BottomPanel:Dock(BOTTOM) + BottomPanel:DockMargin(0, 4, 0, 0) + BottomPanel:SetTall(30) + BottomPanel.Paint = function(_, w, h) + draw.RoundedBox(6, 0, 0, w, h, color_action_btn) + end + + BottomPanel.left = vgui.Create('DButton', BottomPanel) + BottomPanel.left:Dock(LEFT) + BottomPanel.left:SetWide(FatedTicket.admin_menu.player_profile:GetWide() * 0.5 - 6) + BottomPanel.left:SetText('') + BottomPanel.left.Paint = function(self, w, h) + draw.SimpleText('Закрыть', 'Fated.22', w * 0.5, h * 0.5 - 1, color_white, TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + end + BottomPanel.left.DoClick = function() + net.Start('FatedTicket-AdminAction') + net.WriteBool(true) + net.WriteEntity(ply) + net.SendToServer() + + FatedTicket.admin_menu.player_profile:Remove() + end + + local right_btn_text = ticket_data.admin and 'Действия' or 'Взять' + + BottomPanel.right = vgui.Create('DButton', BottomPanel) + BottomPanel.right:Dock(RIGHT) + BottomPanel.right:SetWide(BottomPanel.left:GetWide()) + BottomPanel.right:SetText('') + BottomPanel.right.Paint = function(self, w, h) + draw.SimpleText(right_btn_text, 'Fated.22', w * 0.5, h * 0.5 - 1, color_white, TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + end + BottomPanel.right.DoClick = function() + if ! ticket_data.admin then + net.Start('FatedTicket-AdminAction') + net.WriteBool(false) + net.WriteEntity(ply) + net.SendToServer() + + FatedTicket.admin_menu.player_profile:Remove() + else + local DM = Mantle.ui.derma_menu() + + if has_target then + DM:AddOption('SteamID Нарушителя', function() + Mantle.func.sound() + + SetClipboardText(ticket_data.target:SteamID()) + end, 'icon16/bullet_red.png') + end + + DM:AddOption('SteamID Игрока', function() + Mantle.func.sound() + + SetClipboardText(ply:SteamID()) + end, 'icon16/bullet_blue.png') + end + end + end + end + end + + FatedTicket.admin_menu.sp:CreateItems(tickets_count) +end + +net.Receive('FatedTicket-UpdateClientData', function() + if LocalPlayer():IsAdmin() then + FatedTicket.reports_cl = net.ReadTable() + + local count = table.Count(FatedTicket.reports_cl) + + if IsValid(FatedTicket.admin_menu) then + FatedTicket.admin_menu.sp:CreateItems(count) + else + CreateAdminTicketMenu(count) + end + end +end) + +net.Receive('FatedTicket-Rating', function() + if IsValid(FatedTicket.rating_menu) then + FatedTicket.rating_menu:Remove() + end + + local admin = net.ReadEntity() + + FatedTicket.rating_menu = vgui.Create('DFrame') + Mantle.ui.frame(FatedTicket.rating_menu, 'Оцените ' .. admin:Name(), 250, 62, true) + FatedTicket.rating_menu:SetPos(ScrW() * 0.5 - 125, ScrH() - 77) + FatedTicket.rating_menu.star = 0 + + for rating = 1, 10 do + local btn_rating = vgui.Create('DButton', FatedTicket.rating_menu) + + local size = (FatedTicket.rating_menu:GetWide() - 12) * 0.1 + + btn_rating:SetText('') + btn_rating:SetSize(24, 24) + btn_rating:SetPos(6 + 24 * (rating - 1), 30) + btn_rating.Paint = function(self, w, h) + if self:IsHovered() then + FatedTicket.rating_menu.star = rating + end + + surface.SetDrawColor(rating <= FatedTicket.rating_menu.star and color_white or color_gray) + surface.SetMaterial(mat_star) + surface.DrawTexturedRect(0, 0, w, h) + end + btn_rating.DoClick = function() + net.Start('FatedTicket-RatingResult') + net.WriteEntity(admin) + net.WriteInt(rating, 5) + net.SendToServer() + + FatedTicket.rating_menu:Remove() + end + end +end) + +concommand.Add('fated_ticket_create', function(_, _, _, reason_text) + if IsValid(FatedTicket.create_menu) then + FatedTicket.create_menu:Remove() + end + + FatedTicket.create_menu = vgui.Create('DFrame') + FatedTicket.create_menu:MakePopup() + Mantle.ui.frame(FatedTicket.create_menu, 'Создание жалобы', 300, 170, true) + FatedTicket.create_menu:Center() + + local entry_reason = Mantle.ui.desc_entry(FatedTicket.create_menu, 'Причина:', 'Напишите обстоятельства') + + local TargetLabel = vgui.Create('DLabel', FatedTicket.create_menu) + TargetLabel:Dock(TOP) + TargetLabel:DockMargin(0, 4, 0, 0) + TargetLabel:SetText('Нарушитель:') + TargetLabel:SetFont('Fated.16') + + local TargetComboBox = vgui.Create('DComboBox', FatedTicket.create_menu) + TargetComboBox:Dock(TOP) + TargetComboBox:DockMargin(0, 4, 0, 0) + TargetComboBox:SetValue('Выберите игрока') + TargetComboBox:SetFont('Fated.16') + + local players = player.GetAll() + + for plyID = 1, #players do + local ply = players[plyID] + + if ply == LocalPlayer() then + continue + end + + TargetComboBox:AddChoice(ply:Name(), ply, nil, 'icon16/user.png') + end + + TargetComboBox:AddChoice('Без нарушителя', LocalPlayer(), nil, 'icon16/page_white.png') + + if reason_text != '' then + entry_reason:SetValue(reason_text) + end + + local SendButton = vgui.Create('DButton', FatedTicket.create_menu) + Mantle.ui.btn(SendButton, nil, nil, Color(244, 136, 63), nil, nil, nil, true) + SendButton:Dock(FILL) + SendButton:SetText('Отправить') + SendButton:DockMargin(0, 4, 0, 0) + SendButton.DoClick = function() + local _, target = TargetComboBox:GetSelected() + + net.Start('FatedTicket-Send') + net.WriteString(entry_reason:GetValue()) + net.WriteEntity(target) + net.SendToServer() + + FatedTicket.create_menu:Remove() + end +end) + +concommand.Add('fated_ticket_statistic', function() + if ! LocalPlayer():IsAdmin() then + return + end + + if IsValid(FatedTicket.statistic_menu) then + FatedTicket.statistic_menu:Remove() + end + + FatedTicket.statistic_menu = vgui.Create('DFrame') + FatedTicket.statistic_menu:MakePopup() + Mantle.ui.frame(FatedTicket.statistic_menu, 'Статистика администрации', 500, 300, true) + FatedTicket.statistic_menu:Center() + + FatedTicket.statistic_menu.sp = vgui.Create('DScrollPanel', FatedTicket.statistic_menu) + Mantle.ui.sp(FatedTicket.statistic_menu.sp) + FatedTicket.statistic_menu.sp:Dock(FILL) + + local players = player.GetAll() + + for plyID = 1, #players do + local ply = players[plyID] + + if ! ply:IsAdmin() then + continue + end + + local info_pan = vgui.Create('DPanel', FatedTicket.statistic_menu.sp) + info_pan:Dock(TOP) + info_pan:SetTall(34) + info_pan.Paint = function(_, w, h) + draw.SimpleText('Ник', 'Fated.22', 8, h * 0.5 - 3, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText('Рейтинг', 'Fated.22', w * 0.5, h * 0.5 - 3, color_white, TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + draw.SimpleText('Кол-во', 'Fated.22', w - 8, h * 0.5 - 3, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + + local ply_pan = vgui.Create('DPanel', FatedTicket.statistic_menu.sp) + ply_pan:Dock(TOP) + ply_pan:SetTall(30) + + local ply_name = ply:Name() + local ply_tickets = ply:GetNWInt('fated_ticket', 0) + local ply_tickets_rating = ply:GetNWInt('fated_ticket_rating', 0) + local ply_rating_text = string.sub( + ply_tickets_rating / ply_tickets == 0 and 1 or (ply_tickets_rating / ply_tickets), 0, 3) + + ply_pan.Paint = function(_, w, h) + draw.RoundedBox(6, 0, 0, w, h, Mantle.color.panel[2]) + + draw.SimpleText(ply_name, 'Fated.18', 8, h * 0.5 - 1, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(ply_rating_text, 'Fated.18', w * 0.5, h * 0.5 - 1, color_white, TEXT_ALIGN_CENTER, + TEXT_ALIGN_CENTER) + draw.SimpleText(ply_tickets, 'Fated.18', w - 8, h * 0.5 - 1, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER) + end + end +end) diff --git a/addons/reports_darkfated_ultracode/lua/fated_ticket/init.lua b/addons/reports_darkfated_ultracode/lua/fated_ticket/init.lua new file mode 100644 index 0000000..bf911cd --- /dev/null +++ b/addons/reports_darkfated_ultracode/lua/fated_ticket/init.lua @@ -0,0 +1,27 @@ +--[[ + * FatedTicket * + GitHub: https://github.com/darkfated/FatedTicket + Author's discord: darkfated +]]-- + +local function run_scripts() + local cl = SERVER and AddCSLuaFile or include + local sv = SERVER and include or function() end + + sv('server.lua') + + cl('client.lua') +end + +local function init() + if SERVER then + resource.AddFile('materials/fated_ticket/roll_btn.png') + resource.AddFile('materials/fated_ticket/star.png') + end + + FatedTicket = FatedTicket or {} + + run_scripts() +end + +init() \ No newline at end of file diff --git a/addons/reports_darkfated_ultracode/lua/fated_ticket/server.lua b/addons/reports_darkfated_ultracode/lua/fated_ticket/server.lua new file mode 100644 index 0000000..3d66ccf --- /dev/null +++ b/addons/reports_darkfated_ultracode/lua/fated_ticket/server.lua @@ -0,0 +1,171 @@ +util.AddNetworkString('FatedTicket-Msg') +util.AddNetworkString('FatedTicket-Send') +util.AddNetworkString('FatedTicket-UpdateClientData') +util.AddNetworkString('FatedTicket-AdminAction') +util.AddNetworkString('FatedTicket-Rating') +util.AddNetworkString('FatedTicket-RatingResult') + +local function FatedNotify(pl, txt) + net.Start('FatedTicket-Msg') + net.WriteString(txt) + + if pl == true then + net.Broadcast() + else + net.Send(pl) + end +end + +FatedTicket.reports = FatedTicket.reports or {} + +local function FatedUpdateClient() + net.Start('FatedTicket-UpdateClientData') + net.WriteTable(FatedTicket.reports) + net.Broadcast() +end + +net.Receive('FatedTicket-Send', function(_, pl) + local reason = net.ReadString() + local target = net.ReadEntity() + + if reason == '' then + FatedNotify(pl, 'Укажите причину.') + + return + end + + if string.len(reason) > 120 then + FatedNotify(pl, 'Причина слишком длинная.') + + return + end + + if !IsValid(target) then + FatedNotify(pl, 'Выберите игрока.') + + return + end + + FatedNotify(pl, 'Жалоба отправлена!') + + FatedTicket.reports[pl] = { + reason = reason, + target = target, + } + + FatedUpdateClient() +end) + +net.Receive('FatedTicket-AdminAction', function(_, pl) + local mode_close = net.ReadBool() + local ticket_ply = net.ReadEntity() + + if !IsValid(ticket_ply) then + FatedNotify(pl, 'Этот игрок больше не существует.') + + return + end + + if !pl:IsAdmin() then + FatedNotify(pl, 'У вас нет прав на выполнение этого действия.') + + return + end + + if ticket_ply == pl then + FatedNotify(pl, 'Нельзя разбирать свою жалобу!') + + return + end + + local players = player.GetAll() + local current_ticket = FatedTicket.reports[ticket_ply] + + if mode_close then + if current_ticket.admin then + if current_ticket.admin != pl then + FatedNotify(pl, 'Это не ваша жалоба.') + + return + else + pl:SetNWInt('fated_ticket', pl:GetNWInt('fated_ticket', 0) + 1) + + net.Start('FatedTicket-Rating') + net.WriteEntity(pl) + net.Send(ticket_ply) + end + end + + FatedTicket.reports[ticket_ply] = nil + + FatedUpdateClient() + FatedNotify(true, pl:Name() .. ' (' .. pl:SteamID() .. ') закрыл жалобу ' .. ticket_ply:Name()) + else + current_ticket.admin = pl + + FatedUpdateClient() + FatedNotify(true, pl:Name() .. ' (' .. pl:SteamID() .. ') взял жалобу ' .. ticket_ply:Name()) + + pl:SetPos(ticket_ply:GetPos()) + end +end) + +net.Receive('FatedTicket-RatingResult', function(_, pl) + local admin = net.ReadEntity() + + if !IsValid(admin) then + return + end + + local rating = net.ReadInt(5) + + admin:SetNWInt('fated_ticket_rating', pl:GetNWInt('fated_ticket_rating', 0) + rating) + + FatedNotify(admin, 'Вас оценили на ' .. rating .. '.') +end) + +hook.Add('PlayerInitialSpawn', 'FatedTicket', function(pl) + if pl:GetPData('fated_ticket') != nil then + pl:SetNWInt('fated_ticket', pl:GetPData('fated_ticket')) + end + + if pl:GetPData('fated_ticket_rating') != nil then + pl:SetNWInt('fated_ticket_rating', pl:GetPData('fated_ticket_rating')) + end +end) + +hook.Add('PlayerDisconnected', 'FatedTicket', function(pl) + if FatedTicket.reports[pl] then + if IsValid(FatedTicket.reports[pl].admin) then + FatedNotify(FatedTicket.reports[pl].admin, 'Жалоба ' .. pl:Name() .. ' отменена - игрок вышел.') + end + + FatedTicket.reports[pl] = nil + + FatedUpdateClient() + end + + if pl:GetNWInt('fated_ticket') != nil then + pl:SetPData('fated_ticket', pl:GetNWInt('fated_ticket')) + end + + if pl:GetNWInt('fated_ticket_rating') != nil then + pl:SetPData('fated_ticket_rating', pl:GetNWInt('fated_ticket_rating')) + end +end) + +hook.Add('PlayerSay', 'FatedTicket', function(pl, text) + if text == '/report' then + pl:ConCommand('fated_ticket_create') + + return '' + elseif text[1] == '@' then + local text_len = string.len(text) + + if text_len > 1 then + pl:ConCommand('fated_ticket_create ' .. string.sub(text, 2, text_len)) + + return '' + end + end +end) diff --git a/addons/reports_darkfated_ultracode/materials/fated_ticket/roll_btn.png b/addons/reports_darkfated_ultracode/materials/fated_ticket/roll_btn.png new file mode 100644 index 0000000..8c42bd9 Binary files /dev/null and b/addons/reports_darkfated_ultracode/materials/fated_ticket/roll_btn.png differ diff --git a/addons/reports_darkfated_ultracode/materials/fated_ticket/star.png b/addons/reports_darkfated_ultracode/materials/fated_ticket/star.png new file mode 100644 index 0000000..1ab9a57 Binary files /dev/null and b/addons/reports_darkfated_ultracode/materials/fated_ticket/star.png differ diff --git a/gamemodes/base/base.txt b/gamemodes/base/base.txt new file mode 100644 index 0000000..805154e --- /dev/null +++ b/gamemodes/base/base.txt @@ -0,0 +1,5 @@ +"base" +{ + "title" "Base" + "base" "" +} \ No newline at end of file diff --git a/gamemodes/base/entities/effects/base.lua b/gamemodes/base/entities/effects/base.lua new file mode 100644 index 0000000..0333cfb --- /dev/null +++ b/gamemodes/base/entities/effects/base.lua @@ -0,0 +1,39 @@ + +--[[--------------------------------------------------------- + Returns the right shoot start position for a tracer - based on 'data'. +-----------------------------------------------------------]] +function EFFECT:GetTracerShootPos( Position, Ent, Attachment ) + + self.ViewModelTracer = false + + if ( !IsValid( Ent ) ) then return Position end + if ( !Ent:IsWeapon() ) then return Position end + + -- Shoot from the viewmodel + if ( Ent:IsCarriedByLocalPlayer() && !LocalPlayer():ShouldDrawLocalPlayer() ) then + + local ViewModel = LocalPlayer():GetViewModel() + + if ( ViewModel:IsValid() ) then + + local att = ViewModel:GetAttachment( Attachment ) + if ( att ) then + Position = att.Pos + self.ViewModelTracer = true + end + + end + + -- Shoot from the world model + else + + local att = Ent:GetAttachment( Attachment ) + if ( att ) then + Position = att.Pos + end + + end + + return Position + +end diff --git a/gamemodes/base/entities/effects/dof_node.lua b/gamemodes/base/entities/effects/dof_node.lua new file mode 100644 index 0000000..5f05ce7 --- /dev/null +++ b/gamemodes/base/entities/effects/dof_node.lua @@ -0,0 +1,55 @@ + +EFFECT.Mat = Material( "pp/dof" ) + +function EFFECT:Init( data ) + + table.insert( DOF_Ents, self ) + self.Scale = data:GetScale() + + local size = 32 + self:SetCollisionBounds( Vector( -size, -size, -size ), Vector( size, size, size ) ) + +end + +function EFFECT:Think() + + -- If the spacing or offset has changed we need to reconfigure our positions + local ply = LocalPlayer() + + self.spacing = DOF_SPACING * self.Scale + self.offset = DOF_OFFSET + + -- Just return if it hasn't + --if ( spacing == self.spacing && offset == self.offset ) then return true end + + local pos = ply:EyePos() + local fwd = ply:EyeAngles():Forward() + + if ( ply:GetViewEntity() != ply ) then + pos = ply:GetViewEntity():GetPos() + fwd = ply:GetViewEntity():GetForward() + end + + pos = pos + ( fwd * self.spacing ) + ( fwd * self.offset ) + + self:SetParent( nil ) + self:SetPos( pos ) + self:SetParent( ply ) + + -- We don't kill this, the pp effect should + return true + +end + +function EFFECT:Render() + + -- Note: UpdateScreenEffectTexture fucks up the water, RefractTexture is lower quality + render.UpdateRefractTexture() + --render.UpdateScreenEffectTexture() + + local SpriteSize = ( self.spacing + self.offset ) * 8 + + render.SetMaterial( self.Mat ) + render.DrawSprite( self:GetPos(), SpriteSize, SpriteSize, color_white ) + +end diff --git a/gamemodes/base/entities/effects/tooltracer.lua b/gamemodes/base/entities/effects/tooltracer.lua new file mode 100644 index 0000000..2db9712 --- /dev/null +++ b/gamemodes/base/entities/effects/tooltracer.lua @@ -0,0 +1,58 @@ + +EFFECT.Mat = Material( "effects/tool_tracer" ) + +function EFFECT:Init( data ) + + self.Position = data:GetStart() + self.WeaponEnt = data:GetEntity() + self.Attachment = data:GetAttachment() + + -- Keep the start and end pos - we're going to interpolate between them + self.StartPos = self:GetTracerShootPos( self.Position, self.WeaponEnt, self.Attachment ) + self.EndPos = data:GetOrigin() + + self.Alpha = 255 + self.Life = 0 + + self:SetRenderBoundsWS( self.StartPos, self.EndPos ) + +end + +function EFFECT:Think() + + self.Life = self.Life + FrameTime() * 4 + self.Alpha = 255 * ( 1 - self.Life ) + + return self.Life < 1 + +end + +function EFFECT:Render() + + if ( self.Alpha < 1 ) then return end + + render.SetMaterial( self.Mat ) + local texcoord = math.Rand( 0, 1 ) + + local norm = ( self.StartPos - self.EndPos ) * self.Life + + self.Length = norm:Length() + + for i = 1, 3 do + + render.DrawBeam( self.StartPos - norm, -- Start + self.EndPos, -- End + 8, -- Width + texcoord, -- Start tex coord + texcoord + self.Length / 128, -- End tex coord + color_white ) -- Color (optional) + end + + render.DrawBeam( self.StartPos, + self.EndPos, + 8, + texcoord, + texcoord + ( ( self.StartPos - self.EndPos ):Length() / 128 ), + Color( 255, 255, 255, 128 * ( 1 - self.Life ) ) ) + +end diff --git a/gamemodes/base/entities/entities/base_ai/cl_init.lua b/gamemodes/base/entities/entities/base_ai/cl_init.lua new file mode 100644 index 0000000..2d21ac6 --- /dev/null +++ b/gamemodes/base/entities/entities/base_ai/cl_init.lua @@ -0,0 +1,23 @@ + +include( "shared.lua" ) + +--[[--------------------------------------------------------- + Name: Draw + Desc: Draw it! +-----------------------------------------------------------]] +function ENT:Draw() + self:DrawModel() +end + +--[[--------------------------------------------------------- + Name: DrawTranslucent + Desc: Draw translucent +-----------------------------------------------------------]] +function ENT:DrawTranslucent() + + -- This is here just to make it backwards compatible. + -- You shouldn't really be drawing your model here unless it's translucent + + self:Draw() + +end diff --git a/gamemodes/base/entities/entities/base_ai/init.lua b/gamemodes/base/entities/entities/base_ai/init.lua new file mode 100644 index 0000000..5ad6db6 --- /dev/null +++ b/gamemodes/base/entities/entities/base_ai/init.lua @@ -0,0 +1,137 @@ + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) + +include( "shared.lua" ) +include( "schedules.lua" ) +include( "tasks.lua" ) + +-- Variables + +ENT.m_fMaxYawSpeed = 200 -- Max turning speed +ENT.m_iClass = CLASS_CITIZEN_REBEL -- NPC Class + +AccessorFunc( ENT, "m_iClass", "NPCClass" ) +AccessorFunc( ENT, "m_fMaxYawSpeed", "MaxYawSpeed" ) + +function ENT:Initialize() + + -- Some default calls to make the NPC function + self:SetModel( "models/alyx.mdl" ) + self:SetHullType( HULL_HUMAN ) + self:SetHullSizeNormal() + self:SetSolid( SOLID_BBOX ) + self:SetMoveType( MOVETYPE_STEP ) + self:CapabilitiesAdd( bit.bor( CAP_MOVE_GROUND, CAP_OPEN_DOORS, CAP_ANIMATEDFACE, CAP_SQUAD, CAP_USE_WEAPONS, CAP_DUCK, CAP_MOVE_SHOOT, CAP_TURN_HEAD, CAP_USE_SHOT_REGULATOR, CAP_AIM_GUN ) ) + + self:SetHealth( 100 ) + +end + +--[[--------------------------------------------------------- + Name: OnTakeDamage + Desc: Called when the NPC takes damage +-----------------------------------------------------------]] +function ENT:OnTakeDamage( dmginfo ) + +--[[ + Msg( tostring(dmginfo) .. "\n" ) + Msg( "Inflictor:\t" .. tostring(dmginfo:GetInflictor()) .. "\n" ) + Msg( "Attacker:\t" .. tostring(dmginfo:GetAttacker()) .. "\n" ) + Msg( "Damage:\t" .. tostring(dmginfo:GetDamage()) .. "\n" ) + Msg( "Base Damage:\t" .. tostring(dmginfo:GetBaseDamage()) .. "\n" ) + Msg( "Force:\t" .. tostring(dmginfo:GetDamageForce()) .. "\n" ) + Msg( "Position:\t" .. tostring(dmginfo:GetDamagePosition()) .. "\n" ) + Msg( "Reported Pos:\t" .. tostring(dmginfo:GetReportedPosition()) .. "\n" ) -- ?? +--]] + + -- return 1 + +end + + + +--[[--------------------------------------------------------- + Name: Use +-----------------------------------------------------------]] +function ENT:Use( activator, caller, type, value ) +end + +--[[--------------------------------------------------------- + Name: StartTouch +-----------------------------------------------------------]] +function ENT:StartTouch( entity ) +end + +--[[--------------------------------------------------------- + Name: EndTouch +-----------------------------------------------------------]] +function ENT:EndTouch( entity ) +end + +--[[--------------------------------------------------------- + Name: Touch +-----------------------------------------------------------]] +function ENT:Touch( entity ) +end + +--[[--------------------------------------------------------- + Name: GetRelationship + Return the relationship between this NPC and the + passed entity. If you don't return anything then + the default disposition will be used. +-----------------------------------------------------------]] +function ENT:GetRelationship( entity ) + + --return D_NU + +end + +--[[--------------------------------------------------------- + Name: ExpressionFinished + Called when an expression has finished. Duh. +-----------------------------------------------------------]] +function ENT:ExpressionFinished( strExp ) + +end + +--[[--------------------------------------------------------- + Name: OnChangeActivity +-----------------------------------------------------------]] +function ENT:OnChangeActivity( act ) + +end + +--[[--------------------------------------------------------- + Name: Think +-----------------------------------------------------------]] +function ENT:Think() + +end + +-- Called to update which sounds the NPC should be able to hear +function ENT:GetSoundInterests() + -- Hear thumper sound hints + -- return 256 +end + +-- Called when NPC's movement fails +function ENT:OnMovementFailed() +end + +-- Called when NPC's movement succeeds +function ENT:OnMovementComplete() +end + +-- Called when the NPC's active weapon changes +function ENT:OnActiveWeaponChanged( old, new ) +end + +--[[--------------------------------------------------------- + Name: GetAttackSpread + How good is the NPC with this weapon? Return the number + of degrees of inaccuracy for the NPC to use. +-----------------------------------------------------------]] +function ENT:GetAttackSpread( Weapon, Target ) + return 0.1 +end diff --git a/gamemodes/base/entities/entities/base_ai/schedules.lua b/gamemodes/base/entities/entities/base_ai/schedules.lua new file mode 100644 index 0000000..e3018d8 --- /dev/null +++ b/gamemodes/base/entities/entities/base_ai/schedules.lua @@ -0,0 +1,206 @@ + +--[[--------------------------------------------------------- + Name: RunAI - Called from the engine every 0.1 seconds +-----------------------------------------------------------]] +function ENT:RunAI( strExp ) + + -- If we're running an Engine Side behaviour + -- then return true and let it get on with it. + if ( self:IsRunningBehavior() ) then + return true + end + + -- If we're doing an engine schedule then return true + -- This makes it do the normal AI stuff. + if ( self:DoingEngineSchedule() ) then + return true + end + + -- If we're currently running a schedule then run it. + if ( self.CurrentSchedule ) then + self:DoSchedule( self.CurrentSchedule ) + end + + -- If we have no schedule (schedule is finished etc) + -- Then get the derived NPC to select what we should be doing + if ( !self.CurrentSchedule ) then + self:SelectSchedule() + end + + -- Do animation system + self:MaintainActivity() + +end + +--[[--------------------------------------------------------- + Name: SelectSchedule - Set the schedule we should be + playing right now. +-----------------------------------------------------------]] +function ENT:SelectSchedule( iNPCState ) + + self:SetSchedule( SCHED_IDLE_WANDER ) + +end + +--[[--------------------------------------------------------- + Name: StartSchedule - Start a Lua schedule. Not to be + confused with SetSchedule which starts an Engine based + schedule. +-----------------------------------------------------------]] +function ENT:StartSchedule( schedule ) + + self.CurrentSchedule = schedule + self.CurrentTaskID = 1 + self:SetTask( schedule:GetTask( 1 ) ) + +end + + + +--[[--------------------------------------------------------- + Name: DoSchedule - Runs a Lua schedule. +-----------------------------------------------------------]] +function ENT:DoSchedule( schedule ) + + if ( self.CurrentTask ) then + self:RunTask( self.CurrentTask ) + end + + if ( self:TaskFinished() ) then + self:NextTask( schedule ) + end + +end + + + +--[[--------------------------------------------------------- + Name: ScheduleFinished +-----------------------------------------------------------]] +function ENT:ScheduleFinished() + + self.CurrentSchedule = nil + self.CurrentTask = nil + self.CurrentTaskID = nil + +end + + + +--[[--------------------------------------------------------- + Name: DoSchedule - Set the current task. +-----------------------------------------------------------]] +function ENT:SetTask( task ) + + self.CurrentTask = task + self.bTaskComplete = false + self.TaskStartTime = CurTime() + + self:StartTask( self.CurrentTask ) + +end + + + +--[[--------------------------------------------------------- + Name: NextTask - Start the next task in specific schedule. +-----------------------------------------------------------]] +function ENT:NextTask( schedule ) + + -- Increment task id + self.CurrentTaskID = self.CurrentTaskID + 1 + + -- If this was the last task then finish up. + if ( self.CurrentTaskID > schedule:NumTasks() ) then + + self:ScheduleFinished( schedule ) + return + + end + + -- Switch to next task + self:SetTask( schedule:GetTask( self.CurrentTaskID ) ) + +end + +--[[--------------------------------------------------------- + Name: StartTask - called once on starting task +-----------------------------------------------------------]] +function ENT:StartTask( task ) + task:Start( self ) +end + +--[[--------------------------------------------------------- + Name: RunTask - called every think on running task. + The actual task function should tell us when + the task is finished. +-----------------------------------------------------------]] +function ENT:RunTask( task ) + task:Run( self ) +end + +--[[--------------------------------------------------------- + Name: TaskTime - Returns how many seconds we've been + doing this current task +-----------------------------------------------------------]] +function ENT:TaskTime() + return CurTime() - self.TaskStartTime +end + +--[[--------------------------------------------------------- + Name: OnTaskComplete - Called from the engine when + TaskComplete is called. This allows us to move + onto the next task - even when TaskComplete was + called from an engine side task. +-----------------------------------------------------------]] +function ENT:OnTaskComplete() + + self.bTaskComplete = true + +end + +--[[--------------------------------------------------------- + Name: TaskFinished - Returns true if the current + running Task is finished. +-----------------------------------------------------------]] +function ENT:TaskFinished() + return self.bTaskComplete +end + +--[[--------------------------------------------------------- + Name: StartTask + Start the task. You can use this to override engine + side tasks. Return true to not run default stuff. +-----------------------------------------------------------]] +function ENT:StartEngineTask( iTaskID, TaskData ) +end + +--[[--------------------------------------------------------- + Name: RunTask + Run the task. You can use this to override engine + side tasks. Return true to not run default stuff. +-----------------------------------------------------------]] +function ENT:RunEngineTask( iTaskID, TaskData ) +end + +--[[--------------------------------------------------------- + These functions handle the engine schedules + When an engine schedule is set the engine calls StartEngineSchedule + Then when it's finished it calls EngineScheduleFinishHelp me decide +-----------------------------------------------------------]] +function ENT:StartEngineSchedule( scheduleID ) self:ScheduleFinished() self.bDoingEngineSchedule = true end +function ENT:EngineScheduleFinish() self.bDoingEngineSchedule = nil end +function ENT:DoingEngineSchedule() return self.bDoingEngineSchedule end + +function ENT:OnCondition( iCondition ) + + --Msg( self, " Condition: ", iCondition, " - ", self:ConditionName(iCondition), "\n" ) + +end + +function ENT:TranslateActivity( act ) + + -- Return a value to translate the activity to a new one + -- if ( act == ACT_WALK ) then return ACT_RUN end + +end diff --git a/gamemodes/base/entities/entities/base_ai/shared.lua b/gamemodes/base/entities/entities/base_ai/shared.lua new file mode 100644 index 0000000..cdcf8b9 --- /dev/null +++ b/gamemodes/base/entities/entities/base_ai/shared.lua @@ -0,0 +1,44 @@ + +ENT.Base = "base_entity" +ENT.Type = "ai" + +ENT.PrintName = "Base SNPC" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.AutomaticFrameAdvance = false + +--[[--------------------------------------------------------- + Name: OnRemove + Desc: Called just before entity is deleted +-----------------------------------------------------------]] +function ENT:OnRemove() +end + +--[[--------------------------------------------------------- + Name: PhysicsCollide + Desc: Called when physics collides. The table contains + data on the collision +-----------------------------------------------------------]] +function ENT:PhysicsCollide( data, physobj ) +end + +--[[--------------------------------------------------------- + Name: PhysicsUpdate + Desc: Called to update the physics .. or something. +-----------------------------------------------------------]] +function ENT:PhysicsUpdate( physobj ) +end + +--[[--------------------------------------------------------- + Name: SetAutomaticFrameAdvance + Desc: If you're not using animation you should turn this + off - it will save lots of bandwidth. +-----------------------------------------------------------]] +function ENT:SetAutomaticFrameAdvance( bUsingAnim ) + + self.AutomaticFrameAdvance = bUsingAnim + +end diff --git a/gamemodes/base/entities/entities/base_ai/tasks.lua b/gamemodes/base/entities/entities/base_ai/tasks.lua new file mode 100644 index 0000000..1bc0ee2 --- /dev/null +++ b/gamemodes/base/entities/entities/base_ai/tasks.lua @@ -0,0 +1,80 @@ + +--[[--------------------------------------------------------- + Task: PlaySequence + + Accepts: + + data.ID - sequence id + data.Name - sequence name (Must provide either id or name) + data.Wait - Optional. Should we wait for sequence to finish + data.Speed - Optional. Playback speed of sequence + data.Loop - Optional. Should the sequence be looped + +-----------------------------------------------------------]] +function ENT:TaskStart_PlaySequence( data ) + + local SequenceID = data.ID + + if ( data.Name ) then SequenceID = self:LookupSequence( data.Name ) end + + self:ResetSequence( SequenceID ) + self:SetNPCState( NPC_STATE_SCRIPT ) + + local Duration = self:SequenceDuration() + + if ( data.Speed && data.Speed > 0 ) then + + SequenceID = self:SetPlaybackRate( data.Speed ) + Duration = Duration / data.Speed + + end + + self.TaskSequenceEnd = CurTime() + Duration + self.Loop = data.Loop or false + +end + +function ENT:Task_PlaySequence( data ) + + -- Wait until sequence is finished + if ( CurTime() < self.TaskSequenceEnd or self.Loop ) then return end + + self:TaskComplete() + self:SetNPCState( NPC_STATE_NONE ) + + -- Clean up + self.TaskSequenceEnd = nil + +end + +--[[--------------------------------------------------------- + Task: FindEnemy + + Accepts: + + data.ID - sequence id + data.Name - sequence name (Must provide either id or name) + data.Wait - Optional. Should we wait for sequence to finish + data.Speed - Optional. Playback speed of sequence + +-----------------------------------------------------------]] +function ENT:TaskStart_FindEnemy( data ) + + for k, v in ipairs( ents.FindInSphere( self:GetPos(), data.Radius or 512 ) ) do + + if ( v:IsValid() && v != self && v:GetClass() == data.Class ) then + + self:SetEnemy( v, true ) + self:UpdateEnemyMemory( v, v:GetPos() ) + self:TaskComplete() + return + end + + end + + self:SetEnemy( NULL ) + +end + +function ENT:Task_FindEnemy( data ) +end diff --git a/gamemodes/base/entities/entities/base_anim.lua b/gamemodes/base/entities/entities/base_anim.lua new file mode 100644 index 0000000..e6d4a1c --- /dev/null +++ b/gamemodes/base/entities/entities/base_anim.lua @@ -0,0 +1,104 @@ + +AddCSLuaFile() + +ENT.Base = "base_entity" +ENT.Type = "anim" + +ENT.PrintName = "" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +-- Defaulting this to OFF. This will automatically save bandwidth +-- on stuff that is already out there, but might break a few things +-- that are out there. I'm choosing to break those things because +-- there are a lot less of them that are actually using the animtime + +ENT.AutomaticFrameAdvance = false + +function ENT:SetAutomaticFrameAdvance( bUsingAnim ) + self.AutomaticFrameAdvance = bUsingAnim +end + + +function ENT:OnRemove() +end + + +function ENT:PhysicsCollide( data, physobj ) +end + + +function ENT:PhysicsUpdate( physobj ) +end + +if ( CLIENT ) then + + function ENT:Draw( flags ) + + self:DrawModel( flags ) + + end + + function ENT:DrawTranslucent( flags ) + + -- This is here just to make it backwards compatible. + -- You shouldn't really be drawing your model here unless it's translucent + + self:Draw( flags ) + + end + +end + +if ( SERVER ) then + + function ENT:OnTakeDamage( dmginfo ) + + --[[ + Msg( tostring(dmginfo) .. "\n" ) + Msg( "Inflictor:\t" .. tostring(dmginfo:GetInflictor()) .. "\n" ) + Msg( "Attacker:\t" .. tostring(dmginfo:GetAttacker()) .. "\n" ) + Msg( "Damage:\t" .. tostring(dmginfo:GetDamage()) .. "\n" ) + Msg( "Base Damage:\t" .. tostring(dmginfo:GetBaseDamage()) .. "\n" ) + Msg( "Force:\t" .. tostring(dmginfo:GetDamageForce()) .. "\n" ) + Msg( "Position:\t" .. tostring(dmginfo:GetDamagePosition()) .. "\n" ) + Msg( "Reported Pos:\t" .. tostring(dmginfo:GetReportedPosition()) .. "\n" ) -- ?? + --]] + + end + + + function ENT:Use( activator, caller, type, value ) + end + + + function ENT:StartTouch( entity ) + end + + + function ENT:EndTouch( entity ) + end + + + function ENT:Touch( entity ) + end + + --[[--------------------------------------------------------- + Name: Simulate + Desc: Controls/simulates the physics on the entity. + Officially the most complicated callback in the whole mod. + Returns 3 variables.. + 1. A SIM_ enum + 2. A vector representing the linear acceleration/force + 3. A vector represending the angular acceleration/force + If you're doing nothing you can return SIM_NOTHING + Note that you need to call ent:StartMotionController to tell the entity + to start calling this function.. + -----------------------------------------------------------]] + function ENT:PhysicsSimulate( phys, deltatime ) + return SIM_NOTHING + end + +end diff --git a/gamemodes/base/entities/entities/base_brush.lua b/gamemodes/base/entities/entities/base_brush.lua new file mode 100644 index 0000000..4eb4274 --- /dev/null +++ b/gamemodes/base/entities/entities/base_brush.lua @@ -0,0 +1,56 @@ + +ENT.Base = "base_entity" +ENT.Type = "brush" + +--[[--------------------------------------------------------- + Name: Initialize +-----------------------------------------------------------]] +function ENT:Initialize() +end + +--[[--------------------------------------------------------- + Name: StartTouch +-----------------------------------------------------------]] +function ENT:StartTouch( entity ) +end + +--[[--------------------------------------------------------- + Name: EndTouch +-----------------------------------------------------------]] +function ENT:EndTouch( entity ) +end + +--[[--------------------------------------------------------- + Name: Touch +-----------------------------------------------------------]] +function ENT:Touch( entity ) +end + +--[[--------------------------------------------------------- + Name: PassesTriggerFilters + Desc: Return true if this object should trigger us +-----------------------------------------------------------]] +function ENT:PassesTriggerFilters( entity ) + return true +end + +--[[--------------------------------------------------------- + Name: KeyValue + Desc: Called when a keyvalue is added to us +-----------------------------------------------------------]] +function ENT:KeyValue( key, value ) +end + +--[[--------------------------------------------------------- + Name: Think + Desc: Entity's think function. +-----------------------------------------------------------]] +function ENT:Think() +end + +--[[--------------------------------------------------------- + Name: OnRemove + Desc: Called just before entity is deleted +-----------------------------------------------------------]] +function ENT:OnRemove() +end diff --git a/gamemodes/base/entities/entities/base_entity/cl_init.lua b/gamemodes/base/entities/entities/base_entity/cl_init.lua new file mode 100644 index 0000000..41327c7 --- /dev/null +++ b/gamemodes/base/entities/entities/base_entity/cl_init.lua @@ -0,0 +1,24 @@ + +include( "shared.lua" ) + +--[[--------------------------------------------------------- + Name: Initialize + Desc: First function called. Use to set up your entity +-----------------------------------------------------------]] +function ENT:Initialize() +end + + +--[[--------------------------------------------------------- + Name: Think + Desc: Client Think - called every frame +-----------------------------------------------------------]] +function ENT:Think() +end + +--[[--------------------------------------------------------- + Name: OnRestore + Desc: Called immediately after a "load" +-----------------------------------------------------------]] +function ENT:OnRestore() +end diff --git a/gamemodes/base/entities/entities/base_entity/init.lua b/gamemodes/base/entities/entities/base_entity/init.lua new file mode 100644 index 0000000..5eb772a --- /dev/null +++ b/gamemodes/base/entities/entities/base_entity/init.lua @@ -0,0 +1,77 @@ + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "shared.lua" ) + +include( "shared.lua" ) +include( "outputs.lua" ) + +--[[--------------------------------------------------------- + Name: Initialize + Desc: First function called. Use to set up your entity +-----------------------------------------------------------]] +function ENT:Initialize() +end + +--[[--------------------------------------------------------- + Name: KeyValue + Desc: Called when a keyvalue is added to us +-----------------------------------------------------------]] +function ENT:KeyValue( key, value ) +end + +--[[--------------------------------------------------------- + Name: OnRestore + Desc: The game has just been reloaded. This is usually the right place + to call the GetNW* functions to restore the script's values. +-----------------------------------------------------------]] +function ENT:OnRestore() +end + +--[[--------------------------------------------------------- + Name: AcceptInput + Desc: Accepts input, return true to override/accept input +-----------------------------------------------------------]] +function ENT:AcceptInput( name, activator, caller, data ) + return false +end + +--[[--------------------------------------------------------- + Name: UpdateTransmitState + Desc: Set the transmit state +-----------------------------------------------------------]] +function ENT:UpdateTransmitState() + return TRANSMIT_PVS +end + +--[[--------------------------------------------------------- + Name: Think + Desc: Entity's think function. +-----------------------------------------------------------]] +function ENT:Think() +end + +-- +-- Default generic spawn function +-- So you don't have to add one your entitie unless you want to. +-- +function ENT:SpawnFunction( ply, tr, ClassName ) + + if ( !tr.Hit ) then return end + + local SpawnPos = tr.HitPos + tr.HitNormal * 10 + local SpawnAng = ply:EyeAngles() + SpawnAng.p = 0 + SpawnAng.y = SpawnAng.y + 180 + + local ent = ents.Create( ClassName ) + ent:SetCreator( ply ) + ent:SetPos( SpawnPos ) + ent:SetAngles( SpawnAng ) + ent:Spawn() + ent:Activate() + + ent:DropToFloor() + + return ent + +end diff --git a/gamemodes/base/entities/entities/base_entity/outputs.lua b/gamemodes/base/entities/entities/base_entity/outputs.lua new file mode 100644 index 0000000..bebffa4 --- /dev/null +++ b/gamemodes/base/entities/entities/base_entity/outputs.lua @@ -0,0 +1,123 @@ + +-- This is called from ENT:KeyValue(key,value) to store the output from +-- the map, it could also be called from ENT:AcceptInput I think, so if +-- ent_fire addoutput is used, we can store that too (that hasn't been +-- tested though). +-- Usage: self:StoreOutput("",",,,,") +-- If called from ENT:KeyValue, then the first parameter is the key, and +-- the second is value. +function ENT:StoreOutput( name, info ) + + -- Newer Source Engine games use this symbol as a delimiter + local rawData = string.Explode( "\x1B", info ) + if ( #rawData < 2 ) then + rawData = string.Explode( ",", info ) + end + + local Output = {} + Output.entities = rawData[1] or "" + Output.input = rawData[2] or "" + Output.param = rawData[3] or "" + Output.delay = tonumber( rawData[4] ) or 0 + Output.times = tonumber( rawData[5] ) or -1 + + self.m_tOutputs = self.m_tOutputs or {} + self.m_tOutputs[ name ] = self.m_tOutputs[ name ] or {} + + table.insert( self.m_tOutputs[ name ], Output ) + +end + +-- This works like SetNetworkKeyValue, call it from ENT:KeyValue and it'll +-- return true if it successfully added an output from the passed KV pair. +function ENT:AddOutputFromKeyValue( key, value ) + + if ( key:sub( 1, 2 ) == "On" ) then + + self:StoreOutput( key, value ) + return true + + end + + return false + +end + +-- Call from ENT:AcceptInput and it'll return true if the input was AddOutput +-- and if it successfully added an output. +-- Note that to be strictly compatible with Source entities, AddOutput should +-- allow setting arbitrary keyvalues, but this implementation does not. +function ENT:AddOutputFromAcceptInput( name, value ) + + if ( name ~= "AddOutput" ) then + + return false + + end + + local pos = string.find( value, " ", 1, true ) + if ( pos == nil ) then + + return false + + end + + name, value = value:sub( 1, pos - 1 ), value:sub( pos + 1 ) + + -- This is literally KeyValue but as an input and with ,s as :s. + value = value:gsub( ":", "," ) + + return self:AddOutputFromKeyValue( name, value ) + +end + +-- Nice helper function, this does all the work. Returns false if the +-- output should be removed from the list. +local function FireSingleOutput( output, this, activator, data ) + + if ( output.times == 0 ) then return false end + + local entitiesToFire = {} + + if ( output.entities == "!activator" ) then + entitiesToFire = { activator } + elseif ( output.entities == "!self" ) then + entitiesToFire = { this } + elseif ( output.entities == "!player" ) then + entitiesToFire = player.GetAll() + else + entitiesToFire = ents.FindByName( output.entities ) + end + + for _, ent in ipairs( entitiesToFire ) do + ent:Fire( output.input, data or output.param, output.delay, activator, this ) + end + + if ( output.times ~= -1 ) then + output.times = output.times - 1 + end + + return ( output.times > 0 ) || ( output.times == -1 ) + +end + +-- This function is used to trigger an output. +function ENT:TriggerOutput( name, activator, data ) + + if ( !self.m_tOutputs ) then return end + if ( !self.m_tOutputs[ name ] ) then return end + + local OutputList = self.m_tOutputs[ name ] + + for idx = #OutputList, 1, -1 do + + if ( OutputList[ idx ] and !FireSingleOutput( OutputList[ idx ], self, activator, data ) ) then + + -- Shift the indexes so this loop doesn't fail later + table.remove( self.m_tOutputs[ name ], idx ) + + end + + end + +end diff --git a/gamemodes/base/entities/entities/base_entity/shared.lua b/gamemodes/base/entities/entities/base_entity/shared.lua new file mode 100644 index 0000000..a31a702 --- /dev/null +++ b/gamemodes/base/entities/entities/base_entity/shared.lua @@ -0,0 +1,6 @@ + +ENT.Base = "base_entity" +ENT.Type = "anim" + +ENT.Spawnable = false +ENT.AdminOnly = false diff --git a/gamemodes/base/entities/entities/base_filter.lua b/gamemodes/base/entities/entities/base_filter.lua new file mode 100644 index 0000000..f4a226b --- /dev/null +++ b/gamemodes/base/entities/entities/base_filter.lua @@ -0,0 +1,44 @@ + +ENT.Base = "base_entity" +ENT.Type = "filter" + +--[[--------------------------------------------------------- + Name: Initialize +-----------------------------------------------------------]] +function ENT:Initialize() +end + +--[[--------------------------------------------------------- + Name: KeyValue + Desc: Called when a keyvalue is added to us +-----------------------------------------------------------]] +function ENT:KeyValue( key, value ) +end + +--[[--------------------------------------------------------- + Name: Think + Desc: Entity's think function. +-----------------------------------------------------------]] +function ENT:Think() +end + +--[[--------------------------------------------------------- + Name: OnRemove + Desc: Called just before entity is deleted +-----------------------------------------------------------]] +function ENT:OnRemove() +end + +--[[--------------------------------------------------------- + Name: PassesFilter +-----------------------------------------------------------]] +function ENT:PassesFilter( trigger, ent ) + return true +end + +--[[--------------------------------------------------------- + Name: PassesDamageFilter +-----------------------------------------------------------]] +function ENT:PassesDamageFilter( dmg ) + return true +end diff --git a/gamemodes/base/entities/entities/base_nextbot/shared.lua b/gamemodes/base/entities/entities/base_nextbot/shared.lua new file mode 100644 index 0000000..0b00fc6 --- /dev/null +++ b/gamemodes/base/entities/entities/base_nextbot/shared.lua @@ -0,0 +1,54 @@ + +AddCSLuaFile() + +ENT.Base = "base_entity" +ENT.PrintName = "" +ENT.Author = "" +ENT.Contact = "" +ENT.Purpose = "" +ENT.Instructions = "" + +ENT.Type = "nextbot" + +function ENT:Initialize() +end + +if ( SERVER ) then + + -- + -- All of the AI logic is serverside - so we derive it from a + -- specialized class on the server. + -- + include( "sv_nextbot.lua" ) + +else + + --[[--------------------------------------------------------- + Name: Draw + Desc: Draw it! + -----------------------------------------------------------]] + function ENT:Draw() + self:DrawModel() + end + + --[[--------------------------------------------------------- + Name: DrawTranslucent + Desc: Draw translucent + -----------------------------------------------------------]] + function ENT:DrawTranslucent() + + -- This is here just to make it backwards compatible. + -- You shouldn't really be drawing your model here unless it's translucent + + self:Draw() + + end + + --[[--------------------------------------------------------- + Name: FireAnimationEvent + Desc: Called when an animation event is fired. Return true to suppress + -----------------------------------------------------------]] + function ENT:FireAnimationEvent( pos, ang, event, options ) + end + +end diff --git a/gamemodes/base/entities/entities/base_nextbot/sv_nextbot.lua b/gamemodes/base/entities/entities/base_nextbot/sv_nextbot.lua new file mode 100644 index 0000000..ca7e56c --- /dev/null +++ b/gamemodes/base/entities/entities/base_nextbot/sv_nextbot.lua @@ -0,0 +1,415 @@ + +-- +-- Name: NEXTBOT:BehaveStart +-- Desc: Called to initialize the behaviour.\n\n You shouldn't override this - it's used to kick off the coroutine that runs the bot's behaviour. \n\nThis is called automatically when the NPC is created, there should be no need to call it manually. +-- Arg1: +-- Ret1: +-- +function ENT:BehaveStart() + + self.BehaveThread = coroutine.create( function() self:RunBehaviour() end ) + +end + +function ENT:RunBehaviour() +end + +-- +-- Name: NEXTBOT:BehaveUpdate +-- Desc: Called to update the bot's behaviour +-- Arg1: number|interval|How long since the last update +-- Ret1: +-- +function ENT:BehaveUpdate( fInterval ) + + if ( !self.BehaveThread ) then return end + + -- + -- Give a silent warning to developers if RunBehaviour has returned + -- + if ( coroutine.status( self.BehaveThread ) == "dead" ) then + + self.BehaveThread = nil + Msg( self, " Warning: ENT:RunBehaviour() has finished executing\n" ) + + return + + end + + -- + -- Continue RunBehaviour's execution + -- + local ok, message = coroutine.resume( self.BehaveThread ) + if ( ok == false ) then + + self.BehaveThread = nil + ErrorNoHalt( self, " Error: ", message, "\n" ) + + end + +end + +-- +-- Name: NEXTBOT:BodyUpdate +-- Desc: Called to update the bot's animation +-- Arg1: +-- Ret1: +-- +function ENT:BodyUpdate() + + local act = self:GetActivity() + + -- + -- This helper function does a lot of useful stuff for us. + -- It sets the bot's move_x move_y pose parameters, sets their animation speed relative to the ground speed, and calls FrameAdvance. + -- + if ( act == ACT_RUN || act == ACT_WALK ) then + + self:BodyMoveXY() + + -- BodyMoveXY() already calls FrameAdvance, calling it twice will affect animation playback, specifically on layers + return + + end + + -- + -- If we're not walking or running we probably just want to update the anim system + -- + self:FrameAdvance() + +end + +-- +-- Name: NEXTBOT:OnLeaveGround +-- Desc: Called when the bot's feet leave the ground - for whatever reason +-- Arg1: Entity|ent|Entity that the NextBot "jumped" from +-- Ret1: +-- +function ENT:OnLeaveGround( ent ) + + --MsgN( "OnLeaveGround", ent ) + +end + +-- +-- Name: NEXTBOT:OnLeaveGround +-- Desc: Called when the bot's feet return to the ground +-- Arg1: Entity|ent|Entity that the NextBot landed on +-- Ret1: +-- +function ENT:OnLandOnGround( ent ) + + --MsgN( "OnLandOnGround", ent ) + +end + +-- +-- Name: NEXTBOT:OnStuck +-- Desc: Called when the bot thinks it is stuck +-- Arg1: +-- Ret1: +-- +function ENT:OnStuck() + + --MsgN( "OnStuck" ) + +end + +-- +-- Name: NEXTBOT:OnUnStuck +-- Desc: Called when the bot thinks it is un-stuck +-- Arg1: +-- Ret1: +-- +function ENT:OnUnStuck() + + --MsgN( "OnUnStuck" ) + +end + +-- +-- Name: NEXTBOT:OnTakeDamage +-- Desc: Called when the bot is about to take damage +-- Arg1: CTakeDamageInfo|info|damage info +-- Ret1: number|how much damage was taken, prevents default damage code from running +-- +function ENT:OnTakeDamage( damageinfo ) + + -- return 0 + +end + +-- +-- Name: NEXTBOT:OnInjured +-- Desc: Called when the bot gets hurt +-- Arg1: CTakeDamageInfo|info|damage info +-- Ret1: +-- +function ENT:OnInjured( damageinfo ) + +end + +-- +-- Name: NEXTBOT:OnKilled +-- Desc: Called when the bot gets killed +-- Arg1: CTakeDamageInfo|info|damage info +-- Ret1: +-- +function ENT:OnKilled( dmginfo ) + + hook.Run( "OnNPCKilled", self, dmginfo:GetAttacker(), dmginfo:GetInflictor() ) + + self:BecomeRagdoll( dmginfo ) + +end + +-- +-- Name: NEXTBOT:OnOtherKilled +-- Desc: Called when someone else or something else has been killed +-- Arg1: Entity|victim|entity that was killed +-- Arg2: CTakeDamageInfo|info|damage info +-- Ret1: +-- +function ENT:OnOtherKilled( victim, info ) + + --MsgN( "OnOtherKilled", victim, info ) + +end + +function ENT:OnContact( ent ) + + --MsgN( "OnContact", ent ) + +end + +function ENT:OnIgnite() + + --MsgN( "OnIgnite" ) + +end + +function ENT:OnNavAreaChanged( old, new ) + + --MsgN( "OnNavAreaChanged", old, new ) + +end + +-- +-- Name: NextBot:FindSpots +-- Desc: Returns a table of hiding spots. +-- Arg1: table|specs|This table should contain the search info.\n\n * 'type' - the type (either 'hiding')\n * 'pos' - the position to search.\n * 'radius' - the radius to search.\n * 'stepup' - the highest step to step up.\n * 'stepdown' - the highest we can step down without being hurt. +-- Ret1: table|An unsorted table of tables containing\n * 'vector' - the position of the hiding spot\n * 'distance' - the distance to that position +-- +function ENT:FindSpots( tbl ) + + local tbl = tbl or {} + + tbl.pos = tbl.pos or self:WorldSpaceCenter() + tbl.radius = tbl.radius or 1000 + tbl.stepdown = tbl.stepdown or 20 + tbl.stepup = tbl.stepup or 20 + tbl.type = tbl.type or 'hiding' + + -- Use a path to find the length + local path = Path( "Follow" ) + + -- Find a bunch of areas within this distance + local areas = navmesh.Find( tbl.pos, tbl.radius, tbl.stepdown, tbl.stepup ) + + local found = {} + + -- In each area + for _, area in ipairs( areas ) do + + -- get the spots + local spots + + if ( tbl.type == 'hiding' ) then spots = area:GetHidingSpots() end + + for k, vec in ipairs( spots ) do + + -- Work out the length, and add them to a table + path:Invalidate() + + path:Compute( self, vec ) -- TODO: This is bullshit - it's using 'self.pos' not tbl.pos + + table.insert( found, { vector = vec, distance = path:GetLength() } ) + + end + + end + + return found + +end + +-- +-- Name: NextBot:FindSpot +-- Desc: Like FindSpots but only returns a vector +-- Arg1: string|type|Either "random", "near", "far" +-- Arg2: table|options|A table containing a bunch of tweakable options. See the function definition for more details +-- Ret1: vector|If it finds a spot it will return a vector. If not it will return nil. +-- +function ENT:FindSpot( type, options ) + + local spots = self:FindSpots( options ) + if ( !spots || #spots == 0 ) then return end + + if ( type == "near" ) then + + table.SortByMember( spots, "distance", true ) + return spots[1].vector + + end + + if ( type == "far" ) then + + table.SortByMember( spots, "distance", false ) + return spots[1].vector + + end + + -- random + return spots[ math.random( 1, #spots ) ].vector + +end + +-- +-- Name: NextBot:HandleStuck +-- Desc: Called from Lua when the NPC is stuck. This should only be called from the behaviour coroutine - so if you want to override this function and do something special that yields - then go for it.\n\nYou should always call self.loco:ClearStuck() in this function to reset the stuck status - so it knows it's unstuck. +-- Arg1: +-- Ret1: +-- +function ENT:HandleStuck() + + -- + -- Clear the stuck status + -- + self.loco:ClearStuck() + +end + +-- +-- Name: NextBot:MoveToPos +-- Desc: To be called in the behaviour coroutine only! Will yield until the bot has reached the goal or is stuck +-- Arg1: Vector|pos|The position we want to get to +-- Arg2: table|options|A table containing a bunch of tweakable options. See the function definition for more details +-- Ret1: string|Either "failed", "stuck", "timeout" or "ok" - depending on how the NPC got on +-- +function ENT:MoveToPos( pos, options ) + + local options = options or {} + + local path = Path( "Follow" ) + path:SetMinLookAheadDistance( options.lookahead or 300 ) + path:SetGoalTolerance( options.tolerance or 20 ) + path:Compute( self, pos ) + + if ( !path:IsValid() ) then return "failed" end + + while ( path:IsValid() ) do + + path:Update( self ) + + -- Draw the path (only visible on listen servers or single player) + if ( options.draw ) then + path:Draw() + end + + -- If we're stuck then call the HandleStuck function and abandon + if ( self.loco:IsStuck() ) then + + self:HandleStuck() + + return "stuck" + + end + + -- + -- If they set maxage on options then make sure the path is younger than it + -- + if ( options.maxage ) then + if ( path:GetAge() > options.maxage ) then return "timeout" end + end + + -- + -- If they set repath then rebuild the path every x seconds + -- + if ( options.repath ) then + if ( path:GetAge() > options.repath ) then path:Compute( self, pos ) end + end + + coroutine.yield() + + end + + return "ok" + +end + +-- +-- Name: NextBot:PlaySequenceAndWait +-- Desc: To be called in the behaviour coroutine only! Plays an animation sequence and waits for it to end before returning. +-- Arg1: string|name|The sequence name +-- Arg2: number|the speed (default 1) +-- Ret1: +-- +function ENT:PlaySequenceAndWait( name, speed ) + + local len = self:SetSequence( name ) + speed = speed or 1 + + self:ResetSequenceInfo() + self:SetCycle( 0 ) + self:SetPlaybackRate( speed ) + + -- wait for it to finish + coroutine.wait( len / speed ) + +end + +-- +-- Name: NEXTBOT:Use +-- Desc: Called when a player 'uses' the entity +-- Arg1: entity|activator|The entity that activated the use +-- Arg2: entity|called|The entity that called the use +-- Arg3: number|type|The type of use (USE_ON, USE_OFF, USE_TOGGLE, USE_SET) +-- Arg4: number|value|Any passed value +-- Ret1: +-- +function ENT:Use( activator, caller, type, value ) +end + +-- +-- Name: NEXTBOT:Think +-- Desc: Called periodically +-- Arg1: +-- Ret1: +-- +function ENT:Think() +end + +-- +-- Name: NEXTBOT:HandleAnimEvent +-- Desc: Called for serverside events +-- +function ENT:HandleAnimEvent( event, eventtime, cycle, typee, options ) +end + +-- +-- Name: NEXTBOT:OnTraceAttack +-- Desc: Called serverside when the nextbot is attacked +-- +function ENT:OnTraceAttack( dmginfo, dir, trace ) + + hook.Run( "ScaleNPCDamage", self, trace.HitGroup, dmginfo ) + +end + +-- Called when we see a player or another nextbot +function ENT:OnEntitySight( subject ) +end + +-- Called when we see lose sight of a player or a nextbot we saw earlier +function ENT:OnEntitySightLost( subject ) +end diff --git a/gamemodes/base/entities/entities/base_point.lua b/gamemodes/base/entities/entities/base_point.lua new file mode 100644 index 0000000..1bf1f8c --- /dev/null +++ b/gamemodes/base/entities/entities/base_point.lua @@ -0,0 +1,45 @@ + +ENT.Base = "base_entity" +ENT.Type = "point" + +--[[--------------------------------------------------------- + Name: Initialize + Desc: First function called. Use to set up your entity +-----------------------------------------------------------]] +function ENT:Initialize() +end + +--[[--------------------------------------------------------- + Name: KeyValue + Desc: Called when a keyvalue is added to us +-----------------------------------------------------------]] +function ENT:KeyValue( key, value ) +end + +--[[--------------------------------------------------------- + Name: Think + Desc: Entity's think function. +-----------------------------------------------------------]] +function ENT:Think() +end + +-- +-- Name: OnRemove +-- Desc: Called just before entity is deleted +-- +function ENT:OnRemove() +end + +-- +-- UpdateTransmitState +-- +function ENT:UpdateTransmitState() + + -- + -- The default behaviour for point entities is to not be networked. + -- If you're deriving an entity and want it to appear clientside, override this + -- TRANSMIT_ALWAYS = always send, TRANSMIT_PVS = send if in PVS + -- + return TRANSMIT_NEVER + +end diff --git a/gamemodes/base/entities/entities/env_skypaint.lua b/gamemodes/base/entities/entities/env_skypaint.lua new file mode 100644 index 0000000..da4d39d --- /dev/null +++ b/gamemodes/base/entities/entities/env_skypaint.lua @@ -0,0 +1,145 @@ + +AddCSLuaFile() + +ENT.Type = "point" +ENT.DisableDuplicator = true + +-- +-- Make this entity always networked +-- +function ENT:UpdateTransmitState() return TRANSMIT_ALWAYS end + +-- +-- Networked / Saved Data +-- +function ENT:SetupDataTables() + + self:NetworkVar( "Vector", 0, "TopColor", { KeyName = "topcolor", Edit = { type = "VectorColor", category = "Main", order = 1 } } ) + self:NetworkVar( "Vector", 1, "BottomColor", { KeyName = "bottomcolor", Edit = { type = "VectorColor", category = "Main", title = "Color Bottom", order = 2 } } ) + self:NetworkVar( "Float", 0, "FadeBias", { KeyName = "fadebias", Edit = { type = "Float", category = "Main", min = 0, max = 1, order = 3 } } ) + + self:NetworkVar( "Float", 4, "SunSize", { KeyName = "sunsize", Edit = { type = "Float", min = 0, max = 10, category = "Sun" } } ) + self:NetworkVar( "Vector", 2, "SunNormal", { KeyName = "sunnormal" } ) -- No editing this - it's for coders only + self:NetworkVar( "Vector", 3, "SunColor", { KeyName = "suncolor", Edit = { type = "VectorColor", category = "Sun" } } ) + + self:NetworkVar( "Float", 2, "DuskScale", { KeyName = "duskscale", Edit = { type = "Float", min = 0, max = 1, category = "Dusk" } } ) + self:NetworkVar( "Float", 3, "DuskIntensity", { KeyName = "duskintensity", Edit = { type = "Float", min = 0, max = 10, category = "Dusk" } } ) + self:NetworkVar( "Vector", 4, "DuskColor", { KeyName = "duskcolor", Edit = { type = "VectorColor", category = "Dusk" } } ) + + self:NetworkVar( "Bool", 0, "DrawStars", { KeyName = "drawstars", Edit = { type = "Boolean", category = "Stars", order = 10 } } ) + self:NetworkVar( "String", 0, "StarTexture", { KeyName = "startexture", Edit = { type = "Texture", group = "Stars", category = "Stars", order = 11 } } ) + + self:NetworkVar( "Int", 0, "StarLayers", { KeyName = "starlayers", Edit = { type = "Int", min = 1, max = 3, category = "Stars", order = 12 } } ) + self:NetworkVarElement( "Angle", 0, 'p', "StarScale", { KeyName = "starscale", Edit = { type = "Float", min = 0, max = 5, category = "Stars", order = 13 } } ) + self:NetworkVarElement( "Angle", 0, 'y', "StarFade", { KeyName = "starfade", Edit = { type = "Float", min = 0, max = 5, category = "Stars", order = 14 } } ) + self:NetworkVarElement( "Angle", 0, 'r', "StarSpeed", { KeyName = "starspeed", Edit = { type = "Float", min = 0, max = 2, category = "Stars", order = 15 } } ) + + self:NetworkVar( "Float", 1, "HDRScale", { KeyName = "hdrscale", Edit = { type = "Float", category = "Main", min = 0, max = 1, order = 4 } } ) + + -- + -- Entity defaults + -- + if ( SERVER ) then + + self:SetTopColor( Vector( 0.2, 0.5, 1.0 ) ) + self:SetBottomColor( Vector( 0.8, 1.0, 1.0 ) ) + self:SetFadeBias( 1 ) + + self:SetSunNormal( Vector( 0.4, 0.0, 0.01 ) ) + self:SetSunColor( Vector( 0.2, 0.1, 0.0 ) ) + self:SetSunSize( 2.0 ) + + self:SetDuskColor( Vector( 1.0, 0.2, 0.0 ) ) + self:SetDuskScale( 1 ) + self:SetDuskIntensity( 1 ) + + self:SetDrawStars( true ) + self:SetStarLayers( 1 ) + self:SetStarSpeed( 0.01 ) + self:SetStarScale( 0.5 ) + self:SetStarFade( 1.5 ) + self:SetStarTexture( "skybox/starfield" ) + + self:SetHDRScale( 0.66 ) + + end + +end + +function ENT:Initialize() +end + +if ( SERVER ) then + + function ENT:KeyValue( key, value ) + + if ( self:SetNetworkKeyValue( key, value ) ) then + return + end + + -- TODO: sunposmethod + -- 0 : "Custom - Use the Sun Normal to position the sun" + -- 1 : "Automatic - Find a env_sun entity and use that" + + end + + + function ENT:AcceptInput( name, activator, caller, data ) + + if ( self:SetNetworkVarsFromMapInput( name, data ) ) then + return true -- Accept the input so the there are no warnings in console with developer 2 + end + + end + +end + +function ENT:Think() + + -- + -- Find an env_sun - if we don't already have one. + -- + if ( SERVER && self.EnvSun == nil ) then + + -- so this closure only gets called once - even if it fails + self.EnvSun = false + + local sunlist = ents.FindByClass( "env_sun" ) + if ( #sunlist > 0 ) then + self.EnvSun = sunlist[1] + end + + end + + -- + -- If we have a sun - force our sun normal to its value + -- + if ( SERVER && IsValid( self.EnvSun ) ) then + + local vec = self.EnvSun:GetInternalVariable( "m_vDirection" ) + + if ( isvector( vec ) ) then + self:SetSunNormal( vec ) + end + + end + + -- + -- Become the active sky again if we're not already + -- + if ( CLIENT && g_SkyPaint != self && !IsValid( g_SkyPaint ) ) then + + g_SkyPaint = self + + end + +end + +-- +-- To prevent server insanity - only let admins edit the sky. +-- +function ENT:CanEditVariables( ply ) + + return ply:IsAdmin() || game.SinglePlayer() + +end diff --git a/gamemodes/base/entities/entities/gmod_hands.lua b/gamemodes/base/entities/entities/gmod_hands.lua new file mode 100644 index 0000000..90f5e6a --- /dev/null +++ b/gamemodes/base/entities/entities/gmod_hands.lua @@ -0,0 +1,76 @@ + +AddCSLuaFile() + +ENT.Type = "anim" +ENT.RenderGroup = RENDERGROUP_OTHER + +function ENT:Initialize() + + hook.Add( "OnViewModelChanged", self, self.ViewModelChanged ) + + self:SetNotSolid( true ) + self:DrawShadow( false ) + self:SetTransmitWithParent( true ) -- Transmit only when the viewmodel does! + +end + +function ENT:DoSetup( ply, spec ) + + -- Set these hands to the player + ply:SetHands( self ) + self:SetOwner( ply ) + + -- Which hands should we use? Let the gamemode decide + hook.Call( "PlayerSetHandsModel", GAMEMODE, spec or ply, self ) + + -- Attach them to the viewmodel + local vm = ( spec or ply ):GetViewModel( 0 ) + self:AttachToViewmodel( vm ) + + vm:DeleteOnRemove( self ) + ply:DeleteOnRemove( self ) + +end + +function ENT:GetPlayerColor() + + -- + -- Make sure there's an owner and they have this function + -- before trying to call it! + -- + local owner = self:GetOwner() + if ( !IsValid( owner ) ) then return end + if ( !owner.GetPlayerColor ) then return end + + return owner:GetPlayerColor() + +end + +function ENT:ViewModelChanged( vm, old, new ) + + -- Ignore other people's viewmodel changes! + if ( vm:GetOwner() != self:GetOwner() ) then return end + + self:AttachToViewmodel( vm ) + +end + +function ENT:OnRemove( fullUpdate ) + + if ( fullUpdate ) then return end + + -- Resolve engine complaints when unparenting from the viewmodel + self:SetPos( vector_origin ) + +end + +function ENT:AttachToViewmodel( vm ) + + self:SetPos( self:GetOwner():GetPos() ) + self:SetAngles( angle_zero ) + + self:AddEffects( EF_BONEMERGE ) + self:SetParent( vm ) + self:SetMoveType( MOVETYPE_NONE ) + +end diff --git a/gamemodes/base/entities/entities/gmod_player_start.lua b/gamemodes/base/entities/entities/gmod_player_start.lua new file mode 100644 index 0000000..36a6ebf --- /dev/null +++ b/gamemodes/base/entities/entities/gmod_player_start.lua @@ -0,0 +1,133 @@ + +-- This is just a simple point entity. + +-- We only use it to represent the position and angle of a spawn point +-- So we don't have to do anything here because the baseclass will +-- take care of the basics + +-- This file only exists so that the entity is created + +ENT.Type = "point" + +function ENT:Initialize() + + if ( self.RedTeam or self.GreenTeam or self.YellowTeam or self.BlueTeam ) then + + -- If any of these are set to true then + -- make sure that any that aren't setup are + -- set to false. + + self.BlueTeam = self.BlueTeam or false + self.GreenTeam = self.GreenTeam or false + self.YellowTeam = self.YellowTeam or false + self.RedTeam = self.RedTeam or false + + else + + -- If none are set then make it so that they all + -- are set to true since any team can spawn here. + -- This will also happen if we don't have the "spawnflags" + -- keyvalue setup. + + self.BlueTeam = true + self.GreenTeam = true + self.YellowTeam = true + self.RedTeam = true + + end + +end + +function ENT:KeyValue( key, value ) + + if ( key == "spawnflags" ) then + + local sf = tonumber( value ) + + for i = 15, 0, -1 do + + local bit = math.pow( 2, i ) + + -- Quick bit if bitwise math to figure out if the spawnflags + -- represent red/blue/green or yellow. + -- We have to use booleans since the TEAM_ identifiers + -- aren't setup at this point. + -- (this would be easier if we had bitwise operators in Lua) + + if ( ( sf - bit ) >= 0 ) then + + if ( bit == 8 ) then self.RedTeam = true + elseif ( bit == 4 ) then self.GreenTeam = true + elseif ( bit == 2 ) then self.YellowTeam = true + elseif ( bit == 1 ) then self.BlueTeam = true + end + + sf = sf - bit + + else + + if ( bit == 8 ) then self.RedTeam = false + elseif ( bit == 4 ) then self.GreenTeam = false + elseif ( bit == 2 ) then self.YellowTeam = false + elseif ( bit == 1 ) then self.BlueTeam = false + end + + end + + end + + end + +end + +-- Maybe gamemodes want to use this list, maybe base gamemode should +ENT.SpawnPointClasses = { + -- Generic + "info_player_blue", + "info_player_red", + "info_player_coop", -- Synergy? + "info_player_deathmatch", + "info_player_zombiemaster", -- ZM + "info_spawnpoint", + + "info_player_counterterrorist", -- CSS + "info_player_terrorist", + + "info_player_allies", -- DODS + "info_player_axis", + + "info_player_knight", -- PVKII + "info_player_pirate", + "info_player_viking", + + "info_survivor_position", -- L4D + "info_survivor_rescue", + + "info_player_human", -- ZPS + "info_player_zombie", + + "diprip_start_team_red", -- DIPRIP + "diprip_start_team_blue", + + "info_player_fof", -- Firstful of Frags + "info_player_desperado", + "info_player_vigilante", + + "info_player_attacker", -- NEOTOKYO + "info_player_defender", + + "info_coop_spawn", -- Portal 2 Coop + "ins_spawnpoint", -- Insurgency + "dys_spawn_point", -- Dystopia + "aoc_spawnpoint", -- Age of Chivalry + "info_ff_teamspawn", -- Fortress Forever +} + +for _, className in pairs( ENT.SpawnPointClasses ) do + local ent_cpy = table.Copy( ENT ) + ent_cpy.Base = "gmod_player_start" + ent_cpy.Folder = "entities/" .. className + ent_cpy.SpawnPointClasses = nil + + scripted_ents.Register( ent_cpy, className ) +end diff --git a/gamemodes/base/entities/entities/lua_run.lua b/gamemodes/base/entities/entities/lua_run.lua new file mode 100644 index 0000000..d30c357 --- /dev/null +++ b/gamemodes/base/entities/entities/lua_run.lua @@ -0,0 +1,63 @@ + +-- A spawnflag constant for addons +SF_LUA_RUN_ON_SPAWN = 1 + +ENT.Type = "point" +ENT.DisableDuplicator = true + +AccessorFunc( ENT, "m_bDefaultCode", "DefaultCode" ) + +function ENT:Initialize() + + -- If the entity has its first spawnflag set, run the code automatically + if ( self:HasSpawnFlags( SF_LUA_RUN_ON_SPAWN ) ) then + self:RunCode( self, self, self:GetDefaultCode() ) + end + +end + +function ENT:KeyValue( key, value ) + + if ( key:lower() == "code" ) then + self:SetDefaultCode( value ) + end + +end + +function ENT:SetupGlobals( activator, caller ) + + ACTIVATOR = activator + CALLER = caller + + if ( IsValid( activator ) && activator:IsPlayer() ) then + TRIGGER_PLAYER = activator + end + +end + +function ENT:KillGlobals() + + ACTIVATOR = nil + CALLER = nil + TRIGGER_PLAYER = nil + +end + +function ENT:RunCode( activator, caller, code ) + + self:SetupGlobals( activator, caller ) + + RunString( code, "lua_run#" .. self:EntIndex() ) + + self:KillGlobals() + +end + +function ENT:AcceptInput( name, activator, caller, data ) + + if ( name == "RunCode" ) then self:RunCode( activator, caller, self:GetDefaultCode() ) return true end + if ( name == "RunPassedCode" ) then self:RunCode( activator, caller, data ) return true end + + return false + +end diff --git a/gamemodes/base/entities/entities/prop_effect.lua b/gamemodes/base/entities/entities/prop_effect.lua new file mode 100644 index 0000000..9110ea8 --- /dev/null +++ b/gamemodes/base/entities/entities/prop_effect.lua @@ -0,0 +1,178 @@ + +AddCSLuaFile() + +if ( CLIENT ) then + CreateConVar( "cl_draweffectrings", "1", 0, "Should the green effect rings be visible?" ) +end + +ENT.Type = "anim" + +ENT.Spawnable = false + +function ENT:Initialize() + + local Radius = 6 + local mins = Vector( 1, 1, 1 ) * Radius * -0.5 + local maxs = Vector( 1, 1, 1 ) * Radius * 0.5 + + if ( SERVER ) then + + self.AttachedEntity = ents.Create( "prop_dynamic" ) + self.AttachedEntity:SetModel( self:GetModel() ) + self.AttachedEntity:SetAngles( self:GetAngles() ) + self.AttachedEntity:SetPos( self:GetPos() ) + self.AttachedEntity:SetSkin( self:GetSkin() ) + self.AttachedEntity:Spawn() + self.AttachedEntity:SetParent( self ) + self.AttachedEntity:DrawShadow( false ) + + self:SetModel( "models/props_junk/watermelon01.mdl" ) + + self:DeleteOnRemove( self.AttachedEntity ) + self.AttachedEntity:DeleteOnRemove( self ) + + -- Don't use the model's physics - create a box instead + self:PhysicsInitBox( mins, maxs ) + self:SetSolid( SOLID_VPHYSICS ) + + -- Set up our physics object here + local phys = self:GetPhysicsObject() + if ( IsValid( phys ) ) then + phys:Wake() + phys:EnableGravity( false ) + phys:EnableDrag( false ) + end + + self:DrawShadow( false ) + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + else + + -- So addons can override this + self.GripMaterial = Material( "sprites/grip" ) + self.GripMaterialHover = Material( "sprites/grip_hover" ) + + -- Get the attached entity so that clientside functions like properties can interact with it + local tab = ents.FindByClassAndParent( "prop_dynamic", self ) + if ( tab && IsValid( tab[ 1 ] ) ) then self.AttachedEntity = tab[ 1 ] end + + end + + -- Set collision bounds exactly + self:SetCollisionBounds( mins, maxs ) + +end + +function ENT:Draw() + + if ( halo.RenderedEntity() == self ) then + self.AttachedEntity:DrawModel() + return + end + + if ( GetConVarNumber( "cl_draweffectrings" ) == 0 ) then return end + + -- Don't draw the grip if there's no chance of us picking it up + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + if ( !IsValid( wep ) ) then return end + + local weapon_name = wep:GetClass() + + if ( weapon_name != "weapon_physgun" && weapon_name != "gmod_tool" ) then + return + end + + if ( self:BeingLookedAtByLocalPlayer() ) then + render.SetMaterial( self.GripMaterialHover ) + else + render.SetMaterial( self.GripMaterial ) + end + + render.DrawSprite( self:GetPos(), 16, 16, color_white ) + +end + +-- Copied from base_gmodentity.lua +ENT.MaxWorldTipDistance = 256 +function ENT:BeingLookedAtByLocalPlayer() + local ply = LocalPlayer() + if ( !IsValid( ply ) ) then return false end + + local view = ply:GetViewEntity() + local dist = self.MaxWorldTipDistance + dist = dist * dist + + -- If we're spectating a player, perform an eye trace + if ( view:IsPlayer() ) then + return view:EyePos():DistToSqr( self:GetPos() ) <= dist && view:GetEyeTrace().Entity == self + end + + -- If we're not spectating a player, perform a manual trace from the entity's position + local pos = view:GetPos() + + if ( pos:DistToSqr( self:GetPos() ) <= dist ) then + return util.TraceLine( { + start = pos, + endpos = pos + ( view:GetAngles():Forward() * dist ), + filter = view + } ).Entity == self + end + + return false +end + +function ENT:PhysicsUpdate( physobj ) + + if ( CLIENT ) then return end + + -- Don't do anything if the player isn't holding us + if ( !self:IsPlayerHolding() && !self:IsConstrained() ) then + + physobj:SetVelocity( vector_origin ) + physobj:Sleep() + + end + +end + +function ENT:OnEntityCopyTableFinish( tab ) + + -- We need to store the model of the attached entity + -- Not the one we have here. + tab.Model = self.AttachedEntity:GetModel() + + -- Store the attached entity's table so we can restore it after being pasted + tab.AttachedEntityInfo = table.Copy( duplicator.CopyEntTable( self.AttachedEntity ) ) + tab.AttachedEntityInfo.Pos = nil -- Don't even save angles and position, we are a parented entity + tab.AttachedEntityInfo.Angle = nil + + -- Do NOT store the attached entity itself in our table! + -- Otherwise, if we copy-paste the prop with the duplicator, its AttachedEntity value will point towards the original prop's attached entity instead, and that'll break stuff + tab.AttachedEntity = nil + +end + +function ENT:PostEntityPaste( ply ) + + -- Restore the attached entity using the information we've saved + if ( IsValid( self.AttachedEntity ) && self.AttachedEntityInfo ) then + + -- Apply skin, bodygroups, bone manipulator, etc. + duplicator.DoGeneric( self.AttachedEntity, self.AttachedEntityInfo ) + + if ( self.AttachedEntityInfo.EntityMods ) then + self.AttachedEntity.EntityMods = table.Copy( self.AttachedEntityInfo.EntityMods ) + duplicator.ApplyEntityModifiers( ply, self.AttachedEntity ) + end + + if ( self.AttachedEntityInfo.BoneMods ) then + self.AttachedEntity.BoneMods = table.Copy( self.AttachedEntityInfo.BoneMods ) + duplicator.ApplyBoneModifiers( ply, self.AttachedEntity ) + end + + self.AttachedEntityInfo = nil + + end + +end diff --git a/gamemodes/base/entities/entities/ragdoll_motion.lua b/gamemodes/base/entities/entities/ragdoll_motion.lua new file mode 100644 index 0000000..f804e0f --- /dev/null +++ b/gamemodes/base/entities/entities/ragdoll_motion.lua @@ -0,0 +1,307 @@ + +AddCSLuaFile() + +ENT.Type = "anim" +ENT.Spawnable = false +ENT.AdminOnly = false +ENT.Editable = true + +function ENT:SetupDataTables() + + -- + -- Scale - how far the ragdoll will move in the game world in relation to how far it moved in the real world + -- + self:NetworkVar( "Float", 0, "Scale", { KeyName = "scale", Edit = { type = "Float", min = 1, max = 512, order = 1 } } ) + + -- + -- Normalize - if enabled the limbs aren't stretched + -- + self:NetworkVar( "Bool", 0, "Normalize", { KeyName = "normalize", Edit = { type = "Boolean", order = 2 } } ) + + -- + -- Debug - Shows some debug info - only available on a listen server + -- + self:NetworkVar( "Bool", 1, "Debug", { KeyName = "debug", Edit = { type = "Boolean", order = 100 } } ) + + -- + -- Controller - the entity that is currently controlling the ragdoll + -- + self:NetworkVar( "Entity", 0, "Controller" ) + self:NetworkVar( "Entity", 1, "Target" ) + + -- + -- Defaults + -- + if ( SERVER ) then + + self:SetScale( 36 ) + self:SetDebug( false ) + self:SetNormalize( true ) + + end + +end + +function ENT:Initialize() + + if ( SERVER ) then + + self:SetModel( "models/maxofs2d/motion_sensor.mdl" ) + self:PhysicsInit( SOLID_VPHYSICS ) + + -- Don't collide with the player + self:SetCollisionGroup( COLLISION_GROUP_WEAPON ) + + self:DrawShadow( false ) + + local phys = self:GetPhysicsObject() + if ( IsValid( phys ) ) then + phys:Wake() + phys:EnableGravity( false ) + phys:EnableDrag( false ) + end + + local colors = { + Color( 180, 255, 50 ), + Color( 0, 150, 255 ), + Color( 255, 255, 0 ), + Color( 255, 50, 255 ) + } + + self:SetColor( table.Random( colors ) ) + + end + +end + +-- +-- We don't want to move unless the player moves us or we're constrained to something. +-- +function ENT:PhysicsUpdate( physobj ) + + if ( self:IsPlayerHolding() ) then return end + if ( self:IsConstrained() ) then return end + + physobj:SetVelocity( vector_origin ) + physobj:Sleep() + +end + +-- +-- Clean up on remove +-- +function ENT:OnRemove() + + if ( SERVER ) then + + local ragdoll = self:GetTarget() + if ( IsValid( ragdoll ) ) then + ragdoll:SetRagdollBuildFunction( nil ) + + if ( IsValid( ragdoll.MotionSensorController ) && ragdoll.MotionSensorController == self ) then + ragdoll.MotionSensorController = nil + end + end + + end + +end + +function ENT:Draw() + + -- + -- Don't draw if we're holding the camera + -- + local ply = LocalPlayer() + local wep = ply:GetActiveWeapon() + if ( IsValid( wep ) && wep:GetClass() == "gmod_camera" ) then + return + end + + self:DrawModel() + +end + +function ENT:DrawDebug( ragdoll, controller, pos, ang, rotation, scale, center, changed_sensor ) + + local UpdateTime = 0.1 + local StayTime = 0.15 + + if ( self.LastDebugUpdate && CurTime() - self.LastDebugUpdate < UpdateTime ) then return end + + self.LastDebugUpdate = CurTime() + + center = center + + local col_bone = color_white + local col_point = Color( 255, 0, 0, 255 ) + local col_tran_bn = Color( 0, 255, 0, 255 ) + + local realbonepos = {} + local fixedbonepos = {} + local min = Vector( 1, 1, 1 ) * -0.5 + local max = Vector( 1, 1, 1 ) * 0.5 + + -- + -- Draw Points + -- + for i = 0, 19 do + + realbonepos[i] = controller:MotionSensorPos( i ) * scale + realbonepos[i]:Rotate( rotation ) + realbonepos[i] = realbonepos[i] + center + + fixedbonepos[i] = changed_sensor[ i ] * scale + -- (already rotated) + fixedbonepos[i] = fixedbonepos[i] + center + + debugoverlay.Box( realbonepos[i], min, max, StayTime, col_point ) + debugoverlay.Box( fixedbonepos[i], min, max, StayTime, col_tran_bn ) + + end + + -- + -- Draw bones + -- + for k, v in ipairs( motionsensor.DebugBones ) do + + debugoverlay.Line( realbonepos[ v[1] ], realbonepos[ v[2] ], StayTime, col_bone, true ) + + end + + -- + -- Draw translated sensor bones + -- + for k, v in ipairs( motionsensor.DebugBones ) do + + debugoverlay.Line( fixedbonepos[ v[1] ], fixedbonepos[ v[2] ], StayTime, col_tran_bn, true ) + + end + + -- + -- Draw ragdoll physics bones + -- + for i = 0, ragdoll:GetPhysicsObjectCount() - 1 do + + local phys = ragdoll:GetPhysicsObjectNum( i ) + + local position = phys:GetPos() + local angle = phys:GetAngles() + local txt = tostring( i ) + + if ( ang[i] == nil ) then + txt = i .. " (UNSET)" + end + + debugoverlay.Text( position, txt, StayTime ) + debugoverlay.Axis( position, angle, 5, StayTime, true ) + + end + +end + +function ENT:SetRagdoll( ragdoll ) + + ragdoll.MotionSensorController = self + + self:SetTarget( ragdoll ) + ragdoll:PhysWake() + + local buildername = motionsensor.ChooseBuilderFromEntity( ragdoll ) + + -- + -- Set the ragdoll build function. + -- This function is called whenever the ragdoll bones are built. + -- in this function is the one place you can successfully call ent:SetRagdollBone + -- + ragdoll:SetRagdollBuildFunction( function( ragdoll ) + + local controller = self:GetController() + if ( !IsValid( controller ) ) then return end + + local builder = list.GetEntry( "SkeletonConvertor", buildername ) + local scale = self:GetScale() + local rotation = self:GetAngles() + local center = self:GetPos() + local normalize = self:GetNormalize() + local debug = self:GetDebug() + + -- + -- Call the build skeleton function. + -- This is thrown out to a pseudo class because we want to be + -- able to add new skeleton types nice and easily. + -- + local pos, ang, changed_sensor = motionsensor.BuildSkeleton( builder, controller, rotation ) + + -- + -- For development + -- + if ( debug ) then + self:DrawDebug( ragdoll, controller, pos, ang, rotation, scale, center, changed_sensor ) + end + + -- + -- If we don't have 85% of the points, just drop dead + -- + local iSkipped = 0 + local iMaxSkip = table.Count( pos ) * 0.25 + for k, v in pairs( pos ) do + + if ( math.abs( v.x ) > 0.05 ) then continue end + if ( math.abs( v.y ) > 0.05 ) then continue end + + pos[k] = nil -- don't use this point to control the ragdoll + ang[k] = nil -- (use the ragdoll point) + + iSkipped = iSkipped + 1 + + if ( iSkipped > iMaxSkip ) then + + ragdoll:RagdollStopControlling() + return + + end + + end + + -- + -- Loop each returned position + -- + for k, v in pairs( pos ) do + + -- + -- Set the bone angle + -- + if ( ang[ k ] != nil ) then + ragdoll:SetRagdollAng( k, ang[ k ] ) + end + + -- + -- The root bone, we directly set the position of this one. + -- + if ( k == 0 || !normalize ) then + + local new_position = center + v * scale + ragdoll:SetRagdollPos( k, new_position ) + + end + + end + + -- + -- Normalize the ragdoll + -- + if ( normalize ) then + + ragdoll:RagdollSolve() + + end + + -- + -- Makes the physics objects follow the set bone positions + -- + ragdoll:RagdollUpdatePhysics() + + end ) + +end diff --git a/gamemodes/base/entities/weapons/weapon_base/ai_translations.lua b/gamemodes/base/entities/weapons/weapon_base/ai_translations.lua new file mode 100644 index 0000000..36c91d5 --- /dev/null +++ b/gamemodes/base/entities/weapons/weapon_base/ai_translations.lua @@ -0,0 +1,192 @@ + +--[[--------------------------------------------------------- + Name: SetupWeaponHoldTypeForAI + Desc: Sets up ACT translations from generic activities to NPC specific activies. In a seperate file to clean up the init.lua + Not all NPCs have support for all animations (for example Citizens don't have pistol animations) + This only supports the holdtypes the default NPC models can support + All of these are taken directly from IMPLEMENT_ACTTABLE() macro of the C++ weapons +-----------------------------------------------------------]] +function SWEP:SetupWeaponHoldTypeForAI( t ) + + self.ActivityTranslateAI = {} + + -- Default is pistol/revolver for reasons + self.ActivityTranslateAI[ ACT_IDLE ] = ACT_IDLE_PISTOL + self.ActivityTranslateAI[ ACT_IDLE_ANGRY ] = ACT_IDLE_ANGRY_PISTOL + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1 ] = ACT_RANGE_ATTACK_PISTOL + self.ActivityTranslateAI[ ACT_RELOAD ] = ACT_RELOAD_PISTOL + self.ActivityTranslateAI[ ACT_WALK_AIM ] = ACT_WALK_AIM_PISTOL + self.ActivityTranslateAI[ ACT_RUN_AIM ] = ACT_RUN_AIM_PISTOL + self.ActivityTranslateAI[ ACT_GESTURE_RANGE_ATTACK1 ] = ACT_GESTURE_RANGE_ATTACK_PISTOL + self.ActivityTranslateAI[ ACT_RELOAD_LOW ] = ACT_RELOAD_PISTOL_LOW + + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1_LOW ] = ACT_RANGE_ATTACK_PISTOL_LOW + self.ActivityTranslateAI[ ACT_COVER_LOW ] = ACT_COVER_PISTOL_LOW + self.ActivityTranslateAI[ ACT_RANGE_AIM_LOW ] = ACT_RANGE_AIM_PISTOL_LOW + self.ActivityTranslateAI[ ACT_GESTURE_RELOAD ] = ACT_GESTURE_RELOAD_PISTOL + + self.ActivityTranslateAI[ ACT_WALK ] = ACT_WALK_PISTOL + self.ActivityTranslateAI[ ACT_RUN ] = ACT_RUN_PISTOL + + self.ActivityTranslateAI[ ACT_IDLE_RELAXED ] = ACT_IDLE_PISTOL + self.ActivityTranslateAI[ ACT_IDLE_STIMULATED ] = ACT_IDLE_STIMULATED + self.ActivityTranslateAI[ ACT_IDLE_AGITATED ] = ACT_IDLE_ANGRY_PISTOL + self.ActivityTranslateAI[ ACT_IDLE_STEALTH ] = ACT_IDLE_STEALTH_PISTOL + + self.ActivityTranslateAI[ ACT_WALK_RELAXED ] = ACT_WALK + self.ActivityTranslateAI[ ACT_WALK_STIMULATED ] = ACT_WALK_STIMULATED + self.ActivityTranslateAI[ ACT_WALK_AGITATED ] = ACT_WALK_AIM_PISTOL + self.ActivityTranslateAI[ ACT_WALK_STEALTH ] = ACT_WALK_STEALTH_PISTOL + + self.ActivityTranslateAI[ ACT_RUN_RELAXED ] = ACT_RUN + self.ActivityTranslateAI[ ACT_RUN_STIMULATED ] = ACT_RUN_STIMULATED + self.ActivityTranslateAI[ ACT_RUN_AGITATED ] = ACT_RUN_AIM_PISTOL + self.ActivityTranslateAI[ ACT_RUN_STEALTH ] = ACT_RUN_STEALTH_PISTOL + + self.ActivityTranslateAI[ ACT_IDLE_AIM_RELAXED ] = ACT_IDLE_PISTOL + self.ActivityTranslateAI[ ACT_IDLE_AIM_STIMULATED ] = ACT_IDLE_ANGRY_PISTOL + self.ActivityTranslateAI[ ACT_IDLE_AIM_AGITATED ] = ACT_IDLE_ANGRY_PISTOL + self.ActivityTranslateAI[ ACT_IDLE_AIM_STEALTH ] = ACT_IDLE_STEALTH_PISTOL + + self.ActivityTranslateAI[ ACT_WALK_AIM_RELAXED ] = ACT_WALK + self.ActivityTranslateAI[ ACT_WALK_AIM_STIMULATED ] = ACT_WALK_AIM_PISTOL + self.ActivityTranslateAI[ ACT_WALK_AIM_AGITATED ] = ACT_WALK_AIM_PISTOL + self.ActivityTranslateAI[ ACT_WALK_AIM_STEALTH ] = ACT_WALK_AIM_STEALTH_PISTOL + + self.ActivityTranslateAI[ ACT_RUN_AIM_RELAXED ] = ACT_RUN + self.ActivityTranslateAI[ ACT_RUN_AIM_STIMULATED ] = ACT_RUN_AIM_PISTOL + self.ActivityTranslateAI[ ACT_RUN_AIM_AGITATED ] = ACT_RUN_AIM_PISTOL + self.ActivityTranslateAI[ ACT_RUN_AIM_STEALTH ] = ACT_RUN_AIM_STEALTH_PISTOL + + self.ActivityTranslateAI[ ACT_CROUCHIDLE_STIMULATED] = ACT_CROUCHIDLE_STIMULATED + self.ActivityTranslateAI[ ACT_CROUCHIDLE_AIM_STIMULATED ] = ACT_RANGE_AIM_PISTOL_LOW + self.ActivityTranslateAI[ ACT_CROUCHIDLE_AGITATED ] = ACT_RANGE_AIM_PISTOL_LOW + + self.ActivityTranslateAI[ ACT_READINESS_RELAXED_TO_STIMULATED ] = ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED + self.ActivityTranslateAI[ ACT_READINESS_RELAXED_TO_STIMULATED_WALK ] = ACT_READINESS_PISTOL_RELAXED_TO_STIMULATED_WALK + self.ActivityTranslateAI[ ACT_READINESS_AGITATED_TO_STIMULATED ] = ACT_READINESS_PISTOL_AGITATED_TO_STIMULATED + self.ActivityTranslateAI[ ACT_READINESS_STIMULATED_TO_RELAXED ] = ACT_READINESS_PISTOL_STIMULATED_TO_RELAXED + + if ( t == "ar2" || t == "smg" ) then + + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1 ] = ACT_RANGE_ATTACK_AR2 + self.ActivityTranslateAI[ ACT_RELOAD ] = ACT_RELOAD_SMG1 + self.ActivityTranslateAI[ ACT_IDLE ] = ACT_IDLE_SMG1 + self.ActivityTranslateAI[ ACT_IDLE_ANGRY ] = ACT_IDLE_ANGRY_SMG1 + + self.ActivityTranslateAI[ ACT_WALK ] = ACT_WALK_RIFLE + self.ActivityTranslateAI[ ACT_WALK_AIM ] = ACT_WALK_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_IDLE_RELAXED ] = ACT_IDLE_SMG1_RELAXED + self.ActivityTranslateAI[ ACT_IDLE_STIMULATED ] = ACT_IDLE_SMG1_STIMULATED + self.ActivityTranslateAI[ ACT_IDLE_AGITATED ] = ACT_IDLE_ANGRY_SMG1 + + self.ActivityTranslateAI[ ACT_WALK_RELAXED ] = ACT_WALK_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_WALK_STIMULATED ] = ACT_WALK_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_WALK_AGITATED ] = ACT_WALK_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_RUN_RELAXED ] = ACT_RUN_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_RUN_STIMULATED ] = ACT_RUN_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_RUN_AGITATED ] = ACT_RUN_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_IDLE_AIM_RELAXED ] = ACT_IDLE_SMG1_RELAXED + self.ActivityTranslateAI[ ACT_IDLE_AIM_STIMULATED ] = ACT_IDLE_AIM_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_IDLE_AIM_AGITATED ] = ACT_IDLE_ANGRY_SMG1 + + self.ActivityTranslateAI[ ACT_WALK_AIM_RELAXED ] = ACT_WALK_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_WALK_AIM_STIMULATED ] = ACT_WALK_AIM_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_WALK_AIM_AGITATED ] = ACT_WALK_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_RUN_AIM_RELAXED ] = ACT_RUN_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_RUN_AIM_STIMULATED ] = ACT_RUN_AIM_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_RUN_AIM_AGITATED ] = ACT_RUN_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_WALK_CROUCH ] = ACT_WALK_CROUCH_RIFLE + self.ActivityTranslateAI[ ACT_WALK_CROUCH_AIM ] = ACT_WALK_CROUCH_AIM_RIFLE + self.ActivityTranslateAI[ ACT_RUN ] = ACT_RUN_RIFLE + self.ActivityTranslateAI[ ACT_RUN_AIM ] = ACT_RUN_AIM_RIFLE + self.ActivityTranslateAI[ ACT_RUN_CROUCH ] = ACT_RUN_CROUCH_RIFLE + self.ActivityTranslateAI[ ACT_RUN_CROUCH_AIM ] = ACT_RUN_CROUCH_AIM_RIFLE + self.ActivityTranslateAI[ ACT_GESTURE_RANGE_ATTACK1 ] = ACT_GESTURE_RANGE_ATTACK_AR2 + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1_LOW ] = ACT_RANGE_ATTACK_SMG1_LOW + self.ActivityTranslateAI[ ACT_COVER_LOW ] = ACT_COVER_SMG1_LOW + self.ActivityTranslateAI[ ACT_RANGE_AIM_LOW ] = ACT_RANGE_AIM_AR2_LOW + self.ActivityTranslateAI[ ACT_RELOAD_LOW ] = ACT_RELOAD_SMG1_LOW + self.ActivityTranslateAI[ ACT_GESTURE_RELOAD ] = ACT_GESTURE_RELOAD_SMG1 + + -- Extra overrides for SMG holdtype + if ( t == "smg" ) then + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1 ] = ACT_RANGE_ATTACK_SMG1 + self.ActivityTranslateAI[ ACT_GESTURE_RANGE_ATTACK1 ] = ACT_GESTURE_RANGE_ATTACK_SMG1 + self.ActivityTranslateAI[ ACT_RANGE_AIM_LOW ] = ACT_RANGE_AIM_SMG1_LOW + end + + return + end + + if ( t == "shotgun" || t == "crossbow" ) then + self.ActivityTranslateAI[ ACT_IDLE ] = ACT_IDLE_SMG1 + + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1 ] = ACT_RANGE_ATTACK_SHOTGUN + self.ActivityTranslateAI[ ACT_RELOAD ] = ACT_RELOAD_SHOTGUN + self.ActivityTranslateAI[ ACT_WALK ] = ACT_WALK_RIFLE + self.ActivityTranslateAI[ ACT_IDLE_ANGRY ] = ACT_IDLE_ANGRY_SHOTGUN + + self.ActivityTranslateAI[ ACT_IDLE_RELAXED ] = ACT_IDLE_SHOTGUN_RELAXED + self.ActivityTranslateAI[ ACT_IDLE_STIMULATED ] = ACT_IDLE_SHOTGUN_STIMULATED + self.ActivityTranslateAI[ ACT_IDLE_AGITATED ] = ACT_IDLE_SHOTGUN_AGITATED + + self.ActivityTranslateAI[ ACT_WALK_RELAXED ] = ACT_WALK_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_WALK_STIMULATED ] = ACT_WALK_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_WALK_AGITATED ] = ACT_WALK_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_RUN_RELAXED ] = ACT_RUN_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_RUN_STIMULATED ] = ACT_RUN_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_RUN_AGITATED ] = ACT_RUN_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_IDLE_AIM_RELAXED ] = ACT_IDLE_SMG1_RELAXED + self.ActivityTranslateAI[ ACT_IDLE_AIM_STIMULATED ] = ACT_IDLE_AIM_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_IDLE_AIM_AGITATED ] = ACT_IDLE_ANGRY_SMG1 + + self.ActivityTranslateAI[ ACT_WALK_AIM_RELAXED ] = ACT_WALK_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_WALK_AIM_STIMULATED ] = ACT_WALK_AIM_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_WALK_AIM_AGITATED ] = ACT_WALK_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_RUN_AIM_RELAXED ] = ACT_RUN_RIFLE_RELAXED + self.ActivityTranslateAI[ ACT_RUN_AIM_STIMULATED ] = ACT_RUN_AIM_RIFLE_STIMULATED + self.ActivityTranslateAI[ ACT_RUN_AIM_AGITATED ] = ACT_RUN_AIM_RIFLE + + self.ActivityTranslateAI[ ACT_WALK_AIM ] = ACT_WALK_AIM_SHOTGUN + self.ActivityTranslateAI[ ACT_WALK_CROUCH ] = ACT_WALK_CROUCH_RIFLE + self.ActivityTranslateAI[ ACT_WALK_CROUCH_AIM ] = ACT_WALK_CROUCH_AIM_RIFLE + self.ActivityTranslateAI[ ACT_RUN ] = ACT_RUN_RIFLE + self.ActivityTranslateAI[ ACT_RUN_AIM ] = ACT_RUN_AIM_SHOTGUN + self.ActivityTranslateAI[ ACT_RUN_CROUCH ] = ACT_RUN_CROUCH_RIFLE + self.ActivityTranslateAI[ ACT_RUN_CROUCH_AIM ] = ACT_RUN_CROUCH_AIM_RIFLE + self.ActivityTranslateAI[ ACT_GESTURE_RANGE_ATTACK1 ] = ACT_GESTURE_RANGE_ATTACK_SHOTGUN + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1_LOW ] = ACT_RANGE_ATTACK_SHOTGUN_LOW + self.ActivityTranslateAI[ ACT_RELOAD_LOW ] = ACT_RELOAD_SHOTGUN_LOW + self.ActivityTranslateAI[ ACT_GESTURE_RELOAD ] = ACT_GESTURE_RELOAD_SHOTGUN + + return + end + + if ( t == "rpg" ) then + self.ActivityTranslateAI[ ACT_RANGE_ATTACK1 ] = ACT_RANGE_ATTACK_RPG + + self.ActivityTranslateAI[ ACT_IDLE_RELAXED ] = ACT_IDLE_RPG_RELAXED + self.ActivityTranslateAI[ ACT_IDLE_STIMULATED ] = ACT_IDLE_ANGRY_RPG + self.ActivityTranslateAI[ ACT_IDLE_AGITATED ] = ACT_IDLE_ANGRY_RPG + + self.ActivityTranslateAI[ ACT_IDLE ] = ACT_IDLE_RPG + self.ActivityTranslateAI[ ACT_IDLE_ANGRY ] = ACT_IDLE_ANGRY_RPG + self.ActivityTranslateAI[ ACT_WALK ] = ACT_WALK_RPG + self.ActivityTranslateAI[ ACT_WALK_CROUCH ] = ACT_WALK_CROUCH_RPG + self.ActivityTranslateAI[ ACT_RUN ] = ACT_RUN_RPG + self.ActivityTranslateAI[ ACT_RUN_CROUCH ] = ACT_RUN_CROUCH_RPG + self.ActivityTranslateAI[ ACT_COVER_LOW ] = ACT_COVER_LOW_RPG + + return + end + +end diff --git a/gamemodes/base/entities/weapons/weapon_base/cl_init.lua b/gamemodes/base/entities/weapons/weapon_base/cl_init.lua new file mode 100644 index 0000000..3c24586 --- /dev/null +++ b/gamemodes/base/entities/weapons/weapon_base/cl_init.lua @@ -0,0 +1,211 @@ + +include( "ai_translations.lua" ) +include( "sh_anim.lua" ) +include( "shared.lua" ) + +SWEP.Slot = 0 -- Slot in the weapon selection menu +SWEP.SlotPos = 10 -- Position in the slot +SWEP.DrawAmmo = true -- Should draw the default HL2 ammo counter +SWEP.DrawCrosshair = true -- Should draw the default crosshair +SWEP.DrawWeaponInfoBox = true -- Should draw the weapon info box +SWEP.BounceWeaponIcon = true -- Should the weapon icon bounce? +SWEP.SwayScale = 1.0 -- The scale of the viewmodel sway +SWEP.BobScale = 1.0 -- The scale of the viewmodel bob + +SWEP.RenderGroup = RENDERGROUP_OPAQUE + +-- Override this in your SWEP to set the icon in the weapon selection +SWEP.WepSelectIcon = surface.GetTextureID( "weapons/swep" ) + +-- This is the corner of the speech bubble +SWEP.SpeechBubbleLid = surface.GetTextureID( "gui/speech_lid" ) + +--[[--------------------------------------------------------- + You can draw to the HUD here - it will only draw when + the client has the weapon deployed.. +-----------------------------------------------------------]] +function SWEP:DrawHUD() +end + +--[[--------------------------------------------------------- + Checks the objects before any action is taken + This is to make sure that the entities haven't been removed +-----------------------------------------------------------]] +function SWEP:DrawWeaponSelection( x, y, wide, tall, alpha ) + + -- Set us up the texture + surface.SetDrawColor( 255, 255, 255, alpha ) + surface.SetTexture( self.WepSelectIcon ) + + -- Lets get a sin wave to make it bounce + local fsin = 0 + + if ( self.BounceWeaponIcon == true ) then + fsin = math.sin( CurTime() * 10 ) * 5 + end + + -- Borders + y = y + 10 + x = x + 10 + wide = wide - 20 + + -- Draw that mother + surface.DrawTexturedRect( x + fsin, y - fsin, wide - fsin * 2 , ( wide / 2 ) + fsin ) + + -- Draw weapon info box + self:PrintWeaponInfo( x + wide + 20, y + tall * 0.95, alpha ) + +end + +--[[--------------------------------------------------------- + This draws the weapon info box +-----------------------------------------------------------]] +function SWEP:PrintWeaponInfo( x, y, alpha ) + + if ( self.DrawWeaponInfoBox == false ) then return end + + if ( self.InfoMarkup == nil ) then + local title_color = "" + local text_color = "" + + local str = "" + if ( self.Author != "" ) then str = str .. title_color .. "#entityinfo.author\t" .. text_color .. self.Author .. "\n" end + if ( self.Contact != "" ) then str = str .. title_color .. "#entityinfo.contact\t" .. text_color .. self.Contact .. "\n\n" end + if ( self.Purpose != "" ) then str = str .. title_color .. "#entityinfo.purpose\n" .. text_color .. self.Purpose .. "\n\n" end + if ( self.Instructions != "" ) then str = str .. title_color .. "#entityinfo.instructions\n" .. text_color .. self.Instructions .. "\n" end + str = str .. "" + + self.InfoMarkup = markup.Parse( str, 250 ) + end + + surface.SetDrawColor( 60, 60, 60, alpha ) + surface.SetTexture( self.SpeechBubbleLid ) + + surface.DrawTexturedRect( x, y - 64 - 5, 128, 64 ) + draw.RoundedBox( 8, x - 5, y - 5, 260, self.InfoMarkup:GetHeight() + 18, Color( 60, 60, 60, alpha ) ) + + self.InfoMarkup:Draw( x + 5, y + 5, nil, nil, alpha ) + +end + +--[[--------------------------------------------------------- + Name: SWEP:FreezeMovement() + Desc: Return true to freeze moving the view +-----------------------------------------------------------]] +function SWEP:FreezeMovement() + return false +end + +--[[--------------------------------------------------------- + Name: SWEP:ViewModelDrawn( viewModel ) + Desc: Called straight after the viewmodel has been drawn +-----------------------------------------------------------]] +function SWEP:ViewModelDrawn( vm ) +end + +--[[--------------------------------------------------------- + Name: OnRestore + Desc: Called immediately after a "load" +-----------------------------------------------------------]] +function SWEP:OnRestore() +end + +--[[--------------------------------------------------------- + Name: CustomAmmoDisplay + Desc: Return a table +-----------------------------------------------------------]] +function SWEP:CustomAmmoDisplay() +end + +--[[--------------------------------------------------------- + Name: GetViewModelPosition + Desc: Allows you to re-position the view model +-----------------------------------------------------------]] +function SWEP:GetViewModelPosition( pos, ang ) + + return pos, ang + +end + +--[[--------------------------------------------------------- + Name: TranslateFOV + Desc: Allows the weapon to translate the player's FOV (clientside) +-----------------------------------------------------------]] +function SWEP:TranslateFOV( current_fov ) + + return current_fov + +end + +--[[--------------------------------------------------------- + Name: DrawWorldModel + Desc: Draws the world model (not the viewmodel) +-----------------------------------------------------------]] +function SWEP:DrawWorldModel() + + self:DrawModel() + +end + +--[[--------------------------------------------------------- + Name: DrawWorldModelTranslucent + Desc: Draws the world model (not the viewmodel) +-----------------------------------------------------------]] +function SWEP:DrawWorldModelTranslucent() + + self:DrawModel() + +end + +--[[--------------------------------------------------------- + Name: AdjustMouseSensitivity + Desc: Allows you to adjust the mouse sensitivity. +-----------------------------------------------------------]] +function SWEP:AdjustMouseSensitivity() + + return nil + +end + +--[[--------------------------------------------------------- + Name: GetTracerOrigin + Desc: Allows you to override where the tracer comes from (in first person view) + returning anything but a vector indicates that you want the default action +-----------------------------------------------------------]] +function SWEP:GetTracerOrigin() + +--[[ + local ply = self:GetOwner() + local pos = ply:EyePos() + ply:EyeAngles():Right() * -5 + return pos +--]] + +end + +--[[--------------------------------------------------------- + Name: FireAnimationEvent + Desc: Allows you to override weapon animation events +-----------------------------------------------------------]] +function SWEP:FireAnimationEvent( pos, ang, event, options ) + + if ( !self.CSMuzzleFlashes ) then return end + + -- CS Muzzle flashes + if ( event == 5001 or event == 5011 or event == 5021 or event == 5031 ) then + + local data = EffectData() + data:SetFlags( 0 ) + data:SetEntity( self:GetOwner():GetViewModel() ) + data:SetAttachment( math.floor( ( event - 4991 ) / 10 ) ) + data:SetScale( 1 ) + + if ( self.CSMuzzleX ) then + util.Effect( "CS_MuzzleFlash_X", data ) + else + util.Effect( "CS_MuzzleFlash", data ) + end + + return true + end + +end diff --git a/gamemodes/base/entities/weapons/weapon_base/init.lua b/gamemodes/base/entities/weapons/weapon_base/init.lua new file mode 100644 index 0000000..468a1b8 --- /dev/null +++ b/gamemodes/base/entities/weapons/weapon_base/init.lua @@ -0,0 +1,104 @@ + +AddCSLuaFile( "cl_init.lua" ) +AddCSLuaFile( "ai_translations.lua" ) +AddCSLuaFile( "sh_anim.lua" ) +AddCSLuaFile( "shared.lua" ) + +include( "ai_translations.lua" ) +include( "sh_anim.lua" ) +include( "shared.lua" ) + +SWEP.Weight = 5 -- Decides whether we should switch from/to this +SWEP.AutoSwitchTo = true -- Auto switch to if we pick it up +SWEP.AutoSwitchFrom = true -- Auto switch from if you pick up a better weapon + +--[[--------------------------------------------------------- + Name: OnRestore + Desc: The game has just been reloaded. This is usually the right place + to call the GetNW* functions to restore the script's values. +-----------------------------------------------------------]] +function SWEP:OnRestore() +end + +--[[--------------------------------------------------------- + Name: AcceptInput + Desc: Accepts input, return true to override/accept input +-----------------------------------------------------------]] +function SWEP:AcceptInput( name, activator, caller, data ) + return false +end + +--[[--------------------------------------------------------- + Name: KeyValue + Desc: Called when a keyvalue is added to us +-----------------------------------------------------------]] +function SWEP:KeyValue( key, value ) +end + +--[[--------------------------------------------------------- + Name: Equip + Desc: A player or NPC has picked the weapon up +-----------------------------------------------------------]] +function SWEP:Equip( newOwner ) +end + +--[[--------------------------------------------------------- + Name: EquipAmmo + Desc: The player has picked up the weapon and has taken the ammo from it + The weapon will be removed immediately after this call. +-----------------------------------------------------------]] +function SWEP:EquipAmmo( newOwner ) +end + + +--[[--------------------------------------------------------- + Name: OnDrop + Desc: Weapon was dropped +-----------------------------------------------------------]] +function SWEP:OnDrop() +end + +--[[--------------------------------------------------------- + Name: ShouldDropOnDie + Desc: Should this weapon be dropped when its owner dies? +-----------------------------------------------------------]] +function SWEP:ShouldDropOnDie() + return true +end + +--[[--------------------------------------------------------- + Name: GetCapabilities + Desc: For NPCs, returns what they should try to do with it. +-----------------------------------------------------------]] +function SWEP:GetCapabilities() + + return CAP_WEAPON_RANGE_ATTACK1 + +end + +--[[--------------------------------------------------------- + Name: NPCShoot_Secondary + Desc: NPC tried to fire secondary attack +-----------------------------------------------------------]] +function SWEP:NPCShoot_Secondary( shootPos, shootDir ) + + self:SecondaryAttack() + +end + +--[[--------------------------------------------------------- + Name: NPCShoot_Secondary + Desc: NPC tried to fire primary attack +-----------------------------------------------------------]] +function SWEP:NPCShoot_Primary( shootPos, shootDir ) + + self:PrimaryAttack() + +end + +-- These tell the NPC how to use the weapon +AccessorFunc( SWEP, "fNPCMinBurst", "NPCMinBurst" ) +AccessorFunc( SWEP, "fNPCMaxBurst", "NPCMaxBurst" ) +AccessorFunc( SWEP, "fNPCFireRate", "NPCFireRate" ) +AccessorFunc( SWEP, "fNPCMinRestTime", "NPCMinRest" ) +AccessorFunc( SWEP, "fNPCMaxRestTime", "NPCMaxRest" ) diff --git a/gamemodes/base/entities/weapons/weapon_base/sh_anim.lua b/gamemodes/base/entities/weapons/weapon_base/sh_anim.lua new file mode 100644 index 0000000..f68fccd --- /dev/null +++ b/gamemodes/base/entities/weapons/weapon_base/sh_anim.lua @@ -0,0 +1,88 @@ + +local ActIndex = { + [ "pistol" ] = ACT_HL2MP_IDLE_PISTOL, + [ "smg" ] = ACT_HL2MP_IDLE_SMG1, + [ "grenade" ] = ACT_HL2MP_IDLE_GRENADE, + [ "ar2" ] = ACT_HL2MP_IDLE_AR2, + [ "shotgun" ] = ACT_HL2MP_IDLE_SHOTGUN, + [ "rpg" ] = ACT_HL2MP_IDLE_RPG, + [ "physgun" ] = ACT_HL2MP_IDLE_PHYSGUN, + [ "crossbow" ] = ACT_HL2MP_IDLE_CROSSBOW, + [ "melee" ] = ACT_HL2MP_IDLE_MELEE, + [ "slam" ] = ACT_HL2MP_IDLE_SLAM, + [ "normal" ] = ACT_HL2MP_IDLE, + [ "fist" ] = ACT_HL2MP_IDLE_FIST, + [ "melee2" ] = ACT_HL2MP_IDLE_MELEE2, + [ "passive" ] = ACT_HL2MP_IDLE_PASSIVE, + [ "knife" ] = ACT_HL2MP_IDLE_KNIFE, + [ "duel" ] = ACT_HL2MP_IDLE_DUEL, + [ "camera" ] = ACT_HL2MP_IDLE_CAMERA, + [ "magic" ] = ACT_HL2MP_IDLE_MAGIC, + [ "revolver" ] = ACT_HL2MP_IDLE_REVOLVER +} + +--[[--------------------------------------------------------- + Name: SetWeaponHoldType + Desc: Sets up the translation table, to translate from normal + standing idle pose, to holding weapon pose. +-----------------------------------------------------------]] +function SWEP:SetWeaponHoldType( t ) + + t = string.lower( t ) + local index = ActIndex[ t ] + + if ( index == nil ) then + Msg( "SWEP:SetWeaponHoldType - ActIndex[ \"" .. t .. "\" ] isn't set! (defaulting to normal)\n" ) + t = "normal" + index = ActIndex[ t ] + end + + self.ActivityTranslate = {} + self.ActivityTranslate[ ACT_MP_STAND_IDLE ] = index + self.ActivityTranslate[ ACT_MP_WALK ] = index + 1 + self.ActivityTranslate[ ACT_MP_RUN ] = index + 2 + self.ActivityTranslate[ ACT_MP_CROUCH_IDLE ] = index + 3 + self.ActivityTranslate[ ACT_MP_CROUCHWALK ] = index + 4 + self.ActivityTranslate[ ACT_MP_ATTACK_STAND_PRIMARYFIRE ] = index + 5 + self.ActivityTranslate[ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE ] = index + 5 + self.ActivityTranslate[ ACT_MP_RELOAD_STAND ] = index + 6 + self.ActivityTranslate[ ACT_MP_RELOAD_CROUCH ] = index + 6 + self.ActivityTranslate[ ACT_MP_JUMP ] = index + 7 + self.ActivityTranslate[ ACT_RANGE_ATTACK1 ] = index + 8 -- Is this right? Is this for NPCs? + self.ActivityTranslate[ ACT_MP_SWIM_IDLE ] = index + 8 + self.ActivityTranslate[ ACT_MP_SWIM ] = index + 9 + + -- "normal" jump animation doesn't exist + if ( t == "normal" ) then + self.ActivityTranslate[ ACT_MP_JUMP ] = ACT_HL2MP_JUMP_SLAM + end + + self:SetupWeaponHoldTypeForAI( t ) + +end + +-- Default hold pos is the pistol +SWEP:SetWeaponHoldType( "pistol" ) + +--[[--------------------------------------------------------- + Name: weapon:TranslateActivity() + Desc: Translate a player's Activity into a weapon's activity + So for example, ACT_HL2MP_RUN becomes ACT_HL2MP_RUN_PISTOL + Depending on how you want the player to be holding the weapon +-----------------------------------------------------------]] +function SWEP:TranslateActivity( act ) + + if ( self:GetOwner():IsNPC() ) then + if ( self.ActivityTranslateAI[ act ] ) then + return self.ActivityTranslateAI[ act ] + end + return -1 + end + + if ( self.ActivityTranslate[ act ] != nil ) then + return self.ActivityTranslate[ act ] + end + + return -1 + +end diff --git a/gamemodes/base/entities/weapons/weapon_base/shared.lua b/gamemodes/base/entities/weapons/weapon_base/shared.lua new file mode 100644 index 0000000..6a5cecc --- /dev/null +++ b/gamemodes/base/entities/weapons/weapon_base/shared.lua @@ -0,0 +1,279 @@ + +-- Variables that are used on both client and server + +SWEP.PrintName = "Scripted Weapon" -- 'Nice' Weapon name (Shown on HUD) +SWEP.Author = "" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "" + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.ViewModel = "models/weapons/v_pistol.mdl" +SWEP.WorldModel = "models/weapons/w_357.mdl" + +SWEP.Spawnable = false +SWEP.AdminOnly = false + +SWEP.Primary.ClipSize = 8 -- Size of a clip +SWEP.Primary.DefaultClip = 32 -- Default number of bullets in a clip +SWEP.Primary.Automatic = false -- Automatic/Semi Auto +SWEP.Primary.Ammo = "Pistol" + +SWEP.Secondary.ClipSize = 8 -- Size of a clip +SWEP.Secondary.DefaultClip = 32 -- Default number of bullets in a clip +SWEP.Secondary.Automatic = false -- Automatic/Semi Auto +SWEP.Secondary.Ammo = "Pistol" + +--[[--------------------------------------------------------- + Name: SWEP:Initialize() + Desc: Called when the weapon is first loaded +-----------------------------------------------------------]] +function SWEP:Initialize() + + self:SetHoldType( "pistol" ) + +end + +--[[--------------------------------------------------------- + Name: SWEP:PrimaryAttack() + Desc: +attack1 has been pressed +-----------------------------------------------------------]] +function SWEP:PrimaryAttack() + + -- Make sure we can shoot first + if ( !self:CanPrimaryAttack() ) then return end + + -- Play shoot sound + self:EmitSound( "Weapon_AR2.Single" ) + + -- Shoot 9 bullets, 150 damage, 0.75 aimcone + self:ShootBullet( 150, 1, 0.01, self.Primary.Ammo ) + + -- Remove 1 bullet from our clip + self:TakePrimaryAmmo( 1 ) + + -- Punch the player's view + local owner = self:GetOwner() + if ( IsValid( owner ) and !owner:IsNPC() ) then owner:ViewPunch( Angle( -1, 0, 0 ) ) end + +end + +--[[--------------------------------------------------------- + Name: SWEP:SecondaryAttack() + Desc: +attack2 has been pressed +-----------------------------------------------------------]] +function SWEP:SecondaryAttack() + + -- Make sure we can shoot first + if ( !self:CanSecondaryAttack() ) then return end + + -- Play shoot sound + self:EmitSound("Weapon_Shotgun.Single") + + -- Shoot 9 bullets, 150 damage, 0.75 aimcone + self:ShootBullet( 150, 9, 0.2, self.Secondary.Ammo ) + + -- Remove 1 bullet from our clip + self:TakeSecondaryAmmo( 1 ) + + -- Punch the player's view + local owner = self:GetOwner() + if ( IsValid( owner ) and !owner:IsNPC() ) then owner:ViewPunch( Angle( -10, 0, 0 ) ) end + +end + +--[[--------------------------------------------------------- + Name: SWEP:Reload() + Desc: Reload is being pressed +-----------------------------------------------------------]] +function SWEP:Reload() + self:DefaultReload( ACT_VM_RELOAD ) +end + +--[[--------------------------------------------------------- + Name: SWEP:Think() + Desc: Called every frame +-----------------------------------------------------------]] +function SWEP:Think() +end + +--[[--------------------------------------------------------- + Name: SWEP:Holster( weapon_to_swap_to ) + Desc: Weapon wants to holster + RetV: Return true to allow the weapon to holster +-----------------------------------------------------------]] +function SWEP:Holster( wep ) + return true +end + +--[[--------------------------------------------------------- + Name: SWEP:Deploy() + Desc: Whip it out +-----------------------------------------------------------]] +function SWEP:Deploy() + return true +end + +--[[--------------------------------------------------------- + Name: SWEP:ShootEffects() + Desc: A convenience function to create shoot effects +-----------------------------------------------------------]] +function SWEP:ShootEffects() + + local owner = self:GetOwner() + + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) -- View model animation + owner:MuzzleFlash() -- Crappy muzzle light + owner:SetAnimation( PLAYER_ATTACK1 ) -- 3rd Person Animation + +end + +--[[--------------------------------------------------------- + Name: SWEP:ShootBullet() + Desc: A convenience function to shoot bullets +-----------------------------------------------------------]] +function SWEP:ShootBullet( damage, num_bullets, aimcone, ammo_type, force, tracer ) + + -- Effects first, in case the owner dies during firebullets call + self:ShootEffects() + + local owner = self:GetOwner() + + local bullet = {} + bullet.Num = num_bullets + bullet.Src = owner:GetShootPos() -- Source + bullet.Dir = owner:GetAimVector() -- Dir of bullet + bullet.Spread = Vector( aimcone, aimcone, 0 ) -- Aim Cone + bullet.Tracer = tracer || 5 -- Show a tracer on every x bullets + bullet.Force = force || 1 -- Amount of force to give to phys objects + bullet.Damage = damage + bullet.AmmoType = ammo_type || self.Primary.Ammo + bullet.Attacker = owner + bullet.Inflictor = self + + owner:FireBullets( bullet ) + +end + +--[[--------------------------------------------------------- + Name: SWEP:TakePrimaryAmmo() + Desc: A convenience function to remove ammo +-----------------------------------------------------------]] +function SWEP:TakePrimaryAmmo( num ) + + -- Doesn't use clips + if ( self:Clip1() <= 0 ) then + + if ( self:Ammo1() <= 0 ) then return end + + self:GetOwner():RemoveAmmo( num, self:GetPrimaryAmmoType() ) + + return end + + self:SetClip1( self:Clip1() - num ) + +end + +--[[--------------------------------------------------------- + Name: SWEP:TakeSecondaryAmmo() + Desc: A convenience function to remove ammo +-----------------------------------------------------------]] +function SWEP:TakeSecondaryAmmo( num ) + + -- Doesn't use clips + if ( self:Clip2() <= 0 ) then + + if ( self:Ammo2() <= 0 ) then return end + + self:GetOwner():RemoveAmmo( num, self:GetSecondaryAmmoType() ) + + return end + + self:SetClip2( self:Clip2() - num ) + +end + +--[[--------------------------------------------------------- + Name: SWEP:CanPrimaryAttack() + Desc: Helper function for checking for no ammo +-----------------------------------------------------------]] +function SWEP:CanPrimaryAttack() + + if ( self:Clip1() <= 0 ) then + + self:EmitSound( "Weapon_Pistol.Empty" ) + self:SetNextPrimaryFire( CurTime() + 0.2 ) + self:Reload() + return false + + end + + return true + +end + +--[[--------------------------------------------------------- + Name: SWEP:CanSecondaryAttack() + Desc: Helper function for checking for no ammo +-----------------------------------------------------------]] +function SWEP:CanSecondaryAttack() + + if ( self:Clip2() <= 0 ) then + + self:EmitSound( "Weapon_Pistol.Empty" ) + self:SetNextSecondaryFire( CurTime() + 0.2 ) + return false + + end + + return true + +end + +--[[--------------------------------------------------------- + Name: OnRemove + Desc: Called just before entity is deleted +-----------------------------------------------------------]] +function SWEP:OnRemove() +end + +--[[--------------------------------------------------------- + Name: OwnerChanged + Desc: When weapon is dropped or picked up by a new player +-----------------------------------------------------------]] +function SWEP:OwnerChanged() +end + +--[[--------------------------------------------------------- + Name: Ammo1 + Desc: Returns how much of ammo1 the player has +-----------------------------------------------------------]] +function SWEP:Ammo1() + -- Owner cannot have ammo? Such as NPCs. + if ( !self:GetOwner().GetAmmoCount ) then return 0 end + + return self:GetOwner():GetAmmoCount( self:GetPrimaryAmmoType() ) +end + +--[[--------------------------------------------------------- + Name: Ammo2 + Desc: Returns how much of ammo2 the player has +-----------------------------------------------------------]] +function SWEP:Ammo2() + -- Owner cannot have ammo? Such as NPCs. + if ( !self:GetOwner().GetAmmoCount ) then return 0 end + + return self:GetOwner():GetAmmoCount( self:GetSecondaryAmmoType() ) +end + +--[[--------------------------------------------------------- + Name: DoImpactEffect + Desc: Callback so the weapon can override the impact effects it makes + return true to not do the default thing - which is to call UTIL_ImpactTrace in c++ +-----------------------------------------------------------]] +function SWEP:DoImpactEffect( tr, nDamageType ) + + return false + +end diff --git a/gamemodes/base/gamemode/animations.lua b/gamemodes/base/gamemode/animations.lua new file mode 100644 index 0000000..fb16944 --- /dev/null +++ b/gamemodes/base/gamemode/animations.lua @@ -0,0 +1,395 @@ + +function GM:HandlePlayerJumping( ply, velocity, plyTable ) + + if ( !plyTable ) then plyTable = ply:GetTable() end + + if ( ply:GetMoveType() == MOVETYPE_NOCLIP ) then + plyTable.m_bJumping = false + return + end + + -- airwalk more like hl2mp, we airwalk until we have 0 velocity, then it's the jump animation + -- underwater we're alright we airwalking + if ( !plyTable.m_bJumping && !ply:OnGround() && ply:WaterLevel() <= 0 ) then + + if ( !plyTable.m_fGroundTime ) then + + plyTable.m_fGroundTime = CurTime() + + elseif ( ( CurTime() - plyTable.m_fGroundTime ) > 0 && velocity:Length2DSqr() < 0.25 ) then + + plyTable.m_bJumping = true + plyTable.m_bFirstJumpFrame = false + plyTable.m_flJumpStartTime = 0 + + end + end + + if ( plyTable.m_bJumping ) then + + if ( plyTable.m_bFirstJumpFrame ) then + + plyTable.m_bFirstJumpFrame = false + ply:AnimRestartMainSequence() + + end + + if ( ( ply:WaterLevel() >= 2 ) || ( ( CurTime() - plyTable.m_flJumpStartTime ) > 0.2 && ply:OnGround() ) ) then + + plyTable.m_bJumping = false + plyTable.m_fGroundTime = nil + ply:AnimRestartMainSequence() + + end + + if ( plyTable.m_bJumping ) then + plyTable.CalcIdeal = ACT_MP_JUMP + return true + end + end + + return false + +end + +function GM:HandlePlayerDucking( ply, velocity, plyTable ) + + if ( !plyTable ) then plyTable = ply:GetTable() end + + if ( !ply:IsFlagSet( FL_ANIMDUCKING ) ) then return false end + + if ( velocity:Length2DSqr() > 0.25 ) then + plyTable.CalcIdeal = ACT_MP_CROUCHWALK + else + plyTable.CalcIdeal = ACT_MP_CROUCH_IDLE + end + + return true + +end + +function GM:HandlePlayerNoClipping( ply, velocity, plyTable ) + + if ( !plyTable ) then plyTable = ply:GetTable() end + + if ( ply:GetMoveType() != MOVETYPE_NOCLIP || ply:InVehicle() ) then + + if ( plyTable.m_bWasNoclipping ) then + + plyTable.m_bWasNoclipping = nil + ply:AnimResetGestureSlot( GESTURE_SLOT_CUSTOM ) + if ( CLIENT ) then ply:SetIK( true ) end + + end + + return + + end + + if ( !plyTable.m_bWasNoclipping ) then + + ply:AnimRestartGesture( GESTURE_SLOT_CUSTOM, ACT_GMOD_NOCLIP_LAYER, false ) + if ( CLIENT ) then ply:SetIK( false ) end + + end + + return true + +end + +function GM:HandlePlayerVaulting( ply, velocity, plyTable ) + + if ( !plyTable ) then plyTable = ply:GetTable() end + + if ( velocity:LengthSqr() < 1000000 ) then return end + if ( ply:IsOnGround() ) then return end + + plyTable.CalcIdeal = ACT_MP_SWIM + + return true + +end + +function GM:HandlePlayerSwimming( ply, velocity, plyTable ) + + if ( !plyTable ) then plyTable = ply:GetTable() end + + if ( ply:WaterLevel() < 2 || ply:IsOnGround() ) then + plyTable.m_bInSwim = false + return false + end + + plyTable.CalcIdeal = ACT_MP_SWIM + plyTable.m_bInSwim = true + + return true + +end + +function GM:HandlePlayerLanding( ply, velocity, WasOnGround ) + + if ( ply:GetMoveType() == MOVETYPE_NOCLIP ) then return end + + if ( ply:IsOnGround() && !WasOnGround ) then + ply:AnimRestartGesture( GESTURE_SLOT_JUMP, ACT_LAND, true ) + end + +end + +function GM:HandlePlayerDriving( ply, plyTable ) + + if ( !plyTable ) then plyTable = ply:GetTable() end + + -- The player must have a parent to be in a vehicle. If there's no parent, we are in the exit anim, so don't do sitting in 3rd person anymore + if ( !ply:InVehicle() || !IsValid( ply:GetParent() ) ) then return false end + + local pVehicle = ply:GetVehicle() + + if ( !pVehicle.HandleAnimation && pVehicle.GetVehicleClass ) then + local listEntr = list.GetEntry( "Vehicles", pVehicle:GetVehicleClass() ) + if ( listEntr && listEntr.Members && listEntr.Members.HandleAnimation ) then + pVehicle.HandleAnimation = listEntr.Members.HandleAnimation + else + pVehicle.HandleAnimation = true -- Prevent this if block from trying to assign HandleAnimation again. + end + end + + if ( isfunction( pVehicle.HandleAnimation ) ) then + local seq = pVehicle:HandleAnimation( ply ) + if ( seq != nil ) then + plyTable.CalcSeqOverride = seq + end + end + + if ( plyTable.CalcSeqOverride == -1 ) then -- pVehicle.HandleAnimation did not give us an animation + local class = pVehicle:GetClass() + if ( class == "prop_vehicle_jeep" ) then + plyTable.CalcSeqOverride = ply:LookupSequence( "drive_jeep" ) + elseif ( class == "prop_vehicle_airboat" ) then + plyTable.CalcSeqOverride = ply:LookupSequence( "drive_airboat" ) + elseif ( class == "prop_vehicle_prisoner_pod" && pVehicle:GetModel() == "models/vehicles/prisoner_pod_inner.mdl" ) then + -- HACK!! + plyTable.CalcSeqOverride = ply:LookupSequence( "drive_pd" ) + else + plyTable.CalcSeqOverride = ply:LookupSequence( "sit_rollercoaster" ) + end + end + + local use_anims = ( plyTable.CalcSeqOverride == ply:LookupSequence( "sit_rollercoaster" ) || plyTable.CalcSeqOverride == ply:LookupSequence( "sit" ) ) + if ( use_anims && ply:GetAllowWeaponsInVehicle() && IsValid( ply:GetActiveWeapon() ) ) then + local holdtype = ply:GetActiveWeapon():GetHoldType() + if ( holdtype == "smg" ) then holdtype = "smg1" end + + local seqid = ply:LookupSequence( "sit_" .. holdtype ) + if ( seqid != -1 ) then + plyTable.CalcSeqOverride = seqid + end + end + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:UpdateAnimation() + Desc: Animation updates (pose params etc) should be done here +-----------------------------------------------------------]] +function GM:UpdateAnimation( ply, velocity, maxseqgroundspeed ) + + local len = velocity:Length() + local movement = 1.0 + + if ( len > 0.2 ) then + movement = ( len / maxseqgroundspeed ) + end + + local rate = math.min( movement, 2 ) + + -- if we're under water we want to constantly be swimming.. + if ( ply:WaterLevel() >= 2 ) then + rate = math.max( rate, 0.5 ) + elseif ( !ply:IsOnGround() && len >= 1000 ) then + rate = 0.1 + end + + ply:SetPlaybackRate( rate ) + + -- We only need to do this clientside.. + if ( CLIENT ) then + if ( ply:InVehicle() ) then + -- + -- This is used for the 'rollercoaster' arms + -- + local Vehicle = ply:GetVehicle() + local Velocity = Vehicle:GetVelocity() + local fwd = Vehicle:GetUp() + local dp = fwd:Dot( Vector( 0, 0, 1 ) ) + + ply:SetPoseParameter( "vertical_velocity", ( dp < 0 && dp || 0 ) + fwd:Dot( Velocity ) * 0.005 ) + + -- Pass the vehicles steer param down to the player + local steer = Vehicle:GetPoseParameter( "vehicle_steer" ) + steer = steer * 2 - 1 -- convert from 0..1 to -1..1 + if ( Vehicle:GetClass() == "prop_vehicle_prisoner_pod" ) then steer = 0 ply:SetPoseParameter( "aim_yaw", math.NormalizeAngle( ply:GetAimVector():Angle().y - Vehicle:GetAngles().y - 90 ) ) end + ply:SetPoseParameter( "vehicle_steer", steer ) + + end + + GAMEMODE:GrabEarAnimation( ply ) + GAMEMODE:MouthMoveAnimation( ply ) + end + +end + +-- +-- If you don't want the player to grab his ear in your gamemode then +-- just override this. +-- +function GM:GrabEarAnimation( ply, plyTable ) + + if ( !plyTable ) then plyTable = ply:GetTable() end + + plyTable.ChatGestureWeight = plyTable.ChatGestureWeight || 0 + + -- Don't show this when we're playing a taunt! + if ( ply:IsPlayingTaunt() ) then return end + + if ( ply:IsTyping() ) then + plyTable.ChatGestureWeight = math.Approach( plyTable.ChatGestureWeight, 1, FrameTime() * 5.0 ) + else + plyTable.ChatGestureWeight = math.Approach( plyTable.ChatGestureWeight, 0, FrameTime() * 5.0 ) + end + + if ( plyTable.ChatGestureWeight > 0 ) then + + ply:AnimRestartGesture( GESTURE_SLOT_VCD, ACT_GMOD_IN_CHAT, true ) + ply:AnimSetGestureWeight( GESTURE_SLOT_VCD, plyTable.ChatGestureWeight ) + + end + +end + +-- +-- Moves the mouth when talking on voicecom +-- +function GM:MouthMoveAnimation( ply ) + + local flexes = { + ply:GetFlexIDByName( "jaw_drop" ), + ply:GetFlexIDByName( "left_part" ), + ply:GetFlexIDByName( "right_part" ), + ply:GetFlexIDByName( "left_mouth_drop" ), + ply:GetFlexIDByName( "right_mouth_drop" ) + } + + local weight = ply:IsSpeaking() && math.Clamp( ply:VoiceVolume() * 2, 0, 2 ) || 0 + + for k, v in ipairs( flexes ) do + + ply:SetFlexWeight( v, weight ) + + end + +end + +function GM:CalcMainActivity( ply, velocity ) + + local plyTable = ply:GetTable() + plyTable.CalcIdeal = ACT_MP_STAND_IDLE + plyTable.CalcSeqOverride = -1 + + self:HandlePlayerLanding( ply, velocity, plyTable.m_bWasOnGround ) + + if !( self:HandlePlayerNoClipping( ply, velocity, plyTable ) || + self:HandlePlayerDriving( ply, plyTable ) || + self:HandlePlayerVaulting( ply, velocity, plyTable ) || + self:HandlePlayerJumping( ply, velocity, plyTable ) || + self:HandlePlayerSwimming( ply, velocity, plyTable ) || + self:HandlePlayerDucking( ply, velocity, plyTable ) ) then + + local len2d = velocity:Length2DSqr() + if ( len2d > 22500 ) then plyTable.CalcIdeal = ACT_MP_RUN elseif ( len2d > 0.25 ) then plyTable.CalcIdeal = ACT_MP_WALK end + + end + + plyTable.m_bWasOnGround = ply:IsOnGround() + plyTable.m_bWasNoclipping = ( ply:GetMoveType() == MOVETYPE_NOCLIP && !ply:InVehicle() ) + + return plyTable.CalcIdeal, plyTable.CalcSeqOverride + +end + +local IdleActivity = ACT_HL2MP_IDLE +local IdleActivityTranslate = {} +IdleActivityTranslate[ ACT_MP_STAND_IDLE ] = IdleActivity +IdleActivityTranslate[ ACT_MP_WALK ] = IdleActivity + 1 +IdleActivityTranslate[ ACT_MP_RUN ] = IdleActivity + 2 +IdleActivityTranslate[ ACT_MP_CROUCH_IDLE ] = IdleActivity + 3 +IdleActivityTranslate[ ACT_MP_CROUCHWALK ] = IdleActivity + 4 +IdleActivityTranslate[ ACT_MP_ATTACK_STAND_PRIMARYFIRE ] = IdleActivity + 5 +IdleActivityTranslate[ ACT_MP_ATTACK_CROUCH_PRIMARYFIRE ] = IdleActivity + 5 +IdleActivityTranslate[ ACT_MP_RELOAD_STAND ] = IdleActivity + 6 +IdleActivityTranslate[ ACT_MP_RELOAD_CROUCH ] = IdleActivity + 6 +IdleActivityTranslate[ ACT_MP_JUMP ] = ACT_HL2MP_JUMP_SLAM +IdleActivityTranslate[ ACT_MP_SWIM ] = IdleActivity + 9 +IdleActivityTranslate[ ACT_LAND ] = ACT_LAND + +-- it is preferred you return ACT_MP_* in CalcMainActivity, and if you have a specific need to not translate through the weapon do it here +function GM:TranslateActivity( ply, act ) + + local newact = ply:TranslateWeaponActivity( act ) + + -- select idle anims if the weapon didn't decide + if ( act == newact ) then + return IdleActivityTranslate[ act ] + end + + return newact + +end + +function GM:DoAnimationEvent( ply, event, data ) + + if ( event == PLAYERANIMEVENT_ATTACK_PRIMARY ) then + + if ply:IsFlagSet( FL_ANIMDUCKING ) then + ply:AnimRestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_CROUCH_PRIMARYFIRE, true ) + else + ply:AnimRestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_ATTACK_STAND_PRIMARYFIRE, true ) + end + + return ACT_VM_PRIMARYATTACK + + elseif ( event == PLAYERANIMEVENT_ATTACK_SECONDARY ) then + + -- there is no gesture, so just fire off the VM event + return ACT_VM_SECONDARYATTACK + + elseif ( event == PLAYERANIMEVENT_RELOAD ) then + + if ply:IsFlagSet( FL_ANIMDUCKING ) then + ply:AnimRestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_CROUCH, true ) + else + ply:AnimRestartGesture( GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_MP_RELOAD_STAND, true ) + end + + return ACT_INVALID + + elseif ( event == PLAYERANIMEVENT_JUMP ) then + + ply.m_bJumping = true + ply.m_bFirstJumpFrame = true + ply.m_flJumpStartTime = CurTime() + + ply:AnimRestartMainSequence() + + return ACT_INVALID + + elseif ( event == PLAYERANIMEVENT_CANCEL_RELOAD ) then + + ply:AnimResetGestureSlot( GESTURE_SLOT_ATTACK_AND_RELOAD ) + + return ACT_INVALID + end + +end diff --git a/gamemodes/base/gamemode/cl_deathnotice.lua b/gamemodes/base/gamemode/cl_deathnotice.lua new file mode 100644 index 0000000..97bdc8b --- /dev/null +++ b/gamemodes/base/gamemode/cl_deathnotice.lua @@ -0,0 +1,292 @@ + +local hud_deathnotice_time = CreateConVar( "hud_deathnotice_time", "6", FCVAR_REPLICATED, "Amount of time to show death notice (kill feed) for" ) +local cl_drawhud = GetConVar( "cl_drawhud" ) + +-- These are our kill icons +local Color_Icon = Color( 255, 80, 0, 255 ) +local NPC_Color_Enemy = Color( 250, 50, 50, 255 ) +local NPC_Color_Friendly = Color( 50, 200, 50, 255 ) + +killicon.AddFont( "prop_physics", "HL2MPTypeDeath", "9", Color_Icon, 0.52 ) +killicon.AddFont( "weapon_smg1", "HL2MPTypeDeath", "/", Color_Icon, 0.55 ) +killicon.AddFont( "weapon_357", "HL2MPTypeDeath", ".", Color_Icon, 0.55 ) +killicon.AddFont( "weapon_ar2", "HL2MPTypeDeath", "2", Color_Icon, 0.6 ) +killicon.AddFont( "crossbow_bolt", "HL2MPTypeDeath", "1", Color_Icon, 0.5 ) +killicon.AddFont( "weapon_shotgun", "HL2MPTypeDeath", "0", Color_Icon, 0.45 ) +killicon.AddFont( "rpg_missile", "HL2MPTypeDeath", "3", Color_Icon, 0.35 ) +killicon.AddFont( "npc_grenade_frag", "HL2MPTypeDeath", "4", Color_Icon, 0.56 ) +killicon.AddFont( "weapon_pistol", "HL2MPTypeDeath", "-", Color_Icon, 0.52 ) +killicon.AddFont( "prop_combine_ball", "HL2MPTypeDeath", "8", Color_Icon, 0.5 ) +killicon.AddFont( "grenade_ar2", "HL2MPTypeDeath", "7", Color_Icon, 0.35 ) +killicon.AddFont( "weapon_stunstick", "HL2MPTypeDeath", "!", Color_Icon, 0.6 ) +killicon.AddFont( "npc_satchel", "HL2MPTypeDeath", "*", Color_Icon, 0.53 ) +killicon.AddAlias( "npc_tripmine", "npc_satchel" ) +killicon.AddFont( "weapon_crowbar", "HL2MPTypeDeath", "6", Color_Icon, 0.45 ) +killicon.AddFont( "weapon_physcannon", "HL2MPTypeDeath", ",", Color_Icon, 0.55 ) + +-- Prop like objects get the prop kill icon +killicon.AddAlias( "prop_ragdoll", "prop_physics" ) +killicon.AddAlias( "prop_physics_respawnable", "prop_physics" ) +killicon.AddAlias( "func_physbox", "prop_physics" ) +killicon.AddAlias( "func_physbox_multiplayer", "prop_physics" ) +killicon.AddAlias( "trigger_vphysics_motion", "prop_physics" ) +killicon.AddAlias( "func_movelinear", "prop_physics" ) +killicon.AddAlias( "func_plat", "prop_physics" ) +killicon.AddAlias( "func_platrot", "prop_physics" ) +killicon.AddAlias( "func_pushable", "prop_physics" ) +killicon.AddAlias( "func_rotating", "prop_physics" ) +killicon.AddAlias( "func_rot_button", "prop_physics" ) +killicon.AddAlias( "func_tracktrain", "prop_physics" ) +killicon.AddAlias( "func_train", "prop_physics" ) + +local function HandleAchievements( victimType ) + + -- Try to find by name + for id, tab in pairs( list.Get( "NPC" ) ) do + if ( tab.Name == victimType ) then + victimType = tab.Class + end + end + -- If fails, try to omit the translation system # + if ( victimType:StartsWith( "#" ) ) then + victimType = victimType:sub( 2 ) + end + + local bIsEnemy = IsEnemyEntityName( victimType ) + local bIsFriend = IsFriendEntityName( victimType ) + + if ( bIsEnemy ) then + achievements.IncBaddies() + end + + if ( bIsFriend ) then + achievements.IncGoodies() + end + + if ( !bIsFriend && !bIsEnemy ) then + achievements.IncBystander() + end + +end + +-- Backwards compatiblity for addons +net.Receive( "PlayerKilledByPlayer", function() + + local victim = net.ReadEntity() + local inflictor = net.ReadString() + local attacker = net.ReadEntity() + + if ( !IsValid( attacker ) ) then return end + if ( !IsValid( victim ) ) then return end + + hook.Run( "AddDeathNotice", attacker:Name(), attacker:Team(), inflictor, victim:Name(), victim:Team(), 0 ) + +end ) + +net.Receive( "PlayerKilledSelf", function() + + local victim = net.ReadEntity() + if ( !IsValid( victim ) ) then return end + + hook.Run( "AddDeathNotice", nil, 0, "suicide", victim:Name(), victim:Team(), 0 ) + +end ) + +net.Receive( "PlayerKilled", function() + + local victim = net.ReadEntity() + if ( !IsValid( victim ) ) then return end + + local inflictor = net.ReadString() + local attacker = net.ReadString() + + hook.Run( "AddDeathNotice", "#" .. attacker, -1, inflictor, victim:Name(), victim:Team(), 0 ) + +end ) + +net.Receive( "PlayerKilledNPC", function() + + local victimtype = net.ReadString() + local inflictor = net.ReadString() + local attacker = net.ReadEntity() + + -- + -- For some reason the killer isn't known to us, so don't proceed. + -- + if ( !IsValid( attacker ) ) then return end + + hook.Run( "AddDeathNotice", attacker:Name(), attacker:Team(), inflictor, "#" .. victimtype, -1, 0 ) + + local bIsLocalPlayer = ( IsValid( attacker ) && attacker == LocalPlayer() ) + if ( bIsLocalPlayer ) then + HandleAchievements( victimtype ) + end + +end ) + +net.Receive( "NPCKilledNPC", function() + + local victim = "#" .. net.ReadString() + local inflictor = net.ReadString() + local attacker = "#" .. net.ReadString() + + hook.Run( "AddDeathNotice", attacker, -1, inflictor, victim, -1, 0 ) + +end ) + +-- The new way +DEATH_NOTICE_FRIENDLY_VICTIM = 1 +DEATH_NOTICE_FRIENDLY_ATTACKER = 2 +--DEATH_NOTICE_HEADSHOT = 4 +--DEATH_NOTICE_PENETRATION = 8 +net.Receive( "DeathNoticeEvent", function() + + local attacker = nil + local attackerType = net.ReadUInt( 2 ) + if ( attackerType == 1 ) then + attacker = net.ReadString() + elseif ( attackerType == 2 ) then + attacker = net.ReadEntity() + end + + local inflictor = net.ReadString() + + local victim = nil + local victimType = net.ReadUInt( 2 ) + if ( victimType == 1 ) then + victim = net.ReadString() + elseif ( victimType == 2 ) then + victim = net.ReadEntity() + end + + local flags = net.ReadUInt( 8 ) + + local bIsLocalPlayer = ( isentity( attacker ) && IsValid( attacker ) && attacker == LocalPlayer() ) + if ( bIsLocalPlayer && isstring( victim ) ) then + HandleAchievements( victim ) + end + + local team_a = -1 + local team_v = -1 + if ( bit.band( flags, DEATH_NOTICE_FRIENDLY_VICTIM ) != 0 ) then team_v = -2 end + if ( bit.band( flags, DEATH_NOTICE_FRIENDLY_ATTACKER ) != 0 ) then team_a = -2 end + + -- Handle player entities + if ( isentity( attacker ) and attacker:IsValid() and attacker:IsPlayer() ) then team_a = attacker:Team() attacker = attacker:Name() end + if ( isentity( victim ) and victim:IsValid() and victim:IsPlayer() ) then team_v = victim:Team() victim = victim:Name() end + + -- Handle other entities + if ( isentity( attacker ) and attacker:IsValid() ) then attacker = attacker:GetClass() end + if ( isentity( victim ) and victim:IsValid() ) then victim = victim:GetClass() end + + hook.Run( "AddDeathNotice", attacker, team_a, inflictor, victim, team_v, flags ) + +end ) + +local Deaths = {} + +local function getDeathColor( teamID, target ) + + if ( teamID == -1 ) then + return table.Copy( NPC_Color_Enemy ) + end + + if ( teamID == -2 ) then + return table.Copy( NPC_Color_Friendly ) + end + + return table.Copy( team.GetColor( teamID ) ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:AddDeathNotice( Attacker, team1, Inflictor, Victim, team2, flags ) + Desc: Adds an death notice entry +-----------------------------------------------------------]] +function GM:AddDeathNotice( attacker, team1, inflictor, victim, team2, flags ) + + if ( inflictor == "suicide" ) then attacker = nil end + + local Death = {} + Death.time = CurTime() + + Death.left = attacker + Death.right = victim + Death.icon = inflictor + Death.flags = flags + + Death.color1 = getDeathColor( team1, Death.left ) + Death.color2 = getDeathColor( team2, Death.right ) + + table.insert( Deaths, Death ) + +end + +local function DrawDeath( x, y, death, time ) + + local w, h = killicon.GetSize( death.icon ) + if ( !w or !h ) then return end + + local fadeout = ( death.time + time ) - CurTime() + + local alpha = math.Clamp( fadeout * 255, 0, 255 ) + death.color1.a = alpha + death.color2.a = alpha + + -- Draw Icon + killicon.Render( x - w / 2, y, death.icon, alpha ) + + -- Draw KILLER + if ( death.left ) then + draw.SimpleText( death.left, "ChatFont", x - ( w / 2 ) - 16, y + h / 2, death.color1, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER ) + end + + -- Draw VICTIM + draw.SimpleText( death.right, "ChatFont", x + ( w / 2 ) + 16, y + h / 2, death.color2, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER ) + + return math.ceil( y + h * 0.75 ) + + -- Font killicons are too high when height corrected, and changing that is not backwards compatible + --return math.ceil( y + math.max( h, 28 ) ) + +end + +function GM:DrawDeathNotice( x, y ) + + if ( cl_drawhud:GetInt() == 0 ) then return end + + local time = hud_deathnotice_time:GetFloat() + local reset = Deaths[1] != nil -- Don't reset it if there's nothing in it + + x = x * ScrW() + y = y * ScrH() + + -- Draw + for k, Death in ipairs( Deaths ) do + + if ( Death.time + time > CurTime() ) then + + if ( Death.lerp ) then + x = x * 0.3 + Death.lerp.x * 0.7 + y = y * 0.3 + Death.lerp.y * 0.7 + end + + Death.lerp = Death.lerp or {} + Death.lerp.x = x + Death.lerp.y = y + + y = DrawDeath( math.floor( x ), math.floor( y ), Death, time ) + reset = false + + end + + end + + -- We want to maintain the order of the table so instead of removing + -- expired entries one by one we will just clear the entire table + -- once everything is expired. + if ( reset ) then + Deaths = {} + end + +end diff --git a/gamemodes/base/gamemode/cl_hudpickup.lua b/gamemodes/base/gamemode/cl_hudpickup.lua new file mode 100644 index 0000000..48ffcd3 --- /dev/null +++ b/gamemodes/base/gamemode/cl_hudpickup.lua @@ -0,0 +1,173 @@ + +GM.PickupHistory = {} +GM.PickupHistoryLast = 0 +GM.PickupHistoryTop = ScrH() / 2 +GM.PickupHistoryWide = 300 +GM.PickupHistoryCorner = surface.GetTextureID( "gui/corner8" ) + +local function AddGenericPickup( self, itemname ) + local pickup = {} + pickup.time = CurTime() + pickup.name = itemname + pickup.holdtime = 5 + pickup.font = "DermaDefaultBold" + pickup.fadein = 0.04 + pickup.fadeout = 0.3 + + surface.SetFont( pickup.font ) + local w, h = surface.GetTextSize( pickup.name ) + pickup.height = h + pickup.width = w + + --[[if ( self.PickupHistoryLast >= pickup.time ) then + pickup.time = self.PickupHistoryLast + 0.05 + end]] + + table.insert( self.PickupHistory, pickup ) + self.PickupHistoryLast = pickup.time + + return pickup +end + +--[[--------------------------------------------------------- + Name: gamemode:HUDWeaponPickedUp( wep ) + Desc: The game wants you to draw on the HUD that a weapon has been picked up +-----------------------------------------------------------]] +function GM:HUDWeaponPickedUp( wep ) + + if ( !IsValid( LocalPlayer() ) || !LocalPlayer():Alive() ) then return end + if ( !IsValid( wep ) ) then return end + if ( !isfunction( wep.GetPrintName ) ) then return end + + local pickup = AddGenericPickup( self, wep:GetPrintName() ) + pickup.color = Color( 255, 200, 50, 255 ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:HUDItemPickedUp( itemname ) + Desc: An item has been picked up.. +-----------------------------------------------------------]] +function GM:HUDItemPickedUp( itemname ) + + if ( !IsValid( LocalPlayer() ) || !LocalPlayer():Alive() ) then return end + + local pickup = AddGenericPickup( self, "#" .. itemname ) + pickup.color = Color( 180, 255, 180, 255 ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:HUDAmmoPickedUp( itemname, amount ) + Desc: Ammo has been picked up.. +-----------------------------------------------------------]] +function GM:HUDAmmoPickedUp( itemname, amount ) + + if ( !IsValid( LocalPlayer() ) || !LocalPlayer():Alive() ) then return end + + -- Try to tack it onto an exisiting ammo pickup + if ( self.PickupHistory ) then + + for k, v in pairs( self.PickupHistory ) do + + if ( v.name == "#" .. itemname .. "_ammo" ) then + + v.amount = tostring( tonumber( v.amount ) + amount ) + v.time = CurTime() - v.fadein + return + + end + + end + + end + + local pickup = AddGenericPickup( self, "#" .. itemname .. "_ammo" ) + pickup.color = Color( 180, 200, 255, 255 ) + pickup.amount = tostring( amount ) + + local w, h = surface.GetTextSize( pickup.amount ) + pickup.width = pickup.width + w + 16 + +end + +function GM:HUDDrawPickupHistory() + + if ( self.PickupHistory == nil ) then return end + + local x, y = ScrW() - self.PickupHistoryWide - 20, self.PickupHistoryTop + local tall = 0 + local wide = 0 + + for k, v in pairs( self.PickupHistory ) do + + if ( !istable( v ) ) then + + Msg( tostring( v ) .. "\n" ) + PrintTable( self.PickupHistory ) + self.PickupHistory[ k ] = nil + return + end + + if ( v.time < CurTime() ) then + + if ( v.y == nil ) then v.y = y end + + v.y = ( v.y * 5 + y ) / 6 + + local delta = ( v.time + v.holdtime ) - CurTime() + delta = delta / v.holdtime + + local alpha = 255 + local colordelta = math.Clamp( delta, 0.6, 0.7 ) + + -- Fade in/out + if ( delta > 1 - v.fadein ) then + alpha = math.Clamp( ( 1.0 - delta ) * ( 255 / v.fadein ), 0, 255 ) + elseif ( delta < v.fadeout ) then + alpha = math.Clamp( delta * ( 255 / v.fadeout ), 0, 255 ) + end + + v.x = x + self.PickupHistoryWide - ( self.PickupHistoryWide * ( alpha / 255 ) ) + + local rx, ry, rw, rh = math.Round( v.x - 4 ), math.Round( v.y - ( v.height / 2 ) - 4 ), math.Round( self.PickupHistoryWide + 9 ), math.Round( v.height + 8 ) + local bordersize = 8 + + surface.SetTexture( self.PickupHistoryCorner ) + + surface.SetDrawColor( v.color.r, v.color.g, v.color.b, alpha ) + surface.DrawTexturedRectRotated( rx + bordersize / 2, ry + bordersize / 2, bordersize, bordersize, 0 ) + surface.DrawTexturedRectRotated( rx + bordersize / 2, ry + rh -bordersize / 2, bordersize, bordersize, 90 ) + surface.DrawRect( rx, ry + bordersize, bordersize, rh-bordersize * 2 ) + surface.DrawRect( rx + bordersize, ry, v.height - 4, rh ) + + surface.SetDrawColor( 230 * colordelta, 230 * colordelta, 230 * colordelta, alpha ) + surface.DrawTexturedRectRotated( rx + rw - bordersize / 2 , ry + rh - bordersize / 2, bordersize, bordersize, 180 ) + surface.DrawTexturedRectRotated( rx + rw - bordersize / 2 , ry + bordersize / 2, bordersize, bordersize, 270 ) + surface.DrawRect( rx + rw - bordersize, ry + bordersize, bordersize, rh-bordersize * 2 ) + surface.DrawRect( rx + bordersize + v.height - 4, ry, rw - ( v.height - 4 ) - bordersize * 2, rh ) + + draw.SimpleText( v.name, v.font, v.x + v.height + 9, ry + ( rh / 2 ) + 1, Color( 0, 0, 0, alpha * 0.5 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER ) + draw.SimpleText( v.name, v.font, v.x + v.height + 8, ry + ( rh / 2 ), Color( 255, 255, 255, alpha ), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER ) + + if ( v.amount ) then + + draw.SimpleText( v.amount, v.font, v.x + self.PickupHistoryWide + 1, ry + ( rh / 2 ) + 1, Color( 0, 0, 0, alpha * 0.5 ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER ) + draw.SimpleText( v.amount, v.font, v.x + self.PickupHistoryWide, ry + ( rh / 2 ), Color( 255, 255, 255, alpha ), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER ) + + end + + y = y + ( v.height + 16 ) + tall = tall + v.height + 18 + wide = math.max( wide, v.width + v.height + 24 ) + + if ( alpha == 0 ) then self.PickupHistory[ k ] = nil end + + end + + end + + self.PickupHistoryTop = ( self.PickupHistoryTop * 5 + ( ScrH() * 0.75 - tall ) / 2 ) / 6 + self.PickupHistoryWide = ( self.PickupHistoryWide * 5 + wide ) / 6 + +end diff --git a/gamemodes/base/gamemode/cl_init.lua b/gamemodes/base/gamemode/cl_init.lua new file mode 100644 index 0000000..07c43f1 --- /dev/null +++ b/gamemodes/base/gamemode/cl_init.lua @@ -0,0 +1,736 @@ + +include( "shared.lua" ) +include( "cl_scoreboard.lua" ) +include( "cl_targetid.lua" ) +include( "cl_hudpickup.lua" ) +include( "cl_spawnmenu.lua" ) +include( "cl_deathnotice.lua" ) +include( "cl_pickteam.lua" ) +include( "cl_voice.lua" ) + +--[[--------------------------------------------------------- + Name: gamemode:Initialize() + Desc: Called immediately after starting the gamemode +-----------------------------------------------------------]] +function GM:Initialize() + + GAMEMODE.ShowScoreboard = false + +end + +--[[--------------------------------------------------------- + Name: gamemode:InitPostEntity() + Desc: Called as soon as all map entities have been spawned +-----------------------------------------------------------]] +function GM:InitPostEntity() +end + +--[[--------------------------------------------------------- + Name: gamemode:Think() + Desc: Called every frame +-----------------------------------------------------------]] +function GM:Think() +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerBindPress() + Desc: A player pressed a bound key - return true to override action +-----------------------------------------------------------]] +function GM:PlayerBindPress( pl, bind, down ) + + return false + +end + +--[[--------------------------------------------------------- + Name: gamemode:HUDShouldDraw( name ) + Desc: return true if we should draw the named element +-----------------------------------------------------------]] +function GM:HUDShouldDraw( name ) + + -- Allow the weapon to override this + local ply = LocalPlayer() + if ( IsValid( ply ) ) then + + local wep = ply:GetActiveWeapon() + + if ( IsValid( wep ) ) then + + local fShouldDraw = wep.HUDShouldDraw + + if ( isfunction( fShouldDraw ) ) then + + local ret = fShouldDraw( wep, name ) + if ( ret != nil ) then return ret end + + end + + end + + end + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:HUDPaint() + Desc: Use this section to paint your HUD +-----------------------------------------------------------]] +function GM:HUDPaint() + + hook.Run( "HUDDrawTargetID" ) + hook.Run( "HUDDrawPickupHistory" ) + hook.Run( "DrawDeathNotice", 0.85, 0.04 ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:HUDPaintBackground() + Desc: Same as HUDPaint except drawn before +-----------------------------------------------------------]] +function GM:HUDPaintBackground() +end + +--[[--------------------------------------------------------- + Name: gamemode:GUIMouseReleased( mousecode ) + Desc: The mouse was double clicked +-----------------------------------------------------------]] +function GM:GUIMouseDoublePressed( mousecode, AimVector ) + -- We don't capture double clicks by default, + -- We just treat them as regular presses + GAMEMODE:GUIMousePressed( mousecode, AimVector ) +end + +--[[--------------------------------------------------------- + Name: gamemode:ShutDown( ) + Desc: Called when the Lua system is about to shut down +-----------------------------------------------------------]] +function GM:ShutDown() +end + +--[[--------------------------------------------------------- + Name: gamemode:RenderScreenspaceEffects( ) + Desc: Bloom etc should be drawn here (or using this hook) +-----------------------------------------------------------]] +function GM:RenderScreenspaceEffects() +end + +--[[--------------------------------------------------------- + Name: gamemode:GetTeamColor( ent ) + Desc: Return the color for this ent's team + This is for chat and deathnotice text +-----------------------------------------------------------]] +function GM:GetTeamColor( ent ) + + local team = TEAM_UNASSIGNED + if ( ent.Team ) then team = ent:Team() end + return GAMEMODE:GetTeamNumColor( team ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:GetTeamNumColor( num ) + Desc: returns the colour for this team num +-----------------------------------------------------------]] +function GM:GetTeamNumColor( num ) + + return team.GetColor( num ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:OnPlayerChat() + Process the player's chat.. return true for no default +-----------------------------------------------------------]] +function GM:OnPlayerChat( player, strText, bTeamOnly, bPlayerIsDead ) + + -- + -- I've made this all look more complicated than it is. Here's the easy version + -- + -- chat.AddText( player, color_white, ": ", strText ) + -- + + local tab = {} + + if ( bPlayerIsDead ) then + table.insert( tab, Color( 255, 30, 40 ) ) + table.insert( tab, language.GetPhrase( "chat.dead" ) .. " " ) + end + + if ( bTeamOnly ) then + table.insert( tab, Color( 30, 160, 40 ) ) + table.insert( tab, language.GetPhrase( "chat.team" ) .. " " ) + end + + if ( IsValid( player ) ) then + table.insert( tab, player ) + else + table.insert( tab, language.GetPhrase( "chat.console" ) ) + end + + local filter_context = TEXT_FILTER_GAME_CONTENT + if ( bit.band( GetConVarNumber( "cl_chatfilters" ), 64 ) != 0 ) then filter_context = TEXT_FILTER_CHAT end + + table.insert( tab, color_white ) + table.insert( tab, ": " .. util.FilterText( strText, filter_context, IsValid( player ) and player or nil ) ) + + chat.AddText( unpack( tab ) ) + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:OnChatTab( str ) + Desc: Tab is pressed when typing (Auto-complete names, IRC style) +-----------------------------------------------------------]] +function GM:OnChatTab( str ) + + str = string.TrimRight(str) + + local LastWord + for word in string.gmatch( str, "[^ ]+" ) do + LastWord = word + end + + if ( LastWord == nil ) then return str end + + for k, v in player.Iterator() do + + local nickname = v:Nick() + + if ( string.len( LastWord ) < string.len( nickname ) && string.find( string.lower( nickname ), string.lower( LastWord ), 0, true ) == 1 ) then + + str = string.sub( str, 1, ( string.len( LastWord ) * -1 ) - 1 ) + str = str .. nickname + return str + + end + + end + + return str + +end + +--[[--------------------------------------------------------- + Name: gamemode:StartChat( teamsay ) + Desc: Start Chat. + + If you want to display your chat shit different here's what you'd do: + In StartChat show your text box and return true to hide the default + Update the text in your box with the text passed to ChatTextChanged + Close and clear your text box when FinishChat is called. + Return true in ChatText to not show the default chat text + +-----------------------------------------------------------]] +function GM:StartChat( teamsay ) + + return false + +end + +--[[--------------------------------------------------------- + Name: gamemode:FinishChat() +-----------------------------------------------------------]] +function GM:FinishChat() +end + +--[[--------------------------------------------------------- + Name: gamemode:ChatTextChanged( text) +-----------------------------------------------------------]] +function GM:ChatTextChanged( text ) +end + +--[[--------------------------------------------------------- + Name: ChatText + Allows override of the chat text +-----------------------------------------------------------]] +function GM:ChatText( playerindex, playername, text, filter ) + + if ( filter == "chat" ) then + Msg( playername, ": ", text, "\n" ) + else + Msg( text, "\n" ) + end + + return false + +end + +--[[--------------------------------------------------------- + Name: gamemode:PostProcessPermitted( str ) + Desc: return true/false depending on whether this post process should be allowed +-----------------------------------------------------------]] +function GM:PostProcessPermitted( str ) + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PostRenderVGUI( ) + Desc: Called after VGUI has been rendered +-----------------------------------------------------------]] +function GM:PostRenderVGUI() +end + +--[[--------------------------------------------------------- + Name: gamemode:PreRender( ) + Desc: Called before all rendering + Return true to NOT render this frame for some reason (danger!) +-----------------------------------------------------------]] +function GM:PreRender() + return false +end + +--[[--------------------------------------------------------- + Name: gamemode:PostRender( ) + Desc: Called after all rendering +-----------------------------------------------------------]] +function GM:PostRender() +end + +--[[--------------------------------------------------------- + Name: gamemode:RenderScene( ) + Desc: Render the scene +-----------------------------------------------------------]] +function GM:RenderScene( origin, angle, fov ) +end + +--[[--------------------------------------------------------- + Name: CalcVehicleView +-----------------------------------------------------------]] +function GM:CalcVehicleView( Vehicle, ply, view ) + + if ( Vehicle.GetThirdPersonMode == nil || ply:GetViewEntity() != ply ) then + -- This shouldn't ever happen. + return + end + + -- + -- If we're not in third person mode - then get outa here stalker + -- + if ( !Vehicle:GetThirdPersonMode() ) then return view end + + -- Don't roll the camera + -- view.angles.roll = 0 + + local mn, mx = Vehicle:GetRenderBounds() + local radius = ( mn - mx ):Length() + local radius = radius + radius * Vehicle:GetCameraDistance() + + -- Trace back from the original eye position, so we don't clip through walls/objects + local TargetOrigin = view.origin + ( view.angles:Forward() * -radius ) + local WallOffset = 4 + + local tr = util.TraceHull( { + start = view.origin, + endpos = TargetOrigin, + filter = function( e ) + local c = e:GetClass() -- Avoid contact with entities that can potentially be attached to the vehicle. Ideally, we should check if "e" is constrained to "Vehicle". + return !c:StartsWith( "prop_physics" ) &&!c:StartsWith( "prop_dynamic" ) && !c:StartsWith( "phys_bone_follower" ) && !c:StartsWith( "prop_ragdoll" ) && !e:IsVehicle() && !c:StartsWith( "gmod_" ) + end, + mins = Vector( -WallOffset, -WallOffset, -WallOffset ), + maxs = Vector( WallOffset, WallOffset, WallOffset ), + } ) + + view.origin = tr.HitPos + view.drawviewer = true + + -- + -- If the trace hit something, put the camera there. + -- + if ( tr.Hit && !tr.StartSolid) then + view.origin = view.origin + tr.HitNormal * WallOffset + end + + return view + +end + +--[[--------------------------------------------------------- + Name: CalcView + Allows override of the default view +-----------------------------------------------------------]] +function GM:CalcView( ply, origin, angles, fov, znear, zfar ) + + local Vehicle = ply:GetVehicle() + local Weapon = ply:GetActiveWeapon() + + local view = { + ["origin"] = origin, + ["angles"] = angles, + ["fov"] = fov, + ["znear"] = znear, + ["zfar"] = zfar, + ["drawviewer"] = false, + } + + -- + -- Let the vehicle override the view and allows the vehicle view to be hooked + -- + if ( IsValid( Vehicle ) ) then return hook.Run( "CalcVehicleView", Vehicle, ply, view ) end + + -- + -- Let drive possibly alter the view + -- + if ( drive.CalcView( ply, view ) ) then return view end + + -- + -- Give the player manager a turn at altering the view + -- + player_manager.RunClass( ply, "CalcView", view ) + + -- Give the active weapon a go at changing the view + if ( IsValid( Weapon ) ) then + + local func = Weapon.CalcView + if ( func ) then + local origin, angles, fov = func( Weapon, ply, Vector( view.origin ), Angle( view.angles ), view.fov ) -- Note: Constructor to copy the object so the child function can't edit it. + view.origin, view.angles, view.fov = origin or view.origin, angles or view.angles, fov or view.fov + end + + end + + return view + +end + +-- +-- If return true: Will draw the local player +-- If return false: Won't draw the local player +-- If return nil: Will carry out default action +-- +function GM:ShouldDrawLocalPlayer( ply ) + + return player_manager.RunClass( ply, "ShouldDrawLocal" ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:AdjustMouseSensitivity() + Desc: Allows you to adjust the mouse sensitivity. + The return is a fraction of the normal sensitivity (0.5 would be half as sensitive) + Return -1 to not override. +-----------------------------------------------------------]] +function GM:AdjustMouseSensitivity( fDefault ) + + local ply = LocalPlayer() + if ( !IsValid( ply ) ) then return -1 end + + local wep = ply:GetActiveWeapon() + if ( wep && wep.AdjustMouseSensitivity ) then + return wep:AdjustMouseSensitivity() + end + + return -1 + +end + +--[[--------------------------------------------------------- + Name: gamemode:ForceDermaSkin() + Desc: Return the name of skin this gamemode should use. + If nil is returned the skin will use default +-----------------------------------------------------------]] +function GM:ForceDermaSkin() + + --return "example" + return nil + +end + +--[[--------------------------------------------------------- + Name: gamemode:PostPlayerDraw() + Desc: The player has just been drawn. +-----------------------------------------------------------]] +function GM:PostPlayerDraw( ply ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PrePlayerDraw() + Desc: The player is just about to be drawn. +-----------------------------------------------------------]] +function GM:PrePlayerDraw( ply ) +end + +--[[--------------------------------------------------------- + Name: gamemode:GetMotionBlurSettings() + Desc: Allows you to edit the motion blur values +-----------------------------------------------------------]] +function GM:GetMotionBlurValues( x, y, fwd, spin ) + + -- fwd = 0.5 + math.sin( CurTime() * 5 ) * 0.5 + + return x, y, fwd, spin + +end + +--[[--------------------------------------------------------- + Name: gamemode:InputMouseApply() + Desc: Allows you to control how moving the mouse affects the view angles +-----------------------------------------------------------]] +function GM:InputMouseApply( cmd, x, y, angle ) + + --angle.roll = angle.roll + 1 + --cmd:SetViewAngles( Ang ) + --return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:OnAchievementAchieved() +-----------------------------------------------------------]] +function GM:OnAchievementAchieved( ply, achid ) + + chat.AddText( ply, Color( 230, 230, 230 ), " earned the achievement ", Color( 255, 200, 0 ), achievements.GetName( achid ) ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PreDrawSkyBox() + Desc: Called before drawing the skybox. Return true to not draw the skybox. +-----------------------------------------------------------]] +function GM:PreDrawSkyBox() + + --return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PostDrawSkyBox() + Desc: Called after drawing the skybox +-----------------------------------------------------------]] +function GM:PostDrawSkyBox() +end + +-- +-- Name: GM:PostDraw2DSkyBox +-- Desc: Called right after the 2D skybox has been drawn - allowing you to draw over it. +-- Arg1: +-- Ret1: +-- +function GM:PostDraw2DSkyBox() +end + +--[[--------------------------------------------------------- + Name: gamemode:PreDrawOpaqueRenderables() + Desc: Called before drawing opaque entities +-----------------------------------------------------------]] +function GM:PreDrawOpaqueRenderables( bDrawingDepth, bDrawingSkybox ) + + -- return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PreDrawOpaqueRenderables() + Desc: Called before drawing opaque entities +-----------------------------------------------------------]] +function GM:PostDrawOpaqueRenderables( bDrawingDepth, bDrawingSkybox ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PreDrawOpaqueRenderables() + Desc: Called before drawing opaque entities +-----------------------------------------------------------]] +function GM:PreDrawTranslucentRenderables( bDrawingDepth, bDrawingSkybox ) + + -- return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PreDrawOpaqueRenderables() + Desc: Called before drawing opaque entities +-----------------------------------------------------------]] +function GM:PostDrawTranslucentRenderables( bDrawingDepth, bDrawingSkybox ) +end + +--[[--------------------------------------------------------- + Name: gamemode:CalcViewModelView() + Desc: Called to set the view model's position +-----------------------------------------------------------]] +function GM:CalcViewModelView( Weapon, ViewModel, OldEyePos, OldEyeAng, EyePos, EyeAng ) + + if ( !IsValid( Weapon ) ) then return end + + local vm_origin, vm_angles = EyePos, EyeAng + + -- Controls the position of all viewmodels + local func = Weapon.GetViewModelPosition + if ( func ) then + local pos, ang = func( Weapon, EyePos*1, EyeAng*1 ) + vm_origin = pos or vm_origin + vm_angles = ang or vm_angles + end + + -- Controls the position of individual viewmodels + func = Weapon.CalcViewModelView + if ( func ) then + local pos, ang = func( Weapon, ViewModel, OldEyePos*1, OldEyeAng*1, EyePos*1, EyeAng*1 ) + vm_origin = pos or vm_origin + vm_angles = ang or vm_angles + end + + return vm_origin, vm_angles + +end + +--[[--------------------------------------------------------- + Name: gamemode:PreDrawViewModel() + Desc: Called before drawing the view model +-----------------------------------------------------------]] +function GM:PreDrawViewModel( ViewModel, Player, Weapon ) + + if ( !IsValid( Weapon ) ) then return false end + + player_manager.RunClass( Player, "PreDrawViewModel", ViewModel, Weapon ) + + if ( Weapon.PreDrawViewModel == nil ) then return false end + return Weapon:PreDrawViewModel( ViewModel, Weapon, Player ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PostDrawViewModel() + Desc: Called after drawing the view model +-----------------------------------------------------------]] +function GM:PostDrawViewModel( ViewModel, Player, Weapon ) + + if ( !IsValid( Weapon ) ) then return false end + + if ( Weapon.UseHands || !Weapon:IsScripted() ) then + + local hands = Player:GetHands() + if ( IsValid( hands ) && IsValid( hands:GetParent() ) ) then + + if ( not hook.Call( "PreDrawPlayerHands", self, hands, ViewModel, Player, Weapon ) ) then + + if ( Weapon.ViewModelFlip ) then render.CullMode( MATERIAL_CULLMODE_CW ) end + hands:DrawModel() + render.CullMode( MATERIAL_CULLMODE_CCW ) + + end + + hook.Call( "PostDrawPlayerHands", self, hands, ViewModel, Player, Weapon ) + + end + + end + + player_manager.RunClass( Player, "PostDrawViewModel", ViewModel, Weapon ) + + if ( Weapon.PostDrawViewModel == nil ) then return false end + return Weapon:PostDrawViewModel( ViewModel, Weapon, Player ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:DrawPhysgunBeam() + Desc: Return false to override completely +-----------------------------------------------------------]] +function GM:DrawPhysgunBeam( ply, weapon, bOn, target, boneid, pos ) + + -- Do nothing + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:NetworkEntityCreated() + Desc: Entity is created over the network +-----------------------------------------------------------]] +function GM:NetworkEntityCreated( ent ) +end + +--[[--------------------------------------------------------- + Name: gamemode:CreateMove( command ) + Desc: Allows the client to change the move commands + before it's send to the server +-----------------------------------------------------------]] +function GM:CreateMove( cmd ) + + if ( drive.CreateMove( cmd ) ) then return true end + + if ( player_manager.RunClass( LocalPlayer(), "CreateMove", cmd ) ) then return true end + +end + +--[[--------------------------------------------------------- + Name: gamemode:PreventScreenClicks() + Desc: The player is hovering over a ScreenClickable world +-----------------------------------------------------------]] +function GM:PreventScreenClicks( cmd ) + + -- + -- Returning true in this hook will prevent screen clicking sending IN_ATTACK + -- commands to the weapons. We want to do this in the properties system, so + -- that you don't fire guns when opening the properties menu. Holla! + -- + + return false + +end + +--[[--------------------------------------------------------- + Name: gamemode:GUIMousePressed( mousecode ) + Desc: The mouse has been pressed on the game screen +-----------------------------------------------------------]] +function GM:GUIMousePressed( mousecode, AimVector ) +end + +--[[--------------------------------------------------------- + Name: gamemode:GUIMouseReleased( mousecode ) + Desc: The mouse has been released on the game screen +-----------------------------------------------------------]] +function GM:GUIMouseReleased( mousecode, AimVector ) +end + +--[[--------------------------------------------------------- + Player class has been changed +-----------------------------------------------------------]] +function GM:PlayerClassChanged( ply, newID ) + + -- No class is set + if ( newID < 1 ) then return end + + -- Invalid class ID? + local classname = util.NetworkIDToString( newID ) + if ( !classname ) then return end + + -- Initialize the class on client + player_manager.SetPlayerClass( ply, classname ) + +end + +function GM:PreDrawHUD() +end + +function GM:PostDrawHUD() +end + +function GM:DrawOverlay() +end + +function GM:DrawMonitors() +end + +function GM:PreDrawEffects() +end + +function GM:PostDrawEffects() +end + +function GM:PreDrawHalos() +end + +function GM:CloseDermaMenus() +end + +function GM:CreateClientsideRagdoll( entity, ragdoll ) +end + +function GM:VehicleMove( ply, vehicle, mv ) +end diff --git a/gamemodes/base/gamemode/cl_pickteam.lua b/gamemodes/base/gamemode/cl_pickteam.lua new file mode 100644 index 0000000..c3ceeca --- /dev/null +++ b/gamemodes/base/gamemode/cl_pickteam.lua @@ -0,0 +1,65 @@ + +--[[--------------------------------------------------------- + Name: gamemode:ShowTeam() + Desc: +-----------------------------------------------------------]] +function GM:ShowTeam() + + if ( IsValid( self.TeamSelectFrame ) ) then return end + + -- Simple team selection box + self.TeamSelectFrame = vgui.Create( "DFrame" ) + self.TeamSelectFrame:SetTitle( "Pick Team" ) + + local AllTeams = team.GetAllTeams() + local y = 30 + for ID, TeamInfo in pairs ( AllTeams ) do + + if ( ID != TEAM_CONNECTING && ID != TEAM_UNASSIGNED ) then + + local Team = vgui.Create( "DButton", self.TeamSelectFrame ) + function Team.DoClick() self:HideTeam() RunConsoleCommand( "changeteam", ID ) end + Team:SetPos( 10, y ) + Team:SetSize( 130, 20 ) + Team:SetText( TeamInfo.Name ) + + if ( IsValid( LocalPlayer() ) && LocalPlayer():Team() == ID ) then + Team:SetEnabled( false ) + end + + y = y + 30 + + end + + end + + if ( GAMEMODE.AllowAutoTeam ) then + + local Team = vgui.Create( "DButton", self.TeamSelectFrame ) + function Team.DoClick() self:HideTeam() RunConsoleCommand( "autoteam" ) end + Team:SetPos( 10, y ) + Team:SetSize( 130, 20 ) + Team:SetText( "Auto" ) + y = y + 30 + + end + + self.TeamSelectFrame:SetSize( 150, y ) + self.TeamSelectFrame:Center() + self.TeamSelectFrame:MakePopup() + self.TeamSelectFrame:SetKeyboardInputEnabled( false ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:HideTeam() + Desc: +-----------------------------------------------------------]] +function GM:HideTeam() + + if ( IsValid(self.TeamSelectFrame) ) then + self.TeamSelectFrame:Remove() + self.TeamSelectFrame = nil + end + +end diff --git a/gamemodes/base/gamemode/cl_scoreboard.lua b/gamemodes/base/gamemode/cl_scoreboard.lua new file mode 100644 index 0000000..88ae20c --- /dev/null +++ b/gamemodes/base/gamemode/cl_scoreboard.lua @@ -0,0 +1,316 @@ + +surface.CreateFont( "ScoreboardDefault", { + font = "Helvetica", + size = 22, + weight = 800 +} ) + +surface.CreateFont( "ScoreboardDefaultTitle", { + font = "Helvetica", + size = 32, + weight = 800 +} ) + +local mWheelMat = Material( "gui/mwheel.png" ) + +-- +-- This defines a new panel type for the player row. The player row is given a player +-- and then from that point on it pretty much looks after itself. It updates player info +-- in the think function, and removes itself when the player leaves the server. +-- +local PLAYER_LINE = { + Init = function( self ) + + self.AvatarButton = self:Add( "DButton" ) + self.AvatarButton:Dock( LEFT ) + self.AvatarButton:SetSize( 32, 32 ) + self.AvatarButton.DoClick = function() self.Player:ShowProfile() end + + self.Avatar = vgui.Create( "AvatarImage", self.AvatarButton ) + self.Avatar:SetSize( 32, 32 ) + self.Avatar:SetMouseInputEnabled( false ) + + self.Name = self:Add( "DLabel" ) + self.Name:Dock( FILL ) + self.Name:SetFont( "ScoreboardDefault" ) + self.Name:SetTextColor( Color( 93, 93, 93 ) ) + self.Name:DockMargin( 8, 0, 0, 0 ) + + self.Mute = self:Add( "DImageButton" ) + self.Mute:SetSize( 32, 32 ) + self.Mute:Dock( RIGHT ) + self.Mute.OnMouseWheeled = function( s, delta ) + -- Clamp the old value to the closest 5, to deal with floating point imprecision + local oldValue = math.floor( self.Player:GetVoiceVolumeScale() * 100 ) + oldValue = math.Round( oldValue / 5 ) * 5 + + self.Player:SetVoiceVolumeScale( ( oldValue + ( delta * 5 ) ) / 100 ) + s.LastWheelTick = CurTime() + end + self.Mute:NoClipping( true ) + self.Mute.PaintOver = function( s, w, h ) + if ( !IsValid( self.Player ) ) then return end + + if ( s.Hovered ) then s.LastWheelTick = CurTime() end + + local time = math.Clamp( CurTime() - ( s.LastWheelTick or 0 ), 0, 0.75 ) / 0.75 + + local bgAlpha = 255 - time * 255 + if ( bgAlpha <= 0 ) then return end + + local x = w + 8 + w = w * 1.5 + + draw.RoundedBox( 4, x, 0, w, h, Color( 0, 0, 0, bgAlpha * 0.8 ) ) + draw.SimpleText( math.Round( self.Player:GetVoiceVolumeScale() * 100 ) .. "%", "DermaDefault", + x + ( w / 2 ) + 5, h * 0.5, Color( 255, 255, 255, bgAlpha ), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER ) + + surface.SetMaterial( mWheelMat ) + surface.SetDrawColor( 255, 255, 255, bgAlpha ) + surface.DrawTexturedRect( x, ( h / 2 - 8 ), 16, 16 ) + end + + self.Ping = self:Add( "DLabel" ) + self.Ping:Dock( RIGHT ) + self.Ping:SetWidth( 50 ) + self.Ping:SetFont( "ScoreboardDefault" ) + self.Ping:SetTextColor( Color( 93, 93, 93 ) ) + self.Ping:SetContentAlignment( 5 ) + + self.Deaths = self:Add( "DLabel" ) + self.Deaths:Dock( RIGHT ) + self.Deaths:SetWidth( 50 ) + self.Deaths:SetFont( "ScoreboardDefault" ) + self.Deaths:SetTextColor( Color( 93, 93, 93 ) ) + self.Deaths:SetContentAlignment( 5 ) + + self.Kills = self:Add( "DLabel" ) + self.Kills:Dock( RIGHT ) + self.Kills:SetWidth( 50 ) + self.Kills:SetFont( "ScoreboardDefault" ) + self.Kills:SetTextColor( Color( 93, 93, 93 ) ) + self.Kills:SetContentAlignment( 5 ) + + self:Dock( TOP ) + self:DockPadding( 3, 3, 3, 3 ) + self:SetHeight( 32 + 3 * 2 ) + self:DockMargin( 2, 0, 2, 2 ) + + end, + + Setup = function( self, pl ) + + self.Player = pl + + self.Avatar:SetPlayer( pl ) + + self:Think( self ) + + --local friend = self.Player:GetFriendStatus() + --MsgN( pl, " Friend: ", friend ) + + end, + + Think = function( self ) + + if ( !IsValid( self.Player ) ) then + self:SetZPos( 9999 ) -- Causes a rebuild + self:Remove() + return + end + + if ( self.PName == nil || self.PName != self.Player:Nick() ) then + self.PName = self.Player:Nick() + self.Name:SetText( self.PName ) + end + + if ( self.NumKills == nil || self.NumKills != self.Player:Frags() ) then + self.NumKills = self.Player:Frags() + self.Kills:SetText( self.NumKills ) + end + + if ( self.NumDeaths == nil || self.NumDeaths != self.Player:Deaths() ) then + self.NumDeaths = self.Player:Deaths() + self.Deaths:SetText( self.NumDeaths ) + end + + if ( self.NumPing == nil || self.NumPing != self.Player:Ping() ) then + self.NumPing = self.Player:Ping() + self.Ping:SetText( self.NumPing ) + end + + -- + -- Change the icon of the mute button based on state + -- + if ( self.Muted == nil || self.Muted != self.Player:IsMuted() ) then + + self.Muted = self.Player:IsMuted() + if ( self.Muted ) then + self.Mute:SetImage( "icon32/muted.png" ) + else + self.Mute:SetImage( "icon32/unmuted.png" ) + end + + self.Mute.DoClick = function( s ) self.Player:SetMuted( !self.Muted ) end + + end + + -- + -- Connecting players go at the very bottom + -- + if ( self.Player:Team() == TEAM_CONNECTING ) then + self:SetZPos( 2000 + self.Player:EntIndex() ) + return + end + + -- + -- This is what sorts the list. The panels are docked in the z order, + -- so if we set the z order according to kills they'll be ordered that way! + -- Careful though, it's a signed short internally, so needs to range between -32,768k and +32,767 + -- + self:SetZPos( ( self.NumKills * -50 ) + self.NumDeaths + self.Player:EntIndex() ) + + end, + + Paint = function( self, w, h ) + + if ( !IsValid( self.Player ) ) then return end + + draw.RoundedBox( 4, 0, 0, w, h, self:GetLineColor() ) + + end, + + GetLineColor = function( self ) + + -- We draw our background a different colour based on the status of the player + + if ( !IsValid( self.Player ) ) then + return Color( 230, 230, 230, 255 ) + end + + if ( self.Player:Team() == TEAM_CONNECTING ) then + return Color( 200, 200, 200, 200 ) + end + + if ( !self.Player:Alive() ) then + return Color( 230, 200, 200, 255 ) + end + + if ( self.Player:IsAdmin() ) then + return Color( 230, 255, 230, 255 ) + end + + return Color( 230, 230, 230, 255 ) + + end +} + +-- +-- Convert it from a normal table into a Panel Table based on DPanel +-- +PLAYER_LINE = vgui.RegisterTable( PLAYER_LINE, "DPanel" ) + +-- +-- Here we define a new panel table for the scoreboard. It basically consists +-- of a header and a scrollpanel - into which the player lines are placed. +-- +local SCORE_BOARD = { + Init = function( self ) + + self.Header = self:Add( "Panel" ) + self.Header:Dock( TOP ) + self.Header:SetHeight( 100 ) + + self.Name = self.Header:Add( "DLabel" ) + self.Name:SetFont( "ScoreboardDefaultTitle" ) + self.Name:SetTextColor( color_white ) + self.Name:Dock( TOP ) + self.Name:SetHeight( 40 ) + self.Name:SetContentAlignment( 5 ) + self.Name:SetExpensiveShadow( 2, Color( 0, 0, 0, 200 ) ) + + --self.NumPlayers = self.Header:Add( "DLabel" ) + --self.NumPlayers:SetFont( "ScoreboardDefault" ) + --self.NumPlayers:SetTextColor( color_white ) + --self.NumPlayers:SetPos( 0, 100 - 30 ) + --self.NumPlayers:SetSize( 300, 30 ) + --self.NumPlayers:SetContentAlignment( 4 ) + + self.Scores = self:Add( "DScrollPanel" ) + self.Scores:Dock( FILL ) + + end, + + PerformLayout = function( self ) + + self:SetSize( 700, ScrH() - 200 ) + self:SetPos( ScrW() / 2 - 350, 100 ) + + end, + + Paint = function( self, w, h ) + + --draw.RoundedBox( 4, 0, 0, w, h, Color( 0, 0, 0, 200 ) ) + + end, + + Think = function( self, w, h ) + + self.Name:SetText( GetHostName() ) + + -- + -- Loop through each player, and if one doesn't have a score entry - create it. + -- + + for id, pl in player.Iterator() do + + if ( IsValid( pl.ScoreEntry ) ) then continue end + + pl.ScoreEntry = vgui.CreateFromTable( PLAYER_LINE, pl.ScoreEntry ) + pl.ScoreEntry:Setup( pl ) + + self.Scores:AddItem( pl.ScoreEntry ) + + end + + end +} + +SCORE_BOARD = vgui.RegisterTable( SCORE_BOARD, "EditablePanel" ) + +--[[--------------------------------------------------------- + Name: gamemode:ScoreboardShow( ) + Desc: Sets the scoreboard to visible +-----------------------------------------------------------]] +function GM:ScoreboardShow() + + if ( !IsValid( g_Scoreboard ) ) then + g_Scoreboard = vgui.CreateFromTable( SCORE_BOARD ) + end + + if ( IsValid( g_Scoreboard ) ) then + g_Scoreboard:Show() + g_Scoreboard:MakePopup() + g_Scoreboard:SetKeyboardInputEnabled( false ) + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:ScoreboardHide( ) + Desc: Hides the scoreboard +-----------------------------------------------------------]] +function GM:ScoreboardHide() + + if ( IsValid( g_Scoreboard ) ) then + g_Scoreboard:Hide() + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:HUDDrawScoreBoard( ) + Desc: If you prefer to draw your scoreboard the stupid way (without vgui) +-----------------------------------------------------------]] +function GM:HUDDrawScoreBoard() +end diff --git a/gamemodes/base/gamemode/cl_spawnmenu.lua b/gamemodes/base/gamemode/cl_spawnmenu.lua new file mode 100644 index 0000000..0b6a206 --- /dev/null +++ b/gamemodes/base/gamemode/cl_spawnmenu.lua @@ -0,0 +1,36 @@ +--[[--------------------------------------------------------- + Spawn Menu +-----------------------------------------------------------]] +function GM:OnSpawnMenuOpen() +end + +function GM:OnSpawnMenuClose() +end + +concommand.Add( "+menu", function() + hook.Run( "OnSpawnMenuOpen" ) +end, nil, "Opens the spawnmenu", FCVAR_DONTRECORD ) + +concommand.Add( "-menu", function() + if ( input.IsKeyTrapping() ) then return end + hook.Run( "OnSpawnMenuClose" ) +end, nil, "Closes the spawnmenu", FCVAR_DONTRECORD ) + + +--[[--------------------------------------------------------- + Context Menu +-----------------------------------------------------------]] +function GM:OnContextMenuOpen() +end + +function GM:OnContextMenuClose() +end + +concommand.Add( "+menu_context", function() + hook.Run( "OnContextMenuOpen" ) +end, nil, "Opens the context menu", FCVAR_DONTRECORD ) + +concommand.Add( "-menu_context", function() + if ( input.IsKeyTrapping() ) then return end + hook.Run( "OnContextMenuClose" ) +end, nil, "Closes the context menu", FCVAR_DONTRECORD ) diff --git a/gamemodes/base/gamemode/cl_targetid.lua b/gamemodes/base/gamemode/cl_targetid.lua new file mode 100644 index 0000000..d540d12 --- /dev/null +++ b/gamemodes/base/gamemode/cl_targetid.lua @@ -0,0 +1,59 @@ + +--[[--------------------------------------------------------- + Name: gamemode:HUDDrawTargetID( ) + Desc: Draw the target id (the name of the player you're currently looking at) +-----------------------------------------------------------]] +function GM:HUDDrawTargetID() + + local trace = LocalPlayer():GetEyeTrace() + if ( !trace.Hit ) then return end + if ( !trace.HitNonWorld ) then return end + + local text = "ERROR" + local font = "TargetID" + + if ( trace.Entity:IsPlayer() ) then + text = trace.Entity:Nick() + else + --text = trace.Entity:GetClass() + return + end + + surface.SetFont( font ) + local w, h = surface.GetTextSize( text ) + + local MouseX, MouseY = input.GetCursorPos() + + if ( MouseX == 0 && MouseY == 0 || !vgui.CursorVisible() ) then + + MouseX = ScrW() / 2 + MouseY = ScrH() / 2 + + end + + local x = MouseX + local y = MouseY + + x = x - w / 2 + y = y + 30 + + -- The fonts internal drop shadow looks lousy with AA on + draw.SimpleText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ) ) + draw.SimpleText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ) ) + draw.SimpleText( text, font, x, y, self:GetTeamColor( trace.Entity ) ) + + y = y + h + 5 + + -- Draw the health + text = trace.Entity:Health() .. "%" + font = "TargetIDSmall" + + surface.SetFont( font ) + w, h = surface.GetTextSize( text ) + x = MouseX - w / 2 + + draw.SimpleText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ) ) + draw.SimpleText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ) ) + draw.SimpleText( text, font, x, y, self:GetTeamColor( trace.Entity ) ) + +end diff --git a/gamemodes/base/gamemode/cl_voice.lua b/gamemodes/base/gamemode/cl_voice.lua new file mode 100644 index 0000000..6719c25 --- /dev/null +++ b/gamemodes/base/gamemode/cl_voice.lua @@ -0,0 +1,144 @@ + +local PANEL = {} +local PlayerVoicePanels = {} + +function PANEL:Init() + + self.LabelName = vgui.Create( "DLabel", self ) + self.LabelName:SetFont( "GModNotify" ) + self.LabelName:Dock( FILL ) + self.LabelName:DockMargin( 8, 0, 0, 0 ) + self.LabelName:SetTextColor( color_white ) + + self.Avatar = vgui.Create( "AvatarImage", self ) + self.Avatar:Dock( LEFT ) + self.Avatar:SetSize( 32, 32 ) + + self.Color = color_transparent + + self:SetSize( 250, 32 + 8 ) + self:DockPadding( 4, 4, 4, 4 ) + self:DockMargin( 2, 2, 2, 2 ) + self:Dock( BOTTOM ) + +end + +function PANEL:Setup( ply ) + + self.ply = ply + self.LabelName:SetText( ply:Nick() ) + self.Avatar:SetPlayer( ply ) + + self.Color = hook.Run( "GetTeamColor", ply ) + + self:InvalidateLayout() + +end + +function PANEL:Paint( w, h ) + + if ( !IsValid( self.ply ) ) then return end + draw.RoundedBox( 4, 0, 0, w, h, Color( 0, self.ply:VoiceVolume() * 255, 0, 240 ) ) + +end + +function PANEL:Think() + + if ( IsValid( self.ply ) ) then + self.LabelName:SetText( self.ply:Nick() ) + end + + if ( self.fadeAnim ) then + self.fadeAnim:Run() + end + +end + +function PANEL:FadeOut( anim, delta, data ) + + if ( anim.Finished ) then + + if ( IsValid( PlayerVoicePanels[ self.ply ] ) ) then + PlayerVoicePanels[ self.ply ]:Remove() + PlayerVoicePanels[ self.ply ] = nil + return + end + + return end + + self:SetAlpha( 255 - ( 255 * delta ) ) + +end + +derma.DefineControl( "VoiceNotify", "", PANEL, "DPanel" ) + + + +function GM:PlayerStartVoice( ply ) + + if ( !IsValid( g_VoicePanelList ) ) then return end + + -- There'd be an exta one if voice_loopback is on, so remove it. + GAMEMODE:PlayerEndVoice( ply ) + + + if ( IsValid( PlayerVoicePanels[ ply ] ) ) then + + if ( PlayerVoicePanels[ ply ].fadeAnim ) then + PlayerVoicePanels[ ply ].fadeAnim:Stop() + PlayerVoicePanels[ ply ].fadeAnim = nil + end + + PlayerVoicePanels[ ply ]:SetAlpha( 255 ) + + return + + end + + if ( !IsValid( ply ) ) then return end + + local pnl = g_VoicePanelList:Add( "VoiceNotify" ) + pnl:Setup( ply ) + + PlayerVoicePanels[ ply ] = pnl + +end + +local function VoiceClean() + + for k, v in pairs( PlayerVoicePanels ) do + + if ( !IsValid( k ) ) then + GAMEMODE:PlayerEndVoice( k ) + end + + end + +end +timer.Create( "VoiceClean", 10, 0, VoiceClean ) + +function GM:PlayerEndVoice( ply ) + + if ( IsValid( PlayerVoicePanels[ ply ] ) ) then + + if ( PlayerVoicePanels[ ply ].fadeAnim ) then return end + + PlayerVoicePanels[ ply ].fadeAnim = Derma_Anim( "FadeOut", PlayerVoicePanels[ ply ], PlayerVoicePanels[ ply ].FadeOut ) + PlayerVoicePanels[ ply ].fadeAnim:Start( 2 ) + + end + +end + +local function CreateVoiceVGUI() + + g_VoicePanelList = vgui.Create( "DPanel" ) + + g_VoicePanelList:ParentToHUD() + g_VoicePanelList:SetPos( ScrW() - 300, 100 ) + g_VoicePanelList:SetSize( 250, ScrH() - 200 ) + g_VoicePanelList:SetPaintBackground( false ) + +end + +hook.Add( "InitPostEntity", "CreateVoiceVGUI", CreateVoiceVGUI ) diff --git a/gamemodes/base/gamemode/gravitygun.lua b/gamemodes/base/gamemode/gravitygun.lua new file mode 100644 index 0000000..b521248 --- /dev/null +++ b/gamemodes/base/gamemode/gravitygun.lua @@ -0,0 +1,36 @@ + +--[[--------------------------------------------------------- + Name: gamemode:GravGunPunt() + Desc: We're about to punt an entity (primary fire). + Return true if we're allowed to. +-----------------------------------------------------------]] +function GM:GravGunPunt( ply, ent ) + return true +end + +--[[--------------------------------------------------------- + Name: gamemode:GravGunPickupAllowed() + Desc: Return true if we're allowed to pickup entity +-----------------------------------------------------------]] +function GM:GravGunPickupAllowed( ply, ent ) + return true +end + +if ( SERVER ) then + + --[[--------------------------------------------------------- + Name: gamemode:GravGunOnPickedUp() + Desc: The entity has been picked up + -----------------------------------------------------------]] + function GM:GravGunOnPickedUp( ply, ent ) + end + + + --[[--------------------------------------------------------- + Name: gamemode:GravGunOnDropped() + Desc: The entity has been dropped + -----------------------------------------------------------]] + function GM:GravGunOnDropped( ply, ent ) + end + +end diff --git a/gamemodes/base/gamemode/init.lua b/gamemodes/base/gamemode/init.lua new file mode 100644 index 0000000..0536496 --- /dev/null +++ b/gamemodes/base/gamemode/init.lua @@ -0,0 +1,180 @@ + +include( 'shared.lua' ) +include( 'player.lua' ) +include( 'npc.lua' ) +include( 'variable_edit.lua' ) + +--[[--------------------------------------------------------- + Name: gamemode:Initialize() + Desc: Called immediately after starting the gamemode +-----------------------------------------------------------]] +function GM:Initialize() +end + +--[[--------------------------------------------------------- + Name: gamemode:InitPostEntity() + Desc: Called as soon as all map entities have been spawned +-----------------------------------------------------------]] +function GM:InitPostEntity() +end + +--[[--------------------------------------------------------- + Name: gamemode:Think() + Desc: Called every frame +-----------------------------------------------------------]] +function GM:Think() +end + +--[[--------------------------------------------------------- + Name: gamemode:ShutDown() + Desc: Called when the Lua system is about to shut down +-----------------------------------------------------------]] +function GM:ShutDown() +end + +--[[--------------------------------------------------------- + Name: gamemode:DoPlayerDeath( ) + Desc: Carries out actions when the player dies +-----------------------------------------------------------]] +function GM:DoPlayerDeath( ply, attacker, dmginfo ) + + if ( !dmginfo:IsDamageType( DMG_REMOVENORAGDOLL ) ) then + ply:CreateRagdoll() + end + + ply:AddDeaths( 1 ) + + if ( attacker:IsValid() && attacker:IsPlayer() ) then + + if ( attacker == ply ) then + attacker:AddFrags( -1 ) + else + attacker:AddFrags( 1 ) + end + + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerShouldTakeDamage + Return true if this player should take damage from this attacker +-----------------------------------------------------------]] +function GM:PlayerShouldTakeDamage( ply, attacker ) + return true +end + +--[[--------------------------------------------------------- + Name: gamemode:EntityTakeDamage( ent, info ) + Desc: The entity has received damage +-----------------------------------------------------------]] +function GM:EntityTakeDamage( ent, info ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerHurt( ) + Desc: Called when a player is hurt. +-----------------------------------------------------------]] +function GM:PlayerHurt( player, attacker, healthleft, healthtaken ) +end + +--[[--------------------------------------------------------- + Name: gamemode:CreateEntityRagdoll( entity, ragdoll ) + Desc: A ragdoll of an entity has been created +-----------------------------------------------------------]] +function GM:CreateEntityRagdoll( entity, ragdoll ) +end + +-- Set the ServerName every 30 seconds in case it changes.. +-- This is for backwards compatibility only - client can now use GetHostName() +local function HostnameThink() + + SetGlobalString( "ServerName", GetHostName() ) + +end + +timer.Create( "HostnameThink", 30, 0, HostnameThink ) + +--[[--------------------------------------------------------- + Show the default team selection screen +-----------------------------------------------------------]] +function GM:ShowTeam( ply ) + + if ( !GAMEMODE.TeamBased ) then return end + + local TimeBetweenSwitches = GAMEMODE.SecondsBetweenTeamSwitches or 10 + if ( ply.LastTeamSwitch && RealTime() - ply.LastTeamSwitch < TimeBetweenSwitches ) then + ply.LastTeamSwitch = ply.LastTeamSwitch + 1 + ply:ChatPrint( Format( "Please wait %i more seconds before trying to change team again", ( TimeBetweenSwitches - ( RealTime() - ply.LastTeamSwitch ) ) + 1 ) ) + return + end + + -- For clientside see cl_pickteam.lua + ply:SendLua( "GAMEMODE:ShowTeam()" ) + +end + +-- +-- CheckPassword( steamid, networkid, server_password, password, name ) +-- +-- Called every time a non-localhost player joins the server. steamid is their 64bit +-- steamid. Return false and a reason to reject their join. Return true to allow +-- them to join. +-- +function GM:CheckPassword( steamid, networkid, server_password, password, name ) + + -- The server has sv_password set + if ( server_password != "" ) then + + -- The joining clients password doesn't match sv_password + if ( server_password != password ) then + return false + end + + end + + -- + -- Returning true means they're allowed to join the server + -- + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:FinishMove( player, movedata ) +-----------------------------------------------------------]] +function GM:VehicleMove( ply, vehicle, mv ) + + -- + -- On duck toggle third person view + -- + if ( mv:KeyPressed( IN_DUCK ) && vehicle.SetThirdPersonMode ) then + vehicle:SetThirdPersonMode( !vehicle:GetThirdPersonMode() ) + end + + -- + -- Adjust the camera distance with the mouse wheel + -- + local iWheel = ply:GetCurrentCommand():GetMouseWheel() + if ( iWheel != 0 && vehicle.SetCameraDistance ) then + -- The distance is a multiplier + -- Actual camera distance = ( renderradius + renderradius * dist ) + -- so -1 will be zero.. clamp it there. + local newdist = math.Clamp( vehicle:GetCameraDistance() - iWheel * 0.03 * ( 1.1 + vehicle:GetCameraDistance() ), -1, 10 ) + vehicle:SetCameraDistance( newdist ) + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:PreUndo( undo ) +-----------------------------------------------------------]] +function GM:PreUndo( undo ) + return true +end + +--[[--------------------------------------------------------- + Name: gamemode:PreUndo( undo ) +-----------------------------------------------------------]] +function GM:PostUndo( undo, count ) +end diff --git a/gamemodes/base/gamemode/npc.lua b/gamemodes/base/gamemode/npc.lua new file mode 100644 index 0000000..aedae7c --- /dev/null +++ b/gamemodes/base/gamemode/npc.lua @@ -0,0 +1,179 @@ + +-- Backwards compatibility with addons +util.AddNetworkString( "PlayerKilledNPC" ) +util.AddNetworkString( "NPCKilledNPC" ) +util.AddNetworkString( "PlayerKilled" ) +util.AddNetworkString( "PlayerKilledSelf" ) +util.AddNetworkString( "PlayerKilledByPlayer" ) + +-- New way +util.AddNetworkString( "DeathNoticeEvent" ) + +DEATH_NOTICE_FRIENDLY_VICTIM = 1 +DEATH_NOTICE_FRIENDLY_ATTACKER = 2 +--DEATH_NOTICE_HEADSHOT = 4 +--DEATH_NOTICE_PENETRATION = 8 +function GM:SendDeathNotice( attacker, inflictor, victim, flags ) + + net.Start( "DeathNoticeEvent" ) + + if ( isstring( attacker ) ) then + net.WriteUInt( 1, 2 ) + net.WriteString( attacker:sub( 0, 512 ) ) + elseif ( IsValid( attacker ) ) then + net.WriteUInt( 2, 2 ) + net.WriteEntity( attacker ) + else + -- TODO: game.GetWorld will be "written" here, because its not IsValid. Make it write a separate type? + net.WriteUInt( 0, 2 ) + end + + if ( isstring( inflictor ) ) then + net.WriteString( inflictor:sub( 0, 512 ) ) + else + -- Should never really reach here.. + net.WriteString( "" ) + end + + if ( isstring( victim ) ) then + net.WriteUInt( 1, 2 ) + net.WriteString( victim:sub( 0, 512 ) ) + elseif ( IsValid( victim ) ) then + net.WriteUInt( 2, 2 ) + net.WriteEntity( victim ) + else + net.WriteUInt( 0, 2 ) + end + + net.WriteUInt( flags, 8 ) + + net.Broadcast() + +end + +function GM:GetDeathNoticeEntityName( ent ) + + if ( isstring( ent ) ) then return ent end + if ( !IsValid( ent ) ) then return nil end + + -- Some specific HL2 NPCs, just for fun + if ( ent:GetClass() == "npc_citizen" ) then + if ( ent:GetName() == "griggs" ) then return "#npc_citizen_griggs" end + if ( ent:GetName() == "sheckley" ) then return "#npc_citizen_sheckley" end + if ( ent:GetName() == "tobias" ) then return "#npc_citizen_laszlo" end + if ( ent:GetName() == "stanley" ) then return "#npc_citizen_sandy" end + end + if ( ent:GetClass() == "npc_sniper" and ( ent:GetName() == "alyx_sniper" || ent:GetName() == "sniper_alyx" ) ) then return "#npc_alyx" end + + -- Custom vehicle and NPC names from spawnmenu + if ( ent:IsVehicle() ) then + local vehTable = list.GetEntry( "Vehicles", ent.VehicleName ) + if ( vehTable and vehTable.Name ) then return vehTable.Name end + elseif ( ent:IsNPC() ) then + local npcTable = list.GetEntry( "NPC", ent.NPCName ) + if ( npcTable and npcTable.Name ) then return npcTable.Name end + elseif ( ent.EntityName ) then + local sentTable = list.GetEntry( "SpawnableEntities", ent.EntityName ) + if ( sentTable and sentTable.PrintName ) then return sentTable.PrintName end + end + + if ( ent:GetClass() == "npc_antlion" and ent:GetModel() == "models/antlion_worker.mdl" ) then + return list.GetEntry( "NPC", "npc_antlion_worker" ).Name + end + + -- Fallback to old behavior + return "#" .. ent:GetClass() + +end + +--[[--------------------------------------------------------- + Name: gamemode:OnNPCKilled( entity, attacker, inflictor ) + Desc: The NPC has died +-----------------------------------------------------------]] +function GM:OnNPCKilled( ent, attacker, inflictor ) + + -- Don't spam the killfeed with scripted stuff + if ( ent:GetClass() == "npc_bullseye" or ent:GetClass() == "npc_launcher" ) then return end + + -- If killed by trigger_hurt, act as if NPC killed itself + if ( IsValid( attacker ) and attacker:GetClass() == "trigger_hurt" ) then attacker = ent end + + -- NPC got run over.. + if ( IsValid( attacker ) and attacker:IsVehicle() and IsValid( attacker:GetDriver() ) ) then + attacker = attacker:GetDriver() + end + + if ( !IsValid( inflictor ) and IsValid( attacker ) ) then + inflictor = attacker + end + + -- Convert the inflictor to the weapon that they're holding if we can. + if ( IsValid( inflictor ) and attacker == inflictor and ( inflictor:IsPlayer() or inflictor:IsNPC() ) ) then + + inflictor = inflictor:GetActiveWeapon() + if ( !IsValid( attacker ) ) then inflictor = attacker end + + end + + local InflictorClass = "worldspawn" + local AttackerClass = game.GetWorld() + + if ( IsValid( inflictor ) ) then InflictorClass = inflictor:GetClass() end + if ( IsValid( attacker ) ) then + + AttackerClass = attacker + + -- If there is no valid inflictor, use the attacker (i.e. manhacks) + if ( !IsValid( inflictor ) ) then InflictorClass = attacker:GetClass() end + + if ( attacker:IsPlayer() ) then + + local flags = 0 + if ( ent:IsNPC() and ent:Disposition( attacker ) != D_HT ) then flags = flags + DEATH_NOTICE_FRIENDLY_VICTIM end + + self:SendDeathNotice( attacker, InflictorClass, self:GetDeathNoticeEntityName( ent ), flags ) + + return + end + + end + + -- Floor turret got knocked over + if ( ent:GetClass() == "npc_turret_floor" ) then AttackerClass = ent end + + -- It was NPC suicide.. + if ( ent == AttackerClass ) then InflictorClass = "suicide" end + + local flags = 0 + if ( IsValid( Entity( 1 ) ) and ent:IsNPC() and ent:Disposition( Entity( 1 ) ) == D_LI ) then flags = flags + DEATH_NOTICE_FRIENDLY_VICTIM end + if ( IsValid( Entity( 1 ) ) and AttackerClass:IsNPC() and AttackerClass:Disposition( Entity( 1 ) ) == D_LI ) then flags = flags + DEATH_NOTICE_FRIENDLY_ATTACKER end + + self:SendDeathNotice( self:GetDeathNoticeEntityName( AttackerClass ), InflictorClass, self:GetDeathNoticeEntityName( ent ), flags ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:ScaleNPCDamage( ply, hitgroup, dmginfo ) + Desc: Scale the damage based on being shot in a hitbox +-----------------------------------------------------------]] +function GM:ScaleNPCDamage( npc, hitgroup, dmginfo ) + + -- More damage if we're shot in the head + if ( hitgroup == HITGROUP_HEAD ) then + + dmginfo:ScaleDamage( 2 ) + + end + + -- Less damage if we're shot in the arms or legs + if ( hitgroup == HITGROUP_LEFTARM or + hitgroup == HITGROUP_RIGHTARM or + hitgroup == HITGROUP_LEFTLEG or + hitgroup == HITGROUP_RIGHTLEG or + hitgroup == HITGROUP_GEAR ) then + + dmginfo:ScaleDamage( 0.25 ) + + end + +end diff --git a/gamemodes/base/gamemode/obj_player_extend.lua b/gamemodes/base/gamemode/obj_player_extend.lua new file mode 100644 index 0000000..adb46a1 --- /dev/null +++ b/gamemodes/base/gamemode/obj_player_extend.lua @@ -0,0 +1,213 @@ + +local meta = FindMetaTable( "Player" ) +if ( !meta ) then return end + +-- In this file we're adding functions to the player meta table. +-- This means you'll be able to call functions here straight from the player object +-- You can even override already existing functions. + +--[[--------------------------------------------------------- + Name: AddFrozenPhysicsObject + Desc: For the Physgun, adds a frozen object to the player's list +-----------------------------------------------------------]] +function meta:AddFrozenPhysicsObject( ent, phys ) + + -- Get the player's table + local tab = self:GetTable() + + -- Make sure the physics objects table exists + tab.FrozenPhysicsObjects = tab.FrozenPhysicsObjects or {} + + -- Make a new table that contains the info + local entry = {} + entry.ent = ent + entry.phys = phys + + table.insert( tab.FrozenPhysicsObjects, entry ) + + gamemode.Call( "PlayerFrozeObject", self, ent, phys ) + +end + +local function PlayerUnfreezeObject( ply, ent, object ) + + -- Not frozen! + if ( object:IsMoveable() ) then return 0 end + + -- Unfreezable means it can't be frozen or unfrozen. + -- This prevents the player unfreezing the gmod_anchor entity. + if ( ent:GetUnFreezable() ) then return 0 end + + -- NOTE: IF YOU'RE MAKING SOME KIND OF PROP PROTECTOR THEN HOOK "CanPlayerUnfreeze" + if ( !gamemode.Call( "CanPlayerUnfreeze", ply, ent, object ) ) then return 0 end + + object:EnableMotion( true ) + object:Wake() + + gamemode.Call( "PlayerUnfrozeObject", ply, ent, object ) + + return 1 + +end + +--[[--------------------------------------------------------- + Name: UnfreezePhysicsObjects + Desc: For the Physgun, unfreezes all frozen physics objects +-----------------------------------------------------------]] +function meta:PhysgunUnfreeze() + + -- Get the player's table + local tab = self:GetTable() + if ( !tab.FrozenPhysicsObjects ) then return 0 end + + -- Detect double click. Unfreeze all objects on double click. + if ( tab.LastPhysUnfreeze && CurTime() - tab.LastPhysUnfreeze < 0.25 ) then + return self:UnfreezePhysicsObjects() + end + + local tr = self:GetEyeTrace() + if ( tr.HitNonWorld && IsValid( tr.Entity ) ) then + + local Ents = constraint.GetAllConstrainedEntities( tr.Entity ) + local UnfrozenObjects = 0 + + for k, ent in pairs( Ents ) do + + local objects = ent:GetPhysicsObjectCount() + + for i = 1, objects do + + local physobject = ent:GetPhysicsObjectNum( i - 1 ) + UnfrozenObjects = UnfrozenObjects + PlayerUnfreezeObject( self, ent, physobject ) + + end + + end + + + + return UnfrozenObjects + + end + + tab.LastPhysUnfreeze = CurTime() + return 0 + +end + +--[[--------------------------------------------------------- + Name: UnfreezePhysicsObjects + Desc: For the Physgun, unfreezes all frozen physics objects +-----------------------------------------------------------]] +function meta:UnfreezePhysicsObjects() + + -- Get the player's table + local tab = self:GetTable() + + -- If the table doesn't exist then quit here + if ( !tab.FrozenPhysicsObjects ) then return 0 end + + local Count = 0 + + -- Loop through each table in our table + for k, v in pairs( tab.FrozenPhysicsObjects ) do + + -- Make sure the entity to which the physics object + -- is attached is still valid (still exists) + if ( isentity( v.ent ) && IsValid( v.ent ) ) then + + -- We can't directly test to see if EnableMotion is false right now + -- but IsMovable seems to do the job just fine. + -- We only test so the count isn't wrong + if ( IsValid( v.phys ) && !v.phys:IsMoveable() ) then + + -- We need to freeze/unfreeze all physobj's in jeeps to stop it spazzing + if ( v.ent:GetClass() == "prop_vehicle_jeep" ) then + + -- How many physics objects we have + local objects = v.ent:GetPhysicsObjectCount() + + -- Loop through each one + for i = 0, objects - 1 do + + local physobject = v.ent:GetPhysicsObjectNum( i ) + PlayerUnfreezeObject( self, v.ent, physobject ) + + end + + end + + Count = Count + PlayerUnfreezeObject( self, v.ent, v.phys ) + + end + + end + + end + + -- Remove the table + tab.FrozenPhysicsObjects = nil + + return Count + +end + +local g_UniqueIDTable = {} + +--[[--------------------------------------------------------- + This table will persist between client deaths and reconnects +-----------------------------------------------------------]] +function meta:UniqueIDTable( key ) + + local id = 0 + if ( SERVER ) then id = self:SteamID64() end + + g_UniqueIDTable[ id ] = g_UniqueIDTable[ id ] or {} + g_UniqueIDTable[ id ][ key ] = g_UniqueIDTable[ id ][ key ] or {} + + return g_UniqueIDTable[ id ][ key ] + +end + +--[[--------------------------------------------------------- + Player Eye Trace +-----------------------------------------------------------]] +function meta:GetEyeTrace() + if ( CLIENT ) then + local framenum = FrameNumber() + + -- Cache the trace results for the current frame, unless we're serverside + -- in which case it wouldn't play well with lag compensation at all + if ( self.LastPlayerTrace == framenum ) then + return self.PlayerTrace + end + + self.LastPlayerTrace = framenum + end + + local tr = util.TraceLine( util.GetPlayerTrace( self ) ) + self.PlayerTrace = tr + + return tr +end + +--[[--------------------------------------------------------- + GetEyeTraceIgnoreCursor + Like GetEyeTrace but doesn't use the cursor aim vector.. +-----------------------------------------------------------]] +function meta:GetEyeTraceNoCursor() + if ( CLIENT ) then + local framenum = FrameNumber() + + if ( self.LastPlayerAimTrace == framenum ) then + return self.PlayerAimTrace + end + + self.LastPlayerAimTrace = framenum + end + + local tr = util.TraceLine( util.GetPlayerTrace( self, self:EyeAngles():Forward() ) ) + self.PlayerAimTrace = tr + + return tr +end diff --git a/gamemodes/base/gamemode/player.lua b/gamemodes/base/gamemode/player.lua new file mode 100644 index 0000000..f84edc8 --- /dev/null +++ b/gamemodes/base/gamemode/player.lua @@ -0,0 +1,899 @@ + +--[[--------------------------------------------------------- + Name: gamemode:OnPhysgunFreeze( weapon, phys, ent, player ) + Desc: The physgun wants to freeze a prop +-----------------------------------------------------------]] +function GM:OnPhysgunFreeze( weapon, phys, ent, ply ) + + -- Non vphysics entity, we don't know how to handle that + if ( !IsValid( phys ) ) then return end + + -- Object is already frozen (!?) + if ( !phys:IsMoveable() ) then return end + if ( ent:GetUnFreezable() ) then return end + + phys:EnableMotion( false ) + + -- Add it to the player's frozen props + ply:AddFrozenPhysicsObject( ent, phys ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:OnPhysgunReload( weapon, player ) + Desc: The physgun wants to freeze a prop +-----------------------------------------------------------]] +function GM:OnPhysgunReload( weapon, ply ) + + ply:PhysgunUnfreeze() + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerAuthed() + Desc: Player's UniqueID was set +-----------------------------------------------------------]] +function GM:PlayerAuthed( ply, SteamID, UniqueID ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerCanPickupWeapon() + Desc: Called when a player tries to pickup a weapon. + return true to allow the pickup. +-----------------------------------------------------------]] +function GM:PlayerCanPickupWeapon( ply, entity ) + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerCanPickupItem() + Desc: Called when a player tries to pickup an item. + return true to allow the pickup. +-----------------------------------------------------------]] +function GM:PlayerCanPickupItem( ply, entity ) + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:CanPlayerUnfreeze() + Desc: Can the player unfreeze this entity & physobject +-----------------------------------------------------------]] +function GM:CanPlayerUnfreeze( ply, entity, physobject ) + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerDisconnected() + Desc: Player has disconnected from the server. +-----------------------------------------------------------]] +function GM:PlayerDisconnected( ply ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSay() + Desc: A player (or server) has used say. Return a string + for the player to say. Return an empty string if the + player should say nothing. +-----------------------------------------------------------]] +function GM:PlayerSay( ply, text, teamonly ) + + return text + +end + + +--[[--------------------------------------------------------- + Name: gamemode:PlayerDeathThink( player ) + Desc: Called when the player is waiting to respawn +-----------------------------------------------------------]] +function GM:PlayerDeathThink( pl ) + + if ( pl.NextSpawnTime && pl.NextSpawnTime > CurTime() ) then return end + + if ( pl:IsBot() || pl:KeyPressed( IN_ATTACK ) || pl:KeyPressed( IN_ATTACK2 ) || pl:KeyPressed( IN_JUMP ) ) then + + pl:Spawn() + + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerUse( player, entity ) + Desc: A player has attempted to use a specific entity + Return true if the player can use it +------------------------------------------------------------]] +function GM:PlayerUse( ply, entity ) + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSilentDeath() + Desc: Called when a player dies silently +-----------------------------------------------------------]] +function GM:PlayerSilentDeath( Victim ) + + Victim.NextSpawnTime = CurTime() + 2 + Victim.DeathTime = CurTime() + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerDeath() + Desc: Called when a player dies. +-----------------------------------------------------------]] +function GM:PlayerDeath( ply, inflictor, attacker ) + + -- Don't spawn for at least 2 seconds + ply.NextSpawnTime = CurTime() + 2 + ply.DeathTime = CurTime() + + if ( IsValid( attacker ) && attacker:GetClass() == "trigger_hurt" ) then attacker = ply end + + if ( IsValid( attacker ) && attacker:IsVehicle() && IsValid( attacker:GetDriver() ) ) then + attacker = attacker:GetDriver() + end + + if ( !IsValid( inflictor ) && IsValid( attacker ) ) then + inflictor = attacker + end + + -- Convert the inflictor to the weapon that they're holding if we can. + -- This can be right or wrong with NPCs since combine can be holding a + -- pistol but kill you by hitting you with their arm. + if ( IsValid( inflictor ) && inflictor == attacker && ( inflictor:IsPlayer() || inflictor:IsNPC() ) ) then + + inflictor = inflictor:GetActiveWeapon() + if ( !IsValid( inflictor ) ) then inflictor = attacker end + + end + + player_manager.RunClass( ply, "Death", inflictor, attacker ) + + if ( attacker == ply ) then + + self:SendDeathNotice( nil, "suicide", ply, 0 ) + + MsgAll( attacker:Nick() .. " suicided!\n" ) + + return + end + + if ( attacker:IsPlayer() ) then + + self:SendDeathNotice( attacker, inflictor:GetClass(), ply, 0 ) + + MsgAll( attacker:Nick() .. " killed " .. ply:Nick() .. " using " .. inflictor:GetClass() .. "\n" ) + + return + end + + if ( !IsValid( attacker ) ) then attacker = game.GetWorld() end + if ( !IsValid( inflictor ) ) then inflictor = attacker end + + local flags = 0 + if ( attacker:IsNPC() and attacker:Disposition( ply ) != D_HT ) then flags = flags + DEATH_NOTICE_FRIENDLY_ATTACKER end + + self:SendDeathNotice( self:GetDeathNoticeEntityName( attacker ), inflictor:GetClass(), ply, 0 ) + + MsgAll( ply:Nick() .. " was killed by " .. attacker:GetClass() .. "\n" ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerInitialSpawn() + Desc: Called just before the player's first spawn +-----------------------------------------------------------]] +function GM:PlayerInitialSpawn( pl, transiton ) + + pl:SetTeam( TEAM_UNASSIGNED ) + + if ( GAMEMODE.TeamBased ) then + pl:ConCommand( "gm_showteam" ) + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSpawnAsSpectator() + Desc: Player spawns as a spectator +-----------------------------------------------------------]] +function GM:PlayerSpawnAsSpectator( pl ) + + pl:StripWeapons() + + if ( pl:Team() == TEAM_UNASSIGNED ) then + + pl:Spectate( OBS_MODE_FIXED ) + return + + end + + pl:SetTeam( TEAM_SPECTATOR ) + pl:Spectate( OBS_MODE_ROAMING ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSpawn() + Desc: Called when a player spawns +-----------------------------------------------------------]] +function GM:PlayerSpawn( pl, transiton ) + + -- + -- If the player doesn't have a team in a TeamBased game + -- then spawn him as a spectator + -- + if ( self.TeamBased && ( pl:Team() == TEAM_SPECTATOR || pl:Team() == TEAM_UNASSIGNED ) ) then + + self:PlayerSpawnAsSpectator( pl ) + return + + end + + -- Stop observer mode + pl:UnSpectate() + + player_manager.OnPlayerSpawn( pl, transiton ) + player_manager.RunClass( pl, "Spawn" ) + + -- If we are in transition, do not touch player's weapons + if ( !transiton ) then + -- Call item loadout function + hook.Call( "PlayerLoadout", GAMEMODE, pl ) + end + + -- Set player model + hook.Call( "PlayerSetModel", GAMEMODE, pl ) + + pl:SetupHands() + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSetModel() + Desc: Set the player's model +-----------------------------------------------------------]] +function GM:PlayerSetModel( pl ) + + player_manager.RunClass( pl, "SetModel" ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSetHandsModel() + Desc: Sets the player's view model hands model +-----------------------------------------------------------]] +function GM:PlayerSetHandsModel( pl, ent ) + + local info = player_manager.RunClass( pl, "GetHandsModel" ) + if ( !info ) then + local playermodel = player_manager.TranslateToPlayerModelName( pl:GetModel() ) + info = player_manager.TranslatePlayerHands( playermodel ) + end + + if ( info ) then + ent:SetModel( info.model ) + ent:SetSkin( info.matchBodySkin and pl:GetSkin() or info.skin ) + ent:SetBodyGroups( info.body ) + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerLoadout() + Desc: Give the player the default spawning weapons/ammo +-----------------------------------------------------------]] +function GM:PlayerLoadout( pl ) + + player_manager.RunClass( pl, "Loadout" ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSelectTeamSpawn( player ) + Desc: Find a spawn point entity for this player's team +-----------------------------------------------------------]] +function GM:PlayerSelectTeamSpawn( TeamID, pl ) + + local SpawnPoints = team.GetSpawnPoints( TeamID ) + if ( !SpawnPoints || table.IsEmpty( SpawnPoints ) ) then return end + + local ChosenSpawnPoint = nil + + for i = 0, 6 do + + ChosenSpawnPoint = table.Random( SpawnPoints ) + if ( hook.Call( "IsSpawnpointSuitable", GAMEMODE, pl, ChosenSpawnPoint, i == 6 ) ) then + return ChosenSpawnPoint + end + + end + + return ChosenSpawnPoint + +end + + +--[[--------------------------------------------------------- + Name: gamemode:IsSpawnpointSuitable( player ) + Desc: Find out if the spawnpoint is suitable or not +-----------------------------------------------------------]] +local spawnpointmin = Vector( -16, -16, 0 ) +local spawnpointmax = Vector( 16, 16, 64 ) +function GM:IsSpawnpointSuitable( pl, spawnpointent, bMakeSuitable ) + + local Pos = spawnpointent:GetPos() + + -- Note that we're searching the default hull size here for a player in the way of our spawning. + -- This seems pretty rough, seeing as our player's hull could be different.. but it should do the job + -- (HL2DM kills everything within a 128 unit radius) + if ( pl:Team() == TEAM_SPECTATOR ) then return true end + + local Blockers = 0 + for k, v in ipairs( ents.FindInBox( Pos + spawnpointmin, Pos + spawnpointmax ) ) do + if ( IsValid( v ) && v != pl && v:GetClass() == "player" && v:Alive() ) then + + Blockers = Blockers + 1 + + if ( bMakeSuitable ) then + v:Kill() + end + + end + end + + if ( bMakeSuitable ) then return true end + if ( Blockers > 0 ) then return false end + return true + +end + +-- List of all known spawnpoint entity classes +local SpawnPointEntityClasses = { + -- Half-Life 2 (Deathmatch) Maps + ["info_player_start"] = true, + ["info_player_deathmatch"] = true, + ["info_player_combine"] = true, + ["info_player_rebel"] = true, + + -- Portal 2 Coop + ["info_coop_spawn"] = true, + + -- CS Maps + ["info_player_counterterrorist"] = true, + ["info_player_terrorist"] = true, + + -- DOD Maps + ["info_player_axis"] = true, + ["info_player_allies"] = true, + + -- (Old) GMod Maps + ["gmod_player_start"] = true, + + -- TF Maps + ["info_player_teamspawn"] = true, + + -- INS Maps + ["ins_spawnpoint"] = true, + + -- AOC Maps + ["aoc_spawnpoint"] = true, + + -- Dystopia Maps + ["dys_spawn_point"] = true, + + -- PVKII Maps + ["info_player_pirate"] = true, + ["info_player_viking"] = true, + ["info_player_knight"] = true, + + -- DIPRIP Maps + ["diprip_start_team_blue"] = true, + ["diprip_start_team_red"] = true, + + -- OB Maps + ["info_player_red"] = true, + ["info_player_blue"] = true, + + -- SYN Maps + ["info_player_coop"] = true, + + -- ZPS Maps + ["info_player_human"] = true, + ["info_player_zombie"] = true, + + -- ZM Maps + ["info_player_zombiemaster"] = true, + + -- FOF Maps + ["info_player_fof"] = true, + ["info_player_desperado"] = true, + ["info_player_vigilante"] = true, + + -- L4D Maps + ["info_survivor_rescue"] = true, + -- Removing this one for the time being, c1m4_atrium has one of these in a box under the map + --["info_survivor_position"] = true, + + -- NEOTOKYO Maps + ["info_player_attacker"] = true, + ["info_player_defender"] = true, + + -- Fortress Forever Maps + ["info_ff_teamspawn"] = true +} + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSelectSpawn( player ) + Desc: Find a spawn point entity for this player +-----------------------------------------------------------]] +function GM:PlayerSelectSpawn( pl, transiton ) + + -- If we are in transition, do not reset player's position + if ( transiton ) then return end + + if ( self.TeamBased ) then + local ent = self:PlayerSelectTeamSpawn( pl:Team(), pl ) + if ( IsValid( ent ) ) then return ent end + end + + -- Save information about all of the spawn points + -- in a team based game you'd split up the spawns + if ( !IsTableOfEntitiesValid( self.SpawnPoints ) ) then + self.LastSpawnPoint = 0 + self.HasMasterSpawnPoints = false + + self.SpawnPoints = {} + for _, ent in ipairs( ents.GetAll() ) do + if ( SpawnPointEntityClasses[ ent:GetClass() ] ) then + self.SpawnPoints[#self.SpawnPoints + 1] = ent + + if ( ent:HasSpawnFlags( 1 ) ) then + self.HasMasterSpawnPoints = true + end + end + end + end + + local Count = #self.SpawnPoints + if ( Count == 0 ) then + Msg("[PlayerSelectSpawn] Error! No spawn points!\n") + return nil + end + + -- If any of the spawnpoints have a MASTER flag then only use that one. + -- This is needed for single player maps. + if ( self.HasMasterSpawnPoints ) then + for _, ent in ipairs( self.SpawnPoints ) do + if ( ent:HasSpawnFlags( 1 ) && hook.Call( "IsSpawnpointSuitable", GAMEMODE, pl, ent, true ) ) then + return ent + end + end + end + + local ChosenSpawnPoint = nil + + -- Try to work out the best, random spawnpoint + for i = 1, Count do + + ChosenSpawnPoint = self.SpawnPoints[math.random( Count )] + + if ( IsValid( ChosenSpawnPoint ) && ChosenSpawnPoint:IsInWorld() ) then + if ( ( ChosenSpawnPoint == pl:GetVar( "LastSpawnpoint" ) || ChosenSpawnPoint == self.LastSpawnPoint ) && Count > 1 ) then continue end + + if ( hook.Call( "IsSpawnpointSuitable", GAMEMODE, pl, ChosenSpawnPoint, i == Count ) ) then + + self.LastSpawnPoint = ChosenSpawnPoint + pl:SetVar( "LastSpawnpoint", ChosenSpawnPoint ) + return ChosenSpawnPoint + + end + + end + + end + + return ChosenSpawnPoint + +end + +--[[--------------------------------------------------------- + Name: gamemode:WeaponEquip( weapon ) + Desc: Player just picked up (or was given) weapon +-----------------------------------------------------------]] +function GM:WeaponEquip( weapon ) +end + +--[[--------------------------------------------------------- + Name: gamemode:ScalePlayerDamage( ply, hitgroup, dmginfo ) + Desc: Scale the damage based on being shot in a hitbox + Return true to not take damage +-----------------------------------------------------------]] +function GM:ScalePlayerDamage( ply, hitgroup, dmginfo ) + + -- More damage if we're shot in the head + if ( hitgroup == HITGROUP_HEAD ) then + + dmginfo:ScaleDamage( 2 ) + + end + + -- Less damage if we're shot in the arms or legs + if ( hitgroup == HITGROUP_LEFTARM || + hitgroup == HITGROUP_RIGHTARM || + hitgroup == HITGROUP_LEFTLEG || + hitgroup == HITGROUP_RIGHTLEG || + hitgroup == HITGROUP_GEAR ) then + + dmginfo:ScaleDamage( 0.25 ) + + end + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerDeathSound() + Desc: Return true to not play the default sounds +-----------------------------------------------------------]] +function GM:PlayerDeathSound() + return false +end + +--[[--------------------------------------------------------- + Name: gamemode:SetupPlayerVisibility() + Desc: Add extra positions to the player's PVS +-----------------------------------------------------------]] +function GM:SetupPlayerVisibility( pPlayer, pViewEntity ) + --AddOriginToPVS( vector_position_here ) +end + +--[[--------------------------------------------------------- + Name: gamemode:OnDamagedByExplosion( ply, dmginfo) + Desc: Player has been hurt by an explosion +-----------------------------------------------------------]] +local MIN_SHOCK_AND_CONFUSION_DAMAGE = 30 +local MIN_EAR_RINGING_DISTANCE = 240 + +function GM:OnDamagedByExplosion( ply, info ) + + local ear_ringing = false + local inflictor = info:GetInflictor() + if ( IsValid( inflictor ) ) then + local delta = ply:GetPos() - inflictor:GetPos() + ear_ringing = delta:Length() < MIN_EAR_RINGING_DISTANCE + end + + local shock = info:GetDamage() >= MIN_SHOCK_AND_CONFUSION_DAMAGE + + if ( !shock and !ear_ringing ) then return end + + -- The effect names are actually backwards + if ( shock ) then + ply:SetDSP( math.random( 35, 37 ), false ) + return + end + + ply:SetDSP( math.random( 32, 34 ), false ) +end + +--[[--------------------------------------------------------- + Name: gamemode:CanPlayerSuicide( ply ) + Desc: Player typed KILL in the console. Can they kill themselves? +-----------------------------------------------------------]] +function GM:CanPlayerSuicide( ply ) + return true +end + +--[[--------------------------------------------------------- + Name: gamemode:CanPlayerEnterVehicle( player, vehicle, role ) + Desc: Return true if player can enter vehicle +-----------------------------------------------------------]] +function GM:CanPlayerEnterVehicle( ply, vehicle, role ) + return true +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerEnteredVehicle( player, vehicle, role ) + Desc: Player entered the vehicle fine +-----------------------------------------------------------]] +function GM:PlayerEnteredVehicle( ply, vehicle, role ) +end + +--[[--------------------------------------------------------- + Name: gamemode:CanExitVehicle() + Desc: If the player is allowed to leave the vehicle, return true +-----------------------------------------------------------]] +function GM:CanExitVehicle( vehicle, passenger ) + return true +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerLeaveVehicle() + Desc: Player left the vehicle +-----------------------------------------------------------]] +function GM:PlayerLeaveVehicle( ply, vehicle ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSwitchFlashlight() + Desc: Return true to allow action +-----------------------------------------------------------]] +function GM:PlayerSwitchFlashlight( ply, SwitchOn ) + return ply:CanUseFlashlight() +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerCanJoinTeam( ply, teamid ) + Desc: Allow mods/addons to easily determine whether a player + can join a team or not +-----------------------------------------------------------]] +function GM:PlayerCanJoinTeam( ply, teamid ) + + local TimeBetweenSwitches = GAMEMODE.SecondsBetweenTeamSwitches or 10 + if ( ply.LastTeamSwitch && RealTime() - ply.LastTeamSwitch < TimeBetweenSwitches ) then + ply.LastTeamSwitch = ply.LastTeamSwitch + 1 + ply:ChatPrint( Format( "Please wait %i more seconds before trying to change team again", ( TimeBetweenSwitches - ( RealTime() - ply.LastTeamSwitch ) ) + 1 ) ) + return false + end + + -- Already on this team! + if ( ply:Team() == teamid ) then + ply:ChatPrint( "You're already on that team" ) + return false + end + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerRequestTeam() + Desc: Player wants to change team +-----------------------------------------------------------]] +function GM:PlayerRequestTeam( ply, teamid ) + + -- No changing teams if not teambased! + if ( !GAMEMODE.TeamBased ) then return end + + -- This team isn't joinable + if ( !team.Joinable( teamid ) ) then + ply:ChatPrint( "You can't join that team" ) + return end + + -- This team isn't joinable + if ( !GAMEMODE:PlayerCanJoinTeam( ply, teamid ) ) then + -- Messages here should be outputted by this function + return end + + GAMEMODE:PlayerJoinTeam( ply, teamid ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerJoinTeam() + Desc: Make player join this team +-----------------------------------------------------------]] +function GM:PlayerJoinTeam( ply, teamid ) + + local iOldTeam = ply:Team() + + if ( ply:Alive() ) then + if ( iOldTeam == TEAM_SPECTATOR || iOldTeam == TEAM_UNASSIGNED ) then + ply:KillSilent() + else + ply:Kill() + end + end + + ply:SetTeam( teamid ) + ply.LastTeamSwitch = RealTime() + + GAMEMODE:OnPlayerChangedTeam( ply, iOldTeam, teamid ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:OnPlayerChangedTeam( ply, oldteam, newteam ) +-----------------------------------------------------------]] +function GM:OnPlayerChangedTeam( ply, oldteam, newteam ) + + -- Here's an immediate respawn thing by default. If you want to + -- re-create something more like CS or some shit you could probably + -- change to a spectator or something while dead. + if ( newteam == TEAM_SPECTATOR ) then + + -- If we changed to spectator mode, respawn where we are + local Pos = ply:EyePos() + ply:Spawn() + ply:SetPos( Pos ) + + elseif ( oldteam == TEAM_SPECTATOR ) then + + -- If we're changing from spectator, join the game + ply:Spawn() + + else + + -- If we're straight up changing teams just hang + -- around until we're ready to respawn onto the + -- team that we chose + + end + + PrintMessage( HUD_PRINTTALK, Format( "%s joined '%s'", ply:Nick(), team.GetName( newteam ) ) ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerSpray() + Desc: Return true to prevent player spraying +-----------------------------------------------------------]] +function GM:PlayerSpray( ply ) + + return false + +end + +--[[--------------------------------------------------------- + Name: gamemode:OnPlayerHitGround() + Desc: Return true to disable default action +-----------------------------------------------------------]] +function GM:OnPlayerHitGround( ply, bInWater, bOnFloater, flFallSpeed ) + + -- Apply damage and play collision sound here + -- then return true to disable the default action + --MsgN( ply, bInWater, bOnFloater, flFallSpeed ) + --return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:GetFallDamage() + Desc: return amount of damage to do due to fall +-----------------------------------------------------------]] +local mp_falldamage = GetConVar( "mp_falldamage" ) + +function GM:GetFallDamage( ply, flFallSpeed ) + + if ( mp_falldamage:GetBool() ) then -- realistic fall damage is on + return ( flFallSpeed - 526.5 ) * ( 100 / 396 ) -- the Source SDK value + end + + return 10 + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerCanSeePlayersChat() + Desc: Can this player see the other player's chat? +-----------------------------------------------------------]] +function GM:PlayerCanSeePlayersChat( strText, bTeamOnly, pListener, pSpeaker ) + + if ( bTeamOnly ) then + if ( !IsValid( pSpeaker ) || !IsValid( pListener ) ) then return false end + if ( pListener:Team() != pSpeaker:Team() ) then return false end + end + + return true + +end + +local sv_alltalk = GetConVar( "sv_alltalk" ) + +--[[--------------------------------------------------------- + Name: gamemode:PlayerCanHearPlayersVoice() + Desc: Can this player see the other player's voice? + Returns 2 bools. + 1. Can the player hear the other player + 2. Can they hear them spacially +-----------------------------------------------------------]] +function GM:PlayerCanHearPlayersVoice( pListener, pTalker ) + + local alltalk = sv_alltalk:GetInt() + if ( alltalk >= 1 ) then return true, alltalk == 2 end + + return pListener:Team() == pTalker:Team(), false + +end + +--[[--------------------------------------------------------- + Name: gamemode:NetworkIDValidated() + Desc: Called when Steam has validated this as a valid player +-----------------------------------------------------------]] +function GM:NetworkIDValidated( name, steamid ) + + -- MsgN( "GM:NetworkIDValidated", name, steamid ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerShouldTaunt( ply, actid ) +-----------------------------------------------------------]] +function GM:PlayerShouldTaunt( ply, actid ) + + -- The default behaviour is to always let them act + -- Some gamemodes will obviously want to stop this for certain players by returning false + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerStartTaunt( ply, actid, length ) +-----------------------------------------------------------]] +function GM:PlayerStartTaunt( ply, actid, length ) +end + +--[[--------------------------------------------------------- + Name: gamemode:AllowPlayerPickup( ply, object ) +-----------------------------------------------------------]] +function GM:AllowPlayerPickup( ply, object ) + + -- Should the player be allowed to pick this object up (using ENTER)? + -- If no then return false. Default is HELL YEAH + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerDroppedWeapon() + Desc: Player has dropped a weapon +-----------------------------------------------------------]] +function GM:PlayerDroppedWeapon( ply, weapon ) +end + +--[[--------------------------------------------------------- + These are buttons that the client is pressing. They're used + in Sandbox mode to control things like wheels, thrusters etc. +-----------------------------------------------------------]] +function GM:PlayerButtonDown( ply, btn ) end +function GM:PlayerButtonUp( ply, btn ) end + +concommand.Add( "changeteam", function( pl, cmd, args ) hook.Call( "PlayerRequestTeam", GAMEMODE, pl, tonumber( args[ 1 ] ) ) end ) + +--[[--------------------------------------------------------- + Name: gamemode:HandlePlayerArmorReduction() + Desc: Handle player armor reduction +-----------------------------------------------------------]] +function GM:HandlePlayerArmorReduction( ply, dmginfo ) + + -- If no armor, or special damage types, bypass armor + if ( ply:Armor() <= 0 || bit.band( dmginfo:GetDamageType(), DMG_FALL + DMG_DROWN + DMG_POISON + DMG_RADIATION ) != 0 ) then return end + + local flBonus = 1.0 -- Each Point of Armor is worth 1/x points of health + local flRatio = 0.2 -- Armor Takes 80% of the damage + if ( GetConVar( "player_old_armor" ):GetBool() ) then + flBonus = 0.5 + end + + local flNew = dmginfo:GetDamage() * flRatio + local flArmor = (dmginfo:GetDamage() - flNew) * flBonus + + if ( !GetConVar( "player_old_armor" ):GetBool() ) then + if ( flArmor < 0.1 ) then flArmor = 0 end -- Let's not have tiny amounts of damage reduce a lot of our armor + else if ( flArmor < 1.0 ) then flArmor = 1.0 end + end + + -- Does this use more armor than we have? + if ( flArmor > ply:Armor() ) then + + flArmor = ply:Armor() * ( 1 / flBonus ) + flNew = dmginfo:GetDamage() - flArmor + ply:SetArmor( 0 ) + + else + ply:SetArmor( ply:Armor() - flArmor ) + end + + dmginfo:SetDamage( flNew ) + +end diff --git a/gamemodes/base/gamemode/player_class/player_default.lua b/gamemodes/base/gamemode/player_class/player_default.lua new file mode 100644 index 0000000..773b7c9 --- /dev/null +++ b/gamemodes/base/gamemode/player_class/player_default.lua @@ -0,0 +1,135 @@ + +AddCSLuaFile() + +include( "taunt_camera.lua" ) + +local PLAYER = {} + +PLAYER.DisplayName = "Default Class" + +PLAYER.SlowWalkSpeed = 200 -- How fast to move when slow-walking (+WALK) +PLAYER.WalkSpeed = 400 -- How fast to move when not running +PLAYER.RunSpeed = 600 -- How fast to move when running +PLAYER.CrouchedWalkSpeed = 0.3 -- Multiply move speed by this when crouching +PLAYER.DuckSpeed = 0.3 -- How fast to go from not ducking, to ducking +PLAYER.UnDuckSpeed = 0.3 -- How fast to go from ducking, to not ducking +PLAYER.JumpPower = 200 -- How powerful our jump should be +PLAYER.CanUseFlashlight = true -- Can we use the flashlight +PLAYER.MaxHealth = 100 -- Max health we can have +PLAYER.MaxArmor = 100 -- Max armor we can have +PLAYER.StartHealth = 100 -- How much health we start with +PLAYER.StartArmor = 0 -- How much armour we start with +PLAYER.DropWeaponOnDie = false -- Do we drop our weapon when we die +PLAYER.TeammateNoCollide = true -- Do we collide with teammates or run straight through them +PLAYER.AvoidPlayers = true -- Automatically swerves around other players +PLAYER.UseVMHands = true -- Uses viewmodel hands + +-- +-- Name: PLAYER:SetupDataTables +-- Desc: Set up the network table accessors +-- Arg1: +-- Ret1: +-- +function PLAYER:SetupDataTables() +end + +-- +-- Name: PLAYER:Init +-- Desc: Called when the class object is created (shared) +-- Arg1: +-- Ret1: +-- +function PLAYER:Init() +end + +-- +-- Name: PLAYER:Spawn +-- Desc: Called serverside only when the player spawns +-- Arg1: +-- Ret1: +-- +function PLAYER:Spawn() +end + +-- +-- Name: PLAYER:Loadout +-- Desc: Called on spawn to give the player their default loadout +-- Arg1: +-- Ret1: +-- +function PLAYER:Loadout() + + self.Player:Give( "weapon_pistol" ) + self.Player:GiveAmmo( 255, "Pistol", true ) + +end + +function PLAYER:SetModel() + + local cl_playermodel = self.Player:GetInfo( "cl_playermodel" ) + local modelname = player_manager.TranslatePlayerModel( cl_playermodel ) + util.PrecacheModel( modelname ) + self.Player:SetModel( modelname ) + +end + +function PLAYER:Death( inflictor, attacker ) +end + +-- Clientside only +function PLAYER:CalcView( view ) end -- Setup the player's view +function PLAYER:CreateMove( cmd ) end -- Creates the user command on the client +function PLAYER:ShouldDrawLocal() end -- Return true if we should draw the local player + +-- Shared +function PLAYER:StartMove( cmd, mv ) end -- Copies from the user command to the move +function PLAYER:Move( mv ) end -- Runs the move (can run multiple times for the same client) +function PLAYER:FinishMove( mv ) end -- Copy the results of the move back to the Player + +-- +-- Name: PLAYER:ViewModelChanged +-- Desc: Called when the player changes their weapon to another one causing their viewmodel model to change +-- Arg1: Entity|viewmodel|The viewmodel that is changing +-- Arg2: string|old|The old model +-- Arg3: string|new|The new model +-- Ret1: +-- +function PLAYER:ViewModelChanged( vm, old, new ) +end + +-- +-- Name: PLAYER:PreDrawViewmodel +-- Desc: Called before the viewmodel is being drawn (clientside) +-- Arg1: Entity|viewmodel|The viewmodel +-- Arg2: Entity|weapon|The weapon +-- Ret1: +-- +function PLAYER:PreDrawViewModel( vm, weapon ) +end + +-- +-- Name: PLAYER:PostDrawViewModel +-- Desc: Called after the viewmodel has been drawn (clientside) +-- Arg1: Entity|viewmodel|The viewmodel +-- Arg2: Entity|weapon|The weapon +-- Ret1: +-- +function PLAYER:PostDrawViewModel( vm, weapon ) +end + +-- +-- Name: PLAYER:GetHandsModel +-- Desc: Called on player spawn to determine which hand model to use +-- Arg1: +-- Ret1: table|info|A table containing model, skin and body +-- +function PLAYER:GetHandsModel() + + -- return { model = "models/weapons/c_arms_cstrike.mdl", skin = 1, body = "0100000" } + + local playermodel = player_manager.TranslateToPlayerModelName( self.Player:GetModel() ) + return player_manager.TranslatePlayerHands( playermodel ) + +end + +player_manager.RegisterClass( "player_default", PLAYER, nil ) diff --git a/gamemodes/base/gamemode/player_class/taunt_camera.lua b/gamemodes/base/gamemode/player_class/taunt_camera.lua new file mode 100644 index 0000000..e3798e2 --- /dev/null +++ b/gamemodes/base/gamemode/player_class/taunt_camera.lua @@ -0,0 +1,126 @@ + +AddCSLuaFile() + +local m_pitch = GetConVar( "m_pitch" ) +local m_yaw = GetConVar( "m_yaw" ) + +-- +-- This is designed so you can call it like +-- +-- tauntcam = TauntCamera() +-- +-- Then you have your own copy. +-- +function TauntCamera() + + local CAM = {} + + local WasOn = false + + local CustomAngles = angle_zero + local PlayerLockAngles = nil + + local InLerp = 0 + local OutLerp = 1 + + -- + -- Draw the local player if we're active in any way + -- + CAM.ShouldDrawLocalPlayer = function( self, ply, on ) + + return on || OutLerp < 1 + + end + + -- + -- Implements the third person, rotation view (with lerping in/out) + -- + CAM.CalcView = function( self, view, ply, on ) + + if ( !ply:Alive() || !IsValid( ply:GetViewEntity() ) || ply:GetViewEntity() != ply ) then on = false end + + if ( WasOn != on ) then + + if ( on ) then InLerp = 0 end + if ( !on ) then OutLerp = 0 end + + WasOn = on + + end + + if ( !on && OutLerp >= 1 ) then + + CustomAngles = view.angles * 1 + CustomAngles.r = 0 + PlayerLockAngles = nil + InLerp = 0 + return + + end + + if ( PlayerLockAngles == nil ) then return end + + -- + -- Simple 3rd person camera + -- + local TargetOrigin = view.origin - CustomAngles:Forward() * 100 + local tr = util.TraceHull( { start = view.origin, endpos = TargetOrigin, mask = MASK_SHOT, filter = player.GetAll(), mins = Vector( -8, -8, -8 ), maxs = Vector( 8, 8, 8 ) } ) + TargetOrigin = tr.HitPos + tr.HitNormal + + if ( InLerp < 1 ) then + + InLerp = InLerp + FrameTime() * 5.0 + view.origin = LerpVector( InLerp, view.origin, TargetOrigin ) + view.angles = LerpAngle( InLerp, PlayerLockAngles, CustomAngles ) + return true + + end + + if ( OutLerp < 1 ) then + + OutLerp = OutLerp + FrameTime() * 3.0 + view.origin = LerpVector( 1-OutLerp, view.origin, TargetOrigin ) + view.angles = LerpAngle( 1-OutLerp, PlayerLockAngles, CustomAngles ) + return true + + end + + view.angles = CustomAngles * 1 + view.origin = TargetOrigin + return true + + end + + -- + -- Freezes the player in position and uses the input from the user command to + -- rotate the custom third person camera + -- + CAM.CreateMove = function( self, cmd, ply, on ) + + if ( !ply:Alive() ) then on = false end + if ( !on ) then return end + + if ( PlayerLockAngles == nil ) then + PlayerLockAngles = CustomAngles * 1 + end + + -- + -- Rotate our view + -- + CustomAngles.pitch = CustomAngles.pitch + cmd:GetMouseY() * m_pitch:GetFloat() + CustomAngles.yaw = CustomAngles.yaw - cmd:GetMouseX() * m_yaw:GetFloat() + + -- + -- Lock the player's controls and angles + -- + cmd:SetViewAngles( PlayerLockAngles ) + cmd:ClearButtons() + cmd:ClearMovement() + + return true + + end + + return CAM + +end diff --git a/gamemodes/base/gamemode/player_shd.lua b/gamemodes/base/gamemode/player_shd.lua new file mode 100644 index 0000000..f720eee --- /dev/null +++ b/gamemodes/base/gamemode/player_shd.lua @@ -0,0 +1,135 @@ + +--[[--------------------------------------------------------- + Name: gamemode:PlayerTraceAttack( ) + Desc: A bullet has been fired and hit this player + Return true to completely override internals +-----------------------------------------------------------]] +function GM:PlayerTraceAttack( ply, dmginfo, dir, trace ) + + return false + +end + +--[[--------------------------------------------------------- + Name: gamemode:SetPlayerSpeed( ) + Desc: Sets the player's run/walk speed +-----------------------------------------------------------]] +function GM:SetPlayerSpeed( ply, walk, run ) + + ply:SetWalkSpeed( walk ) + ply:SetRunSpeed( run ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerFootstep( ply, vPos, iFoot, strSoundName, fVolume, pFilter ) + Desc: Called when a player steps + pFilter is the recipient filter to use for effects/sounds + and is only valid SERVERSIDE. Clientside needs no filter! + Return true to not play normal sound +-----------------------------------------------------------]] +function GM:PlayerFootstep( ply, vPos, iFoot, strSoundName, fVolume, pFilter ) + if ( IsValid( ply ) and !ply:Alive() ) then + return true + end + + --[[ + -- Draw effect on footdown + local effectdata = EffectData() + effectdata:SetOrigin( vPos ) + util.Effect( "phys_unfreeze", effectdata, true, pFilter ) + --]] + + --[[ + -- Don't play left foot + if ( iFoot == 0 ) then return true end + --]] + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerStepSoundTime( ply, iType, bWalking ) + Desc: Return the time between footsteps +-----------------------------------------------------------]] +function GM:PlayerStepSoundTime( ply, iType, bWalking ) + + local fStepTime = 350 + local fMaxSpeed = ply:GetMaxSpeed() + + if ( iType == STEPSOUNDTIME_NORMAL || iType == STEPSOUNDTIME_WATER_FOOT ) then + + if ( fMaxSpeed <= 100 ) then + fStepTime = 400 + elseif ( fMaxSpeed <= 300 ) then + fStepTime = 350 + else + fStepTime = 250 + end + + elseif ( iType == STEPSOUNDTIME_ON_LADDER ) then + + fStepTime = 450 + + elseif ( iType == STEPSOUNDTIME_WATER_KNEE ) then + + fStepTime = 600 + + end + + -- Step slower if crouching + if ( ply:Crouching() ) then + fStepTime = fStepTime + 50 + end + + return fStepTime + +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerNoClip( player, bool ) + Desc: Player pressed the noclip key, return true if + the player is allowed to noclip, false to block +-----------------------------------------------------------]] +function GM:PlayerNoClip( pl, on ) + if ( !on ) then return true end + -- Allow noclip if we're in single player and living + return game.SinglePlayer() && IsValid( pl ) && pl:Alive() + +end + +-- +-- FindUseEntity +-- +function GM:FindUseEntity( ply, ent ) + + -- ent is what the game found to use by default + -- return what you REALLY want it to use + + -- Simple fix to allow entities inside playerclip brushes to be used. Necessary for c1a0c map in Half-Life: Source + if ( !IsValid( ent ) ) then + local traceEnt = util.TraceLine( { + start = ply:GetShootPos(), + endpos = ply:GetShootPos() + ply:GetAimVector() * 72, + filter = ply + } ).Entity + if ( IsValid( traceEnt ) ) then return traceEnt end + end + + return ent + +end + +-- +-- Player tick +-- +function GM:PlayerTick( ply, mv ) +end + +-- +-- Player is switching weapon. Return true to prevent the switch. +-- +function GM:PlayerSwitchWeapon( ply, oldwep, newwep ) + + return false + +end diff --git a/gamemodes/base/gamemode/shared.lua b/gamemodes/base/gamemode/shared.lua new file mode 100644 index 0000000..e278548 --- /dev/null +++ b/gamemodes/base/gamemode/shared.lua @@ -0,0 +1,272 @@ + +--[[--------------------------------------------------------- + + This file should contain variables and functions that are + the same on both client and server. + + This file will get sent to the client - so don't add + anything to this file that you don't want them to be + able to see. + +-----------------------------------------------------------]] + +include( "obj_player_extend.lua" ) + +include( "gravitygun.lua" ) +include( "player_shd.lua" ) +include( "animations.lua" ) +include( "player_class/player_default.lua" ) + +GM.Name = "Base Gamemode" +GM.Author = "Garry Newman" +GM.Email = "garrynewman@gmail.com" +GM.Website = "www.garry.tv" +GM.TeamBased = false + +--[[--------------------------------------------------------- + Name: gamemode:KeyPress( ) + Desc: Player pressed a key (see IN enums) +-----------------------------------------------------------]] +function GM:KeyPress( player, key ) +end + +--[[--------------------------------------------------------- + Name: gamemode:KeyRelease( ) + Desc: Player released a key (see IN enums) +-----------------------------------------------------------]] +function GM:KeyRelease( player, key ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PlayerConnect( ) + Desc: Player has connects to the server (hasn't spawned) +-----------------------------------------------------------]] +function GM:PlayerConnect( name, address ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PropBreak( ) + Desc: Prop has been broken +-----------------------------------------------------------]] +function GM:PropBreak( attacker, prop ) +end + +--[[--------------------------------------------------------- + Name: gamemode:PhysgunPickup( ) + Desc: Return true if player can pickup entity +-----------------------------------------------------------]] +function GM:PhysgunPickup( ply, ent ) + + -- Don't pick up players + if ( ent:GetClass() == "player" ) then return false end + + return true +end + +--[[--------------------------------------------------------- + Name: gamemode:PhysgunDrop( ) + Desc: Dropped an entity +-----------------------------------------------------------]] +function GM:PhysgunDrop( ply, ent ) +end + +--[[--------------------------------------------------------- + Name: Text to show in the server browser +-----------------------------------------------------------]] +function GM:GetGameDescription() + return self.Name +end + +--[[--------------------------------------------------------- + Name: Saved +-----------------------------------------------------------]] +function GM:Saved() +end + +--[[--------------------------------------------------------- + Name: Restored +-----------------------------------------------------------]] +function GM:Restored() +end + +--[[--------------------------------------------------------- + Name: EntityRemoved + Desc: Called right before an entity is removed. Note that this + isn't going to be totally reliable on the client since the client + only knows about entities that it has had in its PVS. +-----------------------------------------------------------]] +function GM:EntityRemoved( ent ) +end + +--[[--------------------------------------------------------- + Name: Tick + Desc: Like Think except called every tick on both client and server +-----------------------------------------------------------]] +function GM:Tick() +end + +--[[--------------------------------------------------------- + Name: OnEntityCreated + Desc: Called right after the Entity has been made visible to Lua +-----------------------------------------------------------]] +function GM:OnEntityCreated( Ent ) +end + +--[[--------------------------------------------------------- + Name: gamemode:EntityKeyValue( ent, key, value ) + Desc: Called when an entity has a keyvalue set + Returning a string it will override the value +-----------------------------------------------------------]] +function GM:EntityKeyValue( ent, key, value ) +end + +--[[--------------------------------------------------------- + Name: gamemode:CreateTeams() + Desc: Note - HAS to be shared. +-----------------------------------------------------------]] +function GM:CreateTeams() + + -- Don't do this if not teambased. But if it is teambased we + -- create a few teams here as an example. If you're making a teambased + -- gamemode you should override this function in your gamemode + + if ( !GAMEMODE.TeamBased ) then return end + + TEAM_BLUE = 1 + team.SetUp( TEAM_BLUE, "Blue Team", Color( 0, 0, 255 ) ) + team.SetSpawnPoint( TEAM_BLUE, "ai_hint" ) -- <-- This would be info_terrorist or some entity that is in your map + + TEAM_ORANGE = 2 + team.SetUp( TEAM_ORANGE, "Orange Team", Color( 255, 150, 0 ) ) + team.SetSpawnPoint( TEAM_ORANGE, "sky_camera" ) -- <-- This would be info_terrorist or some entity that is in your map + + TEAM_SEXY = 3 + team.SetUp( TEAM_SEXY, "Sexy Team", Color( 255, 150, 150 ) ) + team.SetSpawnPoint( TEAM_SEXY, "info_player_start" ) -- <-- This would be info_terrorist or some entity that is in your map + + team.SetSpawnPoint( TEAM_SPECTATOR, "worldspawn" ) + +end + +--[[--------------------------------------------------------- + Name: gamemode:ShouldCollide( Ent1, Ent2 ) + Desc: This should always return true unless you have + a good reason for it not to. +-----------------------------------------------------------]] +function GM:ShouldCollide( Ent1, Ent2 ) + + return true + +end + +--[[--------------------------------------------------------- + Name: gamemode:Move + This basically overrides the NOCLIP, PLAYERMOVE movement stuff. + It's what actually performs the move. + Return true to not perform any default movement actions. (completely override) +-----------------------------------------------------------]] +function GM:Move( ply, mv ) + + if ( drive.Move( ply, mv ) ) then return true end + if ( player_manager.RunClass( ply, "Move", mv ) ) then return true end + +end + +--[[--------------------------------------------------------- +-- Purpose: This is called pre player movement and copies all the data necessary +-- from the player for movement. Copy from the usercmd to move. +-----------------------------------------------------------]] +function GM:SetupMove( ply, mv, cmd ) + + if ( drive.StartMove( ply, mv, cmd ) ) then return true end + if ( player_manager.RunClass( ply, "StartMove", mv, cmd ) ) then return true end + +end + +--[[--------------------------------------------------------- + Name: gamemode:FinishMove( player, movedata ) +-----------------------------------------------------------]] +function GM:FinishMove( ply, mv ) + + if ( drive.FinishMove( ply, mv ) ) then return true end + if ( player_manager.RunClass( ply, "FinishMove", mv ) ) then return true end + +end + +--[[--------------------------------------------------------- + Called after the player's think. +-----------------------------------------------------------]] +function GM:PlayerPostThink( ply ) + +end + +--[[--------------------------------------------------------- + A player has started driving an entity +-----------------------------------------------------------]] +function GM:StartEntityDriving( ent, ply ) + + drive.Start( ply, ent ) + +end + +--[[--------------------------------------------------------- + A player has stopped driving an entity +-----------------------------------------------------------]] +function GM:EndEntityDriving( ent, ply ) + + drive.End( ply, ent ) + +end + +--[[--------------------------------------------------------- + To update the player's animation during a drive +-----------------------------------------------------------]] +function GM:PlayerDriveAnimate( ply ) + +end + +--[[--------------------------------------------------------- + The gamemode has been reloaded +-----------------------------------------------------------]] +function GM:OnReloaded() +end + +function GM:PreGamemodeLoaded() +end + +function GM:OnGamemodeLoaded() +end + +function GM:PostGamemodeLoaded() +end + +-- +-- Name: GM:OnViewModelChanged +-- Desc: Called when the player changes their weapon to another one - and their viewmodel model changes +-- Arg1: Entity|viewmodel|The viewmodel that is changing +-- Arg2: string|old|The old model +-- Arg3: string|new|The new model +-- Ret1: +-- +function GM:OnViewModelChanged( vm, old, new ) + + local ply = vm:GetOwner() + if ( IsValid( ply ) ) then + player_manager.RunClass( ply, "ViewModelChanged", vm, old, new ) + end + +end + +--[[--------------------------------------------------------- + Disable properties serverside for all non-sandbox derived gamemodes. +-----------------------------------------------------------]] +function GM:CanProperty( pl, property, ent ) + return false +end + +--[[--------------------------------------------------------- + Allow hooks to override bullet without ignoring all other hooks +-----------------------------------------------------------]] +function GM:EntityFireBullets( ent, bullets ) + return true +end diff --git a/gamemodes/base/gamemode/variable_edit.lua b/gamemodes/base/gamemode/variable_edit.lua new file mode 100644 index 0000000..ddfaf96 --- /dev/null +++ b/gamemodes/base/gamemode/variable_edit.lua @@ -0,0 +1,32 @@ + +-- +-- Called when we've received a call from a client who wants to edit +-- a particular entity. +-- +function GM:VariableEdited( ent, ply, key, val, editor ) + + if ( !IsValid( ent ) ) then return end + if ( !IsValid( ply ) ) then return end + + -- + -- Check with the gamemode that we can edit the entity + -- + local CanEdit = hook.Run( "CanEditVariable", ent, ply, key, val, editor ) + if ( !CanEdit ) then return end + + -- + -- Actually apply the edited value + -- + ent:EditValue( key, val ) + +end + +-- +-- Your gamemode should use this hook to allow/dissallow editing +-- By default only admins can edit entities. +-- +function GM:CanEditVariable( ent, ply, key, val, editor ) + + return ply:IsAdmin() || game.SinglePlayer() + +end diff --git a/gamemodes/base/send.txt b/gamemodes/base/send.txt new file mode 100644 index 0000000..5c3d66d --- /dev/null +++ b/gamemodes/base/send.txt @@ -0,0 +1,32 @@ +# +# These files aren't fully sent to the client because they +# should never change. Instead we send the CRC of this file +# so the client can load their version - and we know it's kewl. +# +gamemodes\base\gamemode\player_class\player_default.lua +gamemodes\base\entities\weapons\weapon_base\cl_init.lua +gamemodes\base\entities\weapons\weapon_base\shared.lua +gamemodes\base\entities\weapons\weapon_base\ai_translations.lua +gamemodes\base\entities\weapons\weapon_base\sh_anim.lua +gamemodes\base\entities\entities\base_point.lua +gamemodes\base\entities\entities\base_ai\cl_init.lua +gamemodes\base\entities\entities\base_ai\shared.lua +gamemodes\base\entities\entities\base_anim.lua +gamemodes\base\entities\entities\base_entity\cl_init.lua +gamemodes\base\entities\entities\base_entity\shared.lua +gamemodes\base\entities\entities\prop_effect.lua +gamemodes\base\entities\effects\base.lua +gamemodes\base\entities\effects\dof_node.lua +gamemodes\base\gamemode\cl_init.lua +gamemodes\base\gamemode\shared.lua +gamemodes\base\gamemode\cl_scoreboard.lua +gamemodes\base\gamemode\cl_targetid.lua +gamemodes\base\gamemode\cl_hudpickup.lua +gamemodes\base\gamemode\cl_spawnmenu.lua +gamemodes\base\gamemode\cl_deathnotice.lua +gamemodes\base\gamemode\cl_pickteam.lua +gamemodes\base\gamemode\cl_voice.lua +gamemodes\base\gamemode\gravitygun.lua +gamemodes\base\gamemode\player_shd.lua +gamemodes\base\gamemode\animations.lua +gamemodes\base\gamemode\obj_player_extend.lua \ No newline at end of file diff --git a/gamemodes/darkrp/.editorconfig b/gamemodes/darkrp/.editorconfig new file mode 100644 index 0000000..ef8f44c --- /dev/null +++ b/gamemodes/darkrp/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] + +end_of_line = lf + +[*.{editorconfig,json,yml,lua,txt,md,sh}] + +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 +charset = utf-8 diff --git a/gamemodes/darkrp/.github/ISSUE_TEMPLATE/Bug.yml b/gamemodes/darkrp/.github/ISSUE_TEMPLATE/Bug.yml new file mode 100644 index 0000000..1f61c0f --- /dev/null +++ b/gamemodes/darkrp/.github/ISSUE_TEMPLATE/Bug.yml @@ -0,0 +1,87 @@ +# +# Check the docs on how to write GitHub forms: +# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/about-issue-and-pull-request-templates +# + +name: "🪳 Bug Report" +description: Report a bug in DarkRP. +title: "" + +body: + - type: markdown + + attributes: + value: | + This place is only intended for bug reports. + If you need help, join the **[DarkRP Discord](https://darkrp.page.link/discord)** + + - type: textarea + id: problem + + validations: + required: true + + attributes: + description: | + Describe the issue as accurately as possible. + + placeholder: | + I'm unable to do x when .. + + label: Problem + + - type: textarea + id: reproduce + + validations: + required: false + + attributes: + description: | + Describe how we can reproduce the issue. + + placeholder: | + 1. I first do x + 2. Then I do y and see z + + label: Reproduction + + - type: markdown + + attributes: + value: | + It's important for us to be able to reproduce your problem + so we fix it more easily and be sure it's solved. + + - type: textarea + id: errors + + validations: + required: false + + attributes: + description: | + Provide any errors. Please make sure to look at both the server + console as well as the console when you join the server. + + placeholder: | + attempt to index 'foo' (a nil value) + some_file.lua: 123 + + label: Errors + + - type: markdown + + attributes: + value: | + Please always check the startup log of the server for + errors, as those can cause more errors down the line. + + - type: textarea + id: extra + + attributes: + description: | + Any additional information that you can provide. + + label: Additional Info diff --git a/gamemodes/darkrp/.github/ISSUE_TEMPLATE/config.yml b/gamemodes/darkrp/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c3ba81d --- /dev/null +++ b/gamemodes/darkrp/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +blank_issues_enabled: true + +contact_links: + - about: Sadly, DarkRP is in maintenance only mode, meaning feature requests are generally not accepted. The best way to get the feature might be to create an addon. + name: 🆕 Feature request + url: https://darkrp.page.link/discord + - about: I need help with DarkRP + name: 🚨 Support + url: https://darkrp.page.link/discord diff --git a/gamemodes/darkrp/.github/scripts/check-modified-subtree.sh b/gamemodes/darkrp/.github/scripts/check-modified-subtree.sh new file mode 100644 index 0000000..8b2983f --- /dev/null +++ b/gamemodes/darkrp/.github/scripts/check-modified-subtree.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +# +# Prints a list of errors for any dependencies that have been edited. +# Dependencies should be edited in their respective repositories +# + +echo 'Checking for any dependency changes.' + +set -o errexit # Exit for any error +set -o nounset # Error when referencing unset vars + + +commit=$( + git rev-list \ + --first-parent origin/master \ + --max-count=1 +) + +files=$( + git diff \ + --name-only \ + "$commit" +) + + +# Template for the GitHub summary + +template=" + > [!WARNING] + > Files from **[{project}]({repository})** have been modified! + > These changes originally come from [{project}]({repository}), but are synchronized to DarkRP. + > These files should be edited in the original repositories instead. + > + {files} + > +" + +# Trim leading space for Markdown compatibility + +template="$( echo "$template" | sed 's/^[[:space:]]*//' )" + + +failed=false + +function check { + + local name="$1" + local path="^$2" + local repo="$3" + + + # Check if any matching files were modified + local matched=$( + echo "$files" | + grep --perl-regexp "$path" + ) + + + # Ignore if nothing matched + if [ -z "$matched" ] ; then + return + fi + + + # Append info to GitHub's summary + local info="$template" + + matched="$(printf -- '> `%s` \n' "$matched")" + + info=${info//\{repository\}/$repo} + info=${info//\{project\}/$name} + info=${info//\{files\}/$matched} + + echo "$info" >> "$GITHUB_STEP_SUMMARY" + + failed=true +} + + +check "Falco's Prop Protection" \ + 'gamemode/modules/fpp/pp/' \ + 'https://github.com/fptje/falcos-Prop-protection' + + +check "FN Library" \ + 'gamemode/libraries/fn.lua' \ + 'https://github.com/fptje/GModFunctional' + + +check "MySQLite Library" \ + 'gamemode/libraries/mysqlite/mysqlite.lua' \ + 'https://github.com/fptje/MySQLite' + + +check "Simplerr Library" \ + 'gamemode/libraries/simplerr.lua' \ + 'https://github.com/fptje/simplerr' + + +check "CAMI Library" \ + 'gamemode/libraries/sh_cami.lua' \ + 'https://github.com/glua/CAMI' + + +check "FSpectate" \ + 'gamemode/modules/fspectate/' \ + 'https://github.com/fptje/FSpectate' + + +if [[ "$failed" = true ]] ; then + echo 'Some dependencies have been modified!' + exit 1 +fi + + +echo 'No dependencies have been modified.' + +exit 0 diff --git a/gamemodes/darkrp/.github/workflows/check-modified-subtree.yml b/gamemodes/darkrp/.github/workflows/check-modified-subtree.yml new file mode 100644 index 0000000..90c7099 --- /dev/null +++ b/gamemodes/darkrp/.github/workflows/check-modified-subtree.yml @@ -0,0 +1,20 @@ +name: Check Modified Subtree + +on: + workflow_dispatch: + + pull_request: + branches: [master] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch complete history + clean: false + + - name: Check For Differences + run: ./.github/scripts/check-modified-subtree.sh diff --git a/gamemodes/darkrp/.github/workflows/run-glualint.yml b/gamemodes/darkrp/.github/workflows/run-glualint.yml new file mode 100644 index 0000000..dc6a634 --- /dev/null +++ b/gamemodes/darkrp/.github/workflows/run-glualint.yml @@ -0,0 +1,11 @@ +name: run-glualint +on: + push: + branches: + - master + pull_request: +jobs: + Lint: + uses: FPtje/GLuaFixer/.github/workflows/glualint.yml@master + with: + config: "glualint.json" diff --git a/gamemodes/darkrp/CONTRIBUTING.md b/gamemodes/darkrp/CONTRIBUTING.md new file mode 100644 index 0000000..dd8ee96 --- /dev/null +++ b/gamemodes/darkrp/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# THE GITHUB ISSUES PAGE IS NOT FOR GETTING HELP! +Remember that! We've got a Discord for that kind of stuff. It can be found here: +https://darkrp.page.link/discord + +# What to do when you have a problem in DarkRP + +There are three kinds of problems that can happen in DarkRP: +- The problem caused by the end user (think of a bad modification or a bad setting) +- The problem caused by a mod for DarkRP (think of a weapon pack, model pack or extra money printers or things like that) +- The problem for which the developer of DarkRP is responsible + +The very first step of solving your problem is figuring out who caused it. Often this is easy to figure out. If DarkRP started to error +when you edited your HUD, it's probably your fault (or the server host's). If the server starts in sandbox, or if you get the error + +``` +"couldn't include file <ANYTHING> (File not found)" +``` + +it's your fault. Did you try uploading the entire unzipped DarkRP folder to the server with FileZilla? That's a known problem, it's FileZilla's fault. Half of your files didn't upload properly. + +When a weapon from a weapon pack does crazy things, it's probably the person who made that weapon pack. +When the problem occurs with unedited DarkRP features, it might be DarkRP's fault. +There are cases for which it might be difficult to determine who is responsible for the problem. +In these cases you should look at the errors that usually show up. The errors usually say which mod caused the problem. + +If it's your fault, blame yourself. If you caused a problem you don't know how to solve, you have two options: +<ol> +<li>ask on a forum or ask your friends for help. If you contact mod developers, they might get mad at you for being asked something they have nothing to do with</li> +<li>undo the change that broke DarkRP. To do this, always make sure you have a backup</li> +</ol> + +If it's the fault of a third party mod developer, contact them to report the bug. They are the only ones who can (and are willing to) +solve the problems caused by their mod. + + +# Reporting a bug for DarkRP +Only report bugs for issues of which you are VERY SURE that it is the fault of DarkRP developers. + +To report a bug for DarkRP, you need to follow very strict rules. These rules exist so the bugs can be easily identified and solved. + +The most important rules are: +<ol> +<li>Do not ask for help. Your need of help is not the fault of DarkRP.</li> +<li>Do not report an issue when you are unable to install DarkRP.</li> +<li>Do not report problems that you caused yourself.</li> +<li>Do not report problems for other mods.</li> +<li>Do not report problems for a server that you do not own or develop for</li> +<li>Do not report a problem that has been reported before (you can search on the bug reporting site)</li> +<li>Do not repost your problem when your previous problem has been closed. You can post in a closed issue and you will still be listened to.</li> +<li>Never just post "It doesn't work" that's no information to work on.</li> +</ol> + +Failure to abide by these rules will get your report closed and/or your account banned from reporting issues. + +How to report a bug: +<ol> +<li> Enter lua_log_sv 1 in RCon or the server console</li> +<li> Make the problem happen</li> + if a weapon messes up when you shoot, shoot the weapon. + if it happens on server start, change level or restart the server + if it happens when the mayor tries to place a lawboard, make the mayor try to spawn a lawboard + etc.</li> +<li> Go to the FTP of your server.</li> +<li> In the garrysmod/ folder you should see "lua_errors_server.txt" and/or "clientside_errors.txt" + upload the contents of BOTH these files to www.pastebin.com + if you don't see those files, make sure you did everything right (lua_log_sv must be 1). + if you don't see the files and you're sure that you did the logging right, mention this in the bug report: + "No error log files were generated." + If you only see one file, upload that one file to www.pastebin.com and mention the following in the bug report: + "The other error log file was not generated." + Thanks. Errors help A LOT.</li> +<li> Go to https://github.com/FPtje/DarkRP/issues/new (DON'T SKIP THE PREVIOUS STEPS)</li> +<li> Think of an appropriate title. Try to be specific here</li> +<li> Take the issue template from "github issue template.txt", which is in the HELP folder, and copy paste it into the "Write" field.</li> +<li> Fill it in, try not to leave anything empty!</li> + MORE information = MUCH HIGHER chance that the problem will be solved +<li> Click "Submit new issue"</li> +</ol> diff --git a/gamemodes/darkrp/DON'T TOUCH ANY OF THESE FILES.txt b/gamemodes/darkrp/DON'T TOUCH ANY OF THESE FILES.txt new file mode 100644 index 0000000..13c8f6d --- /dev/null +++ b/gamemodes/darkrp/DON'T TOUCH ANY OF THESE FILES.txt @@ -0,0 +1,35 @@ +DO NOT EDIT ANY FILES OF THE DARKRP GAMEMODE. THAT IS SIMPLY NOT HOW YOU MODIFY DARKRP + +Do not modify ANY files, not in the modules, not in the config folder, not ANYWHERE in this folder. + +Here's what happens if you fuck with DarkRP files: + - You won't be able to update anymore. And yes you do care about updates because they have new features and bug fixes. + Problems often get solved in updates, that is if you didn't fuck it up yourself. + - ANY problem you have with DarkRP will NOT be any of the developer's responsibility. + YOU fucked with the DarkRP files, and now it's broken. Therefore it is your fault by default. + You were warned. All support is immediately dropped when you edit DarkRP files + - When GMod updates, shit will break and you won't know what the fuck to do. + You won't know how to fix it + and you can't update DarkRP to the working version because you fucked it up. + + +Wanting to edit DarkRP files doesn't make you batshit insane unless you think the following: + "I'll modify DarkRP, but when there's an update, I'll MANUALLY COPY THE CHANGED FILES OVER." + This is retarded. Actually proper retarded. This is the absolute dumbest thing you can possibly do. + What if you're reluctant to update for just a week and find out pretty much every file was changed? Yes, this happens. + You'd have to copy the changes over one by fucking one. Are you going to do that? + No you fucking won't. It will take fucking hours of tedious and unnecessary work. You'll say "Bollocks, fuck it". + And your DarkRP is never to be updated properly again. + + +================================== IMPORTANT ================================== + THERE IS ANOTHER WAY TO MODIFY DARKRP WITHOUT HAVING TO FUCK WITH THE DARKRP FOLDER + IT'S JUST AS EASY + AND YOU CAN STILL UPDATE DARKRP +================================== IMPORTANT ================================== +Of course you can modify DarkRP, I'd be daft if I forbade you. Just don't do it by editing DarkRP files. + +Get the DarkRP modification addon. +https://github.com/FPtje/DarkRPModification + +You will be surprised about how much you do with it. diff --git a/gamemodes/darkrp/HELP/Avoid editing core files.txt b/gamemodes/darkrp/HELP/Avoid editing core files.txt new file mode 100644 index 0000000..6ee0204 --- /dev/null +++ b/gamemodes/darkrp/HELP/Avoid editing core files.txt @@ -0,0 +1,35 @@ +Avoid editing DarkRP core files AT ALL COST! +DarkRP files are not meant for you to be edited, not even the config folder. + +Anything in DarkRP should be changeable without having to change core DarkRP files. +If this isn't possible, it should be MADE possible. + +Open an issue on GitHub if there is no +- DarkRP function that allows you to change DarkRP the way you want: + https://darkrp.miraheze.org/wiki/Category:Functions +- DarkRP Hook that allows you to change DarkRP the way you want: + https://darkrp.miraheze.org/wiki/Category:Hooks + +--[[--------------------------------------------------------------------------- + How to configure +---------------------------------------------------------------------------]] +Download https://github.com/FPtje/DarkRPModification as an addon and use the +config folder inside it to configure DarkRP. + +--[[--------------------------------------------------------------------------- + How to modify DarkRP without modifying the core files +---------------------------------------------------------------------------]] +1. Go to darkrpmodification/lua/darkrp_modules +2. Make a folder with any name, e.g. mydarkrpmod + it has to be lowercase + no spaces or weird characters +3. go into mydarkrpmod +4. if you're doing a serverside thing, make a Lua file that starts with sv_ + e.g. sv_init.lua + if you're doing a clientside thing, make a Lua file that starts with cl_ + e.g. cl_init.lua + if you're doing a shared thing, make a Lua file that starts with sh_ + e.g. sh_init.lua + + if you don't know what serverside/clientside/shared is, you should probably not be trying to modify DarkRP. +5. Use the DarkRP functions and hooks (and other functions/hooks) to make your thing diff --git a/gamemodes/darkrp/HELP/DarkRP wiki.txt b/gamemodes/darkrp/HELP/DarkRP wiki.txt new file mode 100644 index 0000000..0949e47 --- /dev/null +++ b/gamemodes/darkrp/HELP/DarkRP wiki.txt @@ -0,0 +1,4 @@ +The official DarkRP wiki can be found at +https://darkrp.miraheze.org/wiki/Main_Page + +You can find out how to make custom jobs, shipments and many other things there! diff --git a/gamemodes/darkrp/HELP/How to install.txt b/gamemodes/darkrp/HELP/How to install.txt new file mode 100644 index 0000000..7753a31 --- /dev/null +++ b/gamemodes/darkrp/HELP/How to install.txt @@ -0,0 +1,5 @@ +Put the darkrp folder in garrysmod/gamemodes + +IF YOU PUT DARKRP IN ADDONS IT WON'T WORK! + +REMOVE OLD DARKRP FOLDER BEFORE PUTTING THE NEW ONE IN \ No newline at end of file diff --git a/gamemodes/darkrp/HELP/Problem in DarkRP.txt b/gamemodes/darkrp/HELP/Problem in DarkRP.txt new file mode 100644 index 0000000..5e19ab1 --- /dev/null +++ b/gamemodes/darkrp/HELP/Problem in DarkRP.txt @@ -0,0 +1,70 @@ +--[[--------------------------------------------------------------------------- +What to do when you have a problem in DarkRP +---------------------------------------------------------------------------]] + +There are three kinds of problems that can happen in DarkRP: +- The problem caused by the end user (think of a bad modification or a bad setting) +- The problem caused by a mod for DarkRP (think of a weapon pack, model pack or extra money printers or things like that) +- The problem caused by the developer of DarkRP + +The very first step of solving your problem is figuring out who caused it. Often this is easy to figure out. If DarkRP started to error +when you edited your HUD, it's probably your fault (or the server host's). If the server starts in sandbox, or if you get the error +"couldn't include file darkrp\gamemode\cl_init.lua (File not found)" +it's your fault. + +When a weapon from a weapon pack does crazy things, it's probably the person who made that weapon pack. +When the problem occurs with unedited DarkRP features, it might be DarkRP's fault. +There are cases for which it might be difficult to determine who is responsible for the problem. +In these cases you should look at the errors that usually show up. The errors usually say which mod caused the problem. + +If it's your fault, blame yourself. If you caused a problem you don't know how to solve, you have two options: +1. ask on a forum or ask your friends for help. If you contact mod developers, + they might get mad at you for being asked something they have nothing to do with +2. undo the change that broke DarkRP. To do this, always make sure you have a backup + +If it's the fault of a third party mod developer, contact them to report the bug. They are the only ones who can (and are willing to) +solve the problems caused by their mod. + + +--[[--------------------------------------------------------------------------- +Reporting a bug for DarkRP +---------------------------------------------------------------------------]] +Only report bugs for issues of which you are VERY SURE that it is the fault of DarkRP developers. + +To report a bug for DarkRP, you need to follow very strict rules. These rules exist so the bugs can be easily identified and solved. + +The most important rules are: +1. Do not ask for help. Your need of help is not the fault of DarkRP. +2. Do not report an issue when you are unable to install DarkRP. +3. Do not report problems that you caused yourself. +4. Do not report problems for other mods. +5. Do not report problems for a server that you do not own or develop for +6. Do not report a problem that has been reported before (you can search on the bug reporting site) +7. Do not repost your problem when your previous problem has been closed. You can post in a closed issue and you will still be listened to. +8. Never just post "It doesn't work" that's no information to work on. + +Failure to abide by these rules will get your report closed and/or your account banned from reporting issues. + +How to report a bug: +1. Enter lua_log_sv 1 in RCon or the server console +2. Make the problem happen + if a weapon messes up when you shoot, shoot the weapon. + if it happens on server start, change level or restart the server + if it happens when the mayor tries to place a lawboard, make the mayor try to spawn a lawboard + etc. +3. Go to the FTP of your server. +4. In the garrysmod/ folder you should see "lua_errors_server.txt" and/or "clientside_errors.txt" + upload the contents of BOTH these files to www.pastebin.com + if you don't see those files, make sure you did everything right (lua_log_sv must be 1). + if you don't see the files and you're sure that you did the logging right, mention this in the bug report: + "No error log files were generated." + If you only see one file, upload that one file to www.pastebin.com and mention the following in the bug report: + "The other error log file was not generated." + + Thanks. Errors help A LOT. +5. Go to https://github.com/FPtje/DarkRP/issues/new (DON'T SKIP THE PREVIOUS STEPS) +6. Think of an appropriate title. Try to be specific here +7. Take the issue template from "github issue template.txt" and copy paste it into the "Write" field. +8. Fill it in, try not to leave anything empty! + MORE information = MUCH HIGHER chance that the problem will be solved +9. Click "Submit new issue" diff --git a/gamemodes/darkrp/HELP/changelog.txt b/gamemodes/darkrp/HELP/changelog.txt new file mode 100644 index 0000000..a1af742 --- /dev/null +++ b/gamemodes/darkrp/HELP/changelog.txt @@ -0,0 +1,1014 @@ +This list is missing content. For a full list see: +https://github.com/FPtje/DarkRP/commits/master (October 2012 and later) +and +http://code.google.com/p/darkrp/source/list (early-October 2012 and earlier) +for the latest changes + +DarkRP 2.4.3 +[ADD] Laws system! Use /placelaws to spawn a screen displaying the laws, /addlaw and /removelaw to edit them. rp_maxlawboards to set the limit. +[ADD] AFK NPC +[ADD] Signing of letters +[ADD] Proplympics! Prop surf games :D +[CHANGE] People need to be alive in order to be able to wanted/warrant +[CHANGE] Updated Notifies to work better with the newer ones +[CHANGE] Lots of function/network optimisation +[CHANGE] Remade HUD +... + + +DarkRP 2.4.2 +[CHANGE Drakehawke] Fixed the default GMod admin cleanup from unowning/resetting doors. +[ADD Drakehawke] Dynamic mic chat - if enabled (rp_voiceradius_dynamic), only players in the same room as you can hear your mic (rp_voiceradius must be 1). +[ADD Drakehawke] AFK module +[ADD] Extra animations! (use key swep!) +[ADD] Group ownable doors +[ADD] rp_startingmoney +[ADD] rp_copscanunweld +[ADD] rp_droppocketondeath toggle dropping items in pocket +[ADD] rp_allowvehicleowning Allow vehicles to be owned +[ADD] Support for multiple models. It still works the old way, don't worry +[ADD] Reason for warrant and wanted +[ADD] Rp_vote, vote with a keybind! bind a key to rp_vote 1 and rp_vote 0 to vote yes or no without clicking! +[ADD] Lockdown stops when mayor changes job or leaves server +[ADD] rp_DeathPOV: Enable/disable whether people see their death in first person view +[ADD] rp_respawninjail - Enable/disable whether people can respawn in jail when they die (default on) +[ADD] /911 and /report: Call 911 when you're being attacked and call /report when you see an illegal entity! +[ADD] rp_enablebuyhealth +[ADD] rp_npcarrest +[ADD] /broadcast for mayors +[ADD] Custom agenda's +[ADD] Police agenda +[ADD] Animations for several things! +[ADD] Possibility to give unownable door a title in the keys menu +[ADD] maximum to /price. Set maximum with rp_pricecap +[ADD] /unownalldoors, sell all your doors at once! +[ADD] rp_droppocketonarrest, drop pocket items on arrest. +[ADD] Showing whether someone has a gunlicense above their heads +[ADD] MySQL support! +[ADD] Support for 3D voice chat! +[ADD] rp_adminvehicles and rp_adminnpc, restrict vehicles and NPC's! +[ADD] rp_pricemin, set minimum price of entities. +[ADD] New fading door tool compatability (DOESN'T WORK WITH OLD DOOR TOOL (Conna's tool)) +[ADD] FAdmin admin mod +[ADD] Threaten with stunstick (reload) +[ADD] E2 antiminge! (one of the best updates ever!) +[FIX] // not working in letters +[FIX] Disabling RP names not working clientside +[FIX] Weapon checker resetting ammo +[FIX] Gunlicense buttons in F4 +[FIX] Shipment issues +[FIX] Pocket glitch +[FIX] drop pickup get ammo glitch +[FIX] Sitting incorrectly in vehicles +[FIX] Switchjob not working when player can't change job for some reason +[FIX] Modules not working when folder name was changed +[FIX] Jail positions not saving +[FIX] Compatability with extra seats in VUMod +[FIX] animations to work with latest update +[FIX] Staying on fire when respawning after money printer explosion +[FIX] Pressing E on a gun you can't pick up making it pick up on walk over it (hard to explain lol) +[FIX] Saving original steam names in the database +[FIX] Door issue: random other people co-owning it when one co-owner is added. +[FIX] Meteor emitter spam +[FIX] Escaping demote +[FIX] Lag when switching team +[CHANGE] Microphone GUI layout. It's way more RP'ish now! +[CHANGE] Credits, added Eusion +[CHANGE] The way Entities like money and money printers show their information. Thanks MavBear! +[CHANGE] Made a lot of functions local, this will give you better overall performance both on server and client +[CHANGE] Stunstick damage to prevent abuse. Left click: no damage, right click: 10 damage +[CHANGE] F1 help text +[CHANGE] Made it so you have to press E on dropped weapons to pick them up +[CHANGE] It doesn't remove vehicles anymore when you change job. +[CHANGE] Made lockpicking time random +[CHANGE] Weapon checker to take a while to confiscate weapons +[CHANGE] Made warrant possible on arrest +[CHANGE] Made it so group doors need to have at least one warranted owner for it to be opened with battering ram +[REMOVE] ALL NWVars in doors, players and custom entities (shipments, druglabs etc.) DarkRP now has LESS NWVars than sandbox! +[REMOVE] VoteCopOn. It's bullshit +[REMOVE] All global integers. Result: Less server crashes, less buffer overflow in net message, faster spawning time +[REMOVE] SendLua's to open keys and F4 menu. I don't even know why I had them there in the first place +[REMOVE] FindPlayerBySID, SID is the player's UserID, Player(SID) does exactly the same thing. +[REMOVE] Unused functions such as rp_paydaytime and FoodHeal +[REMOVE] Bannedprops/allowed props, it has been replaced by FPP. +[REMOVE] CustomTeams and Customshipments.txt, no one used them +[REMOVE] Traditional privilege system. It has been replaced by FAdmin. + +DarkRP 2.4.1 + +[ADD] rp_ironshoot, set if people have to see through the ironsights to shoot +[ADD] Falco's Prop protection (FPP), reporting for duty to replace Simple Prop Protection! +[ADD] Anti tool model crash(FPP feature) +[ADD] An easy way to make normal entities +[ADD] CPPI for FPP +[ADD] rp_dropmoneyondeath and rp_deathfee, drop money when you die. Off by default +[ADD] Demote option in F4 +[ADD] People now walk slow while in ironsight mode of weapon +[ADD] DarkRP skin for the VGUI's +[ADD] Language system, it's really easy to add languages to your DarkRP now! Thanks WoRmS for the danish translation, Donkie for Swedish! +[ADD] Signature to the letters/mails +[ADD] rp_startinghealth: Set the default health +[ADD] /addspawn and /removespawn: You can now add multiple team spawns and you can remove all the teamspawns from one team. +[ADD] Visible message when a lockdown is going on +[ADD] /me command +[ADD] rp_dropweaponondeath - Enable/disable whether people drop their current weapon when they die. +[FIX] Unable to remove door tool doors +[FIX] People with the admin privilege can now see the admin menu +[FIX] Hunger resetting after sleep +[FIX] Max microwaves +[FIX] rp_enforcemodels 0 not working correctly +[FIX] Unneccessary Database access on PlayerInitialSpawn(should save lag) +[FIX] Name exploit. +[FIX] People staying RP admin after having normal admin removed +[FIX] Errors when removing poopeemod +[FIX] Crash sometimes when changing team. +[FIX] Able to sprint after Arrest>sleep>wake up +[FIX] Tool restriction mods not working(Thanks Gorgorp!) +[FIX] Name exploit, names can now only contain letters and numbers. +[FIX] earthquake mod, it's been broken for a long time apparently +[FIX] Chat not appearing in HLSW +[FIX] Arrested timer resetting when other player was arrested +[FIX] Ooc and advert bug where you could say nothing in OOC or advert. +[FIX] Timers for database stuff +[FIX] CPOwnable doors ownable by normal players +[FIX] Unownable doors having an owner +[FIX] ASS antispam and prop protection +[FIX] Lottery saying noone entered even though someone has won +[FIX] Hints about jailtime and money printer saying 0 every time. +[FIX] ULX fuckups: Error and pocket jail +[FIX] Scoreboard kick and ban not working +[FIX] Gunlab exploit to get free guns +[FIX] Sleep and get unarrested bug +[FIX] Being able to set name to world prop +[FIX] Vehicle exploit where you can go through walls with pods +[FIX] Weapon ironsight bugs +[FIX] Sleep and get free ammo +[CHANGE] Default shipments are now in addshipments.lua +[CHANGE] Moved complicated code in shared.lua and addshipments.lua to MakeThings.lua +[CHANGE] Changed filename of addshipments.lua to addentities.lua +[CHANGE] People now can't use wanted as an ESP, you will only see someone is wanted if he is in your sight. This way it won't be abused to find players. +[CHANGE] People now drop everything in pocket when they die. +[CHANGE] Medic gun world model to package, so it doesn't look like he's about to blow you up +[CHANGE] Moneybags now have their own separate entity +[REMOVE] Simple Prop protection. You have served us well, goodbye. +[REMOVE] RunConsoleCommand override + +DarkRP 2.3.7 +Updated by: FPtje Falco + +[ADD] /demote <reason> People need to specify a reason to demote +[ADD] rp_needwantedforarrest to prevent cops from random arresting +[ADD] /channel and /radio command: Set your radio to a channel and talk through the radio so everyone with the same channel hears you. +[ADD] Hobo's can make zombie sounds by right clicking with the bugbait :P -- idea thanks to GoDNeSS +[ADD] Eating food makes the TF2 nom sound(bad luck if you don't have TF2) +[ADD] Poo and pee mod :D enable with rp_poopeemod 1 +[ADD] Anti slobbot server crash +[ADD] Weapon checker for the cops, chief and admin +[ADD] New, more subtle and easier to find easter egg. +[ADD] /wake and /wakeup command :) +[ADD] Gun license, people need a license to be able to pick up guns(toggle with rp_license 1/0) +[ADD] rp_allow<custom team> 1/0 +[ADD] Message when trying to spawn a banned prop +[ADD] Pocket swep, store things temporarilly! (rp_pocket to turn on/off) +[ADD] Sleeping players can get hurt +[ADD] rp_voiceradius: Only hear people's voice in your vicinity!(same distance as yell, 550 units) +[ADD] rp_resetallmoney: Reset EVERY SINGLE stored money on the server(not only of the people who are in the server)(Superadmin only) +[ADD] Players can now see how long before they get unarrested. +[ADD] Phone! You can call people for 1$ per 20 seconds! +[ADD] DarkRP map pattern(in create server/start singleplayer) THANKS \/\/()?|\/| +[ADD] rp_lookup command, look players up(nick, steam name, steam ID) +[ADD] Custom vehicle support! +[ADD] Logging system toggle on/off by doing rp_logging 1/0(superadmin only command) +[ADD] /job antispam, you can now only change job every 60 seconds +[ADD] Effects with money and health on the HUD +[ADD] rp_tellall, put a message in everyone's screen(admin only) +[ADD] Effect when trying to touch an entity you can't touch. +[ADD] Compatability with mods that use PlayerSay(Like the ASS grammar plugin!) +[ADD] Option to unown entities(for clients) +[ADD] Compatability with doormod +[ADD] /switchjob, switch jobs with someone else! Idea by GodNess +[FIX] Version number in the help screen +[FIX] Battering ram not working on warranted people +[FIX] Zombie spawns not saving, jailpositions not saving AND custom spawns not saving! +[FIX] People being able to spawn inside each other +[FIX] rp_max<custom team> erroring sometimes and breaking the whole fucking gamemode +[FIX] rp_grant breaking when the console enters it +[FIX] Atefood icon showing for everyone +[FIX] Prop protection toggle not working +[FIX] Normalsalary being stored twice. +[FIX] /teamban not working +[FIX] RP_max<customteam>s unable to be set to 0 +[FIX] Unable to drop custom shipment weapons +[FIX] Zombie move code(Thanks Enjia2000) +[FIX] Enhanced "players don't spawn in each other" code +[FIX] Zombies spawning in each other using the "players don't spawn in each other" code (a bit thanks to Enjia2000) +[FIX] Settings and globals not loading +[FIX] prop protection assigning all world props to the owner of the server(in case of a listen server) +[FIX] Medkit now heals to someone's max health(determined on spawn) +[FIX] Exploit: Be cop, go to sleep, change to citizen, keep arrest baton and other weapons. +[FIX] Exploit where you could duplicate food +[FIX] Zombiemod does not only spawn zombies anymore(FIX because it was there but not enabled... Idk why) +[FIX] Exploit where you could spawn banned props +[FIX] If rp_allowrpnames = 0 then people won't have rp names anymore. +[FIX] Health is not restored when you come back from sleep +[FIX] HUGE Speed fix!(something with help labels sending from server to client every time) +[FIX] F4 admin menu having to load a few seconds +[FIX] F4 admin menu being unstable +[FIX] F1 menu not showing part of the help. +[FIX] Player warrants resetting after they were already reset. +[FIX] rp_enablemayorsetsalary not working +[FIX] Can't change job when you're dead +[FIX] Shotgun shooting super precise when in ironsight mode +[FIX] Arrest/unarrest baton seeming to hit superfast clientside +[FIX] cl_defaultweapon, you can now spawn and select the weapon you want by doing cl_defaultweapon <weapon name> +[FIX] Ironsights, they were fucked up +[FIX] mp_falldamage not working +[FIX] Chat bug after gamemode_reload(developer's thing) +[FIX] Health showing below 0 +[FIX] SPP exploit where clients could retrieve steamID's with Lua.(Should only be possible with status in console) +[FIX] Bug in SPP menu that made the settings reset themselves(The settings now use the DarkRP settings system) +[FIX] Weapon predictions +[FIX] Meteor erroring etc. +[FIX] Message about someone being unwanted when he already wasn't wanted anymore. +[FIX] Spawning on non-custom spawn pos when custom spawn pos was enabled and guy was unarrested +[FIX] Question screen buttons having wrong position +[CHANGE] /buypistol to /buy because of the custom items +[CHANGE] rp_max<customteam> to rp_max<customteam>s +[CHANGE] Made admins.lua a bit more idiot proof +[CHANGE] Cooks can now buy food too when hungermod is off +[CHANGE] Letters get removed after 10 minutes, long enoug and prevents them for lying there for like hours. +[CHANGE] Custom shipments without a team entered will make it so that every team can spawn the shipment +[CHANGE] Moved changelog part in init.lua to the changelog.txt file +[CHANGE] The way of saving settings and globals, it now doesn't use text files but SQL +[CHANGE] Admins can touch world props to players can touch world props so players can pick up world items. +[CHANGE] Chiefs can now change to cops by doing /cp +[CHANGE] When there's only one option in the keys menu, it will do that option instantly without opening the menu +[CHANGE] Made all yell,whisper,pm ETC. in colour!!! +[CHANGE] Helplabel structure(now MUCH faster!) +[CHANGE] The chat to use the new system +[CHANGE] Removed /zombiehelp and put it in the F1 menu. +[CHANGE] the way how gravgun punting is done +[CHANGE] You can't shoot unless you see through the sight post (ANTI DM!) +[CHANGE] Weapon animations, they are now standard holstered, unless you see through the sight post +[CHANGE] the way chat colours work, it now works completely different! +[CHANGE] The way lockpick works. +[CHANGE] All standard teams are now made the same way as custom teams +[REMOVE] The easter egg :( +[REMOVE] the player possessor +[REMOVE] refesh_int concommand, it's been replaced by rp_resetallsettings +[REMOVE] Restriction of /dropweapon. You can now drop any weapon. +[REMOVE] CSE base and other unused swep bases +[REMOVE] The second stunstick swep(There were 2 the same :S) +[REMOVE] Chat prefix, broken and noone ever used it. +[REMOVE] cl_deathnotice.lua, it was a complete file just for ONE thing! +[REMOVE] rp_currencysymbol, Didn't work and noone used it +[REMOVE] The standard look-at-a-player Name and health thing, it's replaced by the RP one +[REMOVE] /rm, Exploit-sensitive and never used +[REMOVE] Apply settings button in SPP menu, it now applies instantly. +[REMOVE] Melon entity, it wasn't doing anything :S +[REMOVE] Several networked variables that were used only serverside +[REMOVE] The weight of shipments, noone ever cared. + +DarkRP 2.3.5: +Updated by: FPtje Falco + +[ADD] Cops can arrest sleeping people +[ADD] Admins can disable the No-respawning when changing job with rp_norespawn +[ADD] Admins can allow/disallow hobo's by doing rp_allowhobos <1/0> +[ADD] Every door and vehicle controls are put in the reload function of the keys SWEP +[ADD] Re-added the earthquake sound +[ADD] DarkRP now automaticly saves the DarkRP settings(not the costs) in a text file every minute and will get restored when the server starts(like after a crash) +[ADD] Cops chief and mayor only option in the Key menu for superadmins +[ADD] A very easy way to add classes! see shared.lua! +[ADD] rp_hungerspeed command: it's the speed of hunger divided by 10( standard = 7) +[ADD] Admins can choose to make a vote or not to make a vote to become cop/mayor +[ADD] The ability to make custom shipments +[ADD] Console command to remove class specified entities when someone changes class.(Like microwaves get removed when a cook changes to like the hobo): rp_removeclassitems +[ADD] HUD adaptation menu. +[ADD] Timer to the vote screen(You see how much time you have left to vote) +[ADD] DarkRP now saves the costs of guns and other global ints +[ADD] Ability to knock on doors you don't own(left/right mouse with the keys weapon) +[ADD] Some citizen models(Now not every citizen has the same model) +[ADD] Teamban and teamunban (chat = /teamban, console = rp_teamban): Ban/unban a player from a certain team. Use: /teamban <player> <team> or rp_teamban <player> <team> +[ADD] Lottery: The mayor can start a lottery and players can enter the lottery with $50. At the end one of the players wins all the money. Toggle with rp_lottery 1/0 +[ADD] /advert command: Say /advert <advertisement> to advert! +[ADD] Rp_BabyGod: Players are immortal when they've just spawned. +[ADD] Rp_<job> for every job, not only cp, mayor and cpchief +[ADD] Color codes when rp_alltalk = 0: People can see who can hear them whisper/talk normally/yell and who cannot hear them. +[ADD] Expanded /buyfood, Now you can do more than /buyfood melon. It's also in the F4 menu! +[CHANGE] All door controls are now done though reload with the Keys weapon. +[CHANGE] Descriptions in the change job VGUI(thanks james hutchins) +[CHANGE] The way prop protection works for admins +[CHANGE] Moved the job change to F2 and I added a lot of commands/entities to it +[CHANGE] The way Simple prop protection protects against slider bug: you can make the slider but you won't be able to do anything with the prop you made the slider on. +[CHANGE] Lockpick being able to hit other things than doors +[CHANGE] HasPriv() speed by using a cache: THANKS PHILXYZ +[CHANGE] SpreadFire code: THANKS PHILXYZ +[CHANGE] The way movement is done +[CHANGE] The druglab completely! I hope you like it! +[CHANGE] Put the warrant back. +[CHANGE] Scoreboard now sorts the players on their class +[CHANGE] Replaced bad rating with "friendly" rating +[CHANGE] You can see what you rated someone, and the rated guy can see who rated him. +[CHANGE] Only super admins can set peoples money(Due to complaints of admins setting random people's money) +[CHANGE] Pressing F4 when the F4 menu is already open, closes it. +[CHANGE] Lock pick appearance(I also fixed the error with ActIndex("Crowbar") isn't set) +[CHANGE] Merged Simple RP Prop protection with DarkRP +[CHANGE] Put the contents folder in DarkRP with the earthquake sound +[CHANGE] Settings now save when a convar is changed instead of every minute. +[FIX] When use protection is on, doors couldn't be opened by some people, now they can. +[FIX] Rare error with dropweapon +[FIX] Exploit with shipments: Exploding them with more than one explosive would make them spawn more guns than usual. +[FIX] Exploit with wire nailer +[FIX] Exploit that you can duplicate weapons(I made sure that you can't use toolgun on weapons AND I made sure you can't spawn saved weapons works on both normal dup and adv. dup) +[FIX] Bug that even if hungermod was off, food would still cost money. +[FIX] Gravgun punt punting far +[FIX] Not being able to use druglabs/money printers/whatever +[FIX] Vote screens being able to be there forever +[FIX] The save system not saving everything +[FIX] When looking at an item/door, the text won't be shaky anymore! +[FIX] Attempt to fix playerinitialspawn lag +[FIX] Attempt to fix walk speed lag +[FIX] /dropmoney text exploit +[FIX] The vote system was fucked, I completely renewed it(I now use Derma) and HOPEFULLY every shit problem you guys had with it is fixed! +[FIX] Prices etc. resetting to 0 +[FIX] HUGE FUCKING MAJOR EXPLOIT where you enter a console command after doing /votecop or /votemayor and just become a cop without anyone voting yes. +[FIX] The ability of people to somehow get <nothing> as RP name(Always happened with bots too) +[FIX] People being able to get less than 0 money. DarkRP just doesn't work with depts +[FIX] rp_grant and rp_revoke not working properly, Philxyz fucked the caching system up(Phil look at my code to fix it for your release :D) +[FIX] Demote: the demoter can't vote anymore now. +[FIX] Unable to select keys when you got a different weapon(like the 357) +[REMOVE] Data/DarkRP/WHAT_YOU_CAN_DO_IN_THIS_FOLDER.txt and its contents, it had absolutely no purpose. If you have that file, the update won't remove it but it won't remake it either. +[REMOVE] rp_mute <player> <on/off> It didn't work AND you've got admin mods to mute people! + + +DarkRP 2.3.1 +Updated by: FPtje Falco + +[ADD] Awesome change job screen, Press F4 while not looking at a door +[ADD] Player possessor swep for the admins +[ADD] Sleep sound +[ADD] New team! Hobo +[ADD] Cops can warrant through TAB scoreboard :D +[ADD] The battering ram can unfreeze props of warranted players! +[FIX] People being able to duplicate DarkRP entities/Remove other people's entities through a slider/winch/muscle/etc. +[FIX] Having to respawn when you change your job, Now you can change your job without respawning +[FIX] When you go to sleep and back you now get all your weapons back +[FIX] Medic's can't "heal" when someone's health is or is bigger than 100 +[FIX] When you're arrested and join more than 120 seconds later you won't get re-arrested. but if you rejoin in less than those 120 seconds you will get rearrested +[FIX] People being able to disable motionblur/dof/the black screen by doing pp_colormod 0/pp_dof 0/pp_motionblur 0 +[FIX] When you do a vote when alone then it doesn't vote, it just accepts +[FIX] You can't vote when you made the vote yourself +[FIX] The moneyhack with the gunlabs and microwaves :D +[Change] Medic gun can now heal self +[Change] The way the medic kit heals, it doesn't do it instantly anymore +[Change] The chief has a different model now +[Change] If an admin/whatever does votemayor he skips the voting + +DarkRP 2.2.20 +Updated by: Chrome Bolt(Unib5) + +-- Self Ideas/Fixes + +[Fix] /r was showing on the help list +[Fix] Clarified some of the help screens - added their alternate commands. +[Added] Added some more banned props, including the money printer model. +[Added] Added rp_respawntime, default to 4. +[Added] Players are now wanted by the Police set amount of time - controlled with rp_wantedtime. +[Added] rp_deathblack - Players vision is black when dead. +[Added] Added myself to credits list :-) +[Change] Cash no longer displays a message when it is burned up. + +DarkRP 2.2.19 +Nov 08, 2008 +Updated by: philxyz + +-- Old +[Fix] /addowner and /removeowner were blocked +[Fix] Phantom "Owned By:" labels on doors + + +DarkRP 2.2.18 +Nov 06, 2008 +Updated by: philxyz + +-- New +[Changed] Add rp_maxletters command to replace the fixed limit of 4 +[Changed] Add rp_customjobs command (default 1) to enable toggling of /job command + +-- Old +[Fix] Missing return "" in /job lower bound string check +[Fix] You could set your RP name to Shared or World (dangerous - conflicts with prop protection) +[Fix] Prop protection not updating for a user's props when changing RP name +[Fix] Per-player letter limits were broken +[Fix] Increase the maximum amount of allowed text in a letter +[Fix] Remove some old debugging messages + + +DarkRP 2.2.17 +Oct 31, 2008 +Updated by: philxyz + +-- New +[Changed] Non-ownable doors can now have a title (/title from a super admin when door is disabled) +[Changed] Add a minimum number of characters (3) to the /job command +[Changed] When your RP name is the same as your Steam name (the name set in Steam, not your steam username) don't print both names in the chat box + +-- Old +[Fix] Client-side Lua spam when looking at a non-ownable door (thanks Scooby) +[Fix] The use of player commands on a door which has been disabled +[Fix] The ability to own or unown a door while arrested +[Fix] Baton animation (a bit better now) +[Fix] Fix a lot of Notify messages that had inconsistent use of upper and lower case letters mid-sentence +[Fix] Simplify and clean up the code for all batons +[Fix] Make the rp name chat text slightly shorter in length +[Fix] "OOC" was an allowable RP name +[Fix] Chief and CPs not spawning with guns regardless of rp_noguns setting +[Fix] Change some spaces back to tabs +[Removed] Remove the final references to budgets from data.lua and main.lua +[Removed] Remove the obsolete player.NotifyArrest function (we already do it) + + +DarkRP 2.2.16 +Oct 26, 2008 +Updated by: philxyz + +-- New +[Changed by philxyz] Display custom jobs in the scoreboard +[Changed by philxyz] Add support for multiple jail positions per map +[Changed by philxyz] Add all jail positions for the most popular rp maps at time of release to static_data.lua for auto-loading on map load +[Changed by philxyz] Jail time on punishment is now the full jail time from the previous session to avoid buggy corner cases +[Changed by philxyz] Add /rpname command to allow a player to choose a custom RP name per server +[Changed by philxyz] Add a rp_allowrpnames command to control the above +[Changed by philxyz] Add "find by rp name" support (built-in behind all find commands) +[Changed by philxyz] Super admins can now make a door non-ownable by pressing F2 on it. The setting remains during a server restart until F2 is pressed on that door again or the database is cleared +[Changed by philxyz] Add rp_noguns command to allow admins to disable gundealers and spawning with pistols. NOTE: Not designed to prevent map-based gun factories +[Changed by philxyz] Add rp_chiefjailpos (default: 1) to determine whether or not the Chief can use /jailpos and /addjailpos +[Changed by philxyz] Change rp_earthquake_chance_is_1_in to rp_quakechance_1_in + +-- Old +[Fix by philxyz] Jail punishment timer +[Fix by philxyz] Mayor salary setting commands no longer permit negative salaries +[Fix by philxyz] Impossible to set the moneyprinter cost +[Fix by philxyz] Another player_row.lua runtime error caused by viewing the scoreboard while another player is disconnecting +[Fix by philxyz] Ability to see who killed who during rp_showdeaths 0 by sneaking a look in the console +[Fix by philxyz] rp_dm_autokick always enabled regardless of setting +[Fix by philxyz] Shared items no longer responding to the "Use" key after a player has been killed while holding that item with the gravity gun +[Fix by philxyz] Negative salary setting was allowed +[Fix by philxyz] Useless brackets all over the place +[Fix by philxyz] Privilege cleanup +[Fix by philxyz] Money Printer hint index +[Fix by philxyz] hints.lua in general - it was a mess +[Fix by philxyz] Players walking too quickly for it to be realistic +[Fix by philxyz] Make most calls to the Notify() function last at least 4 seconds +[Fix by philxyz] Limit the number of mayors to 1 and remove the maxmayors setting (this is because only 1 mayor can answer warrant requests, etc) +[Fix by philxyz] Gangsters not seeing the agenda unless the Mob Boss sets it while they are a Gangster +[Fix by philxyz] Custom player spawn positions (table loading design fault) +[Fix by philxyz] /job command should intercept job names that are similar to or exactly the names in the main F2 job menu and change the player's team +[Fix by philxyz] Lots of little SQL problems +[Removed by philxyz] Pointless file in Hunger Mod +[Removed by philxyz] economy.lua (only had one function left) +[Removed by philxyz] The ability to set budgets - broken and unused +[Removed by philxyz] The ability to set bails - broken and unused (and should be RP'd anyway) +[Removed by philxyz] Combine radio command (/r) - Just use /g (group chat) + + +DarkRP 2.2.15 +Oct 15, 2008 +Updated by: philxyz + +--New +[Changed by philxyz] Objectify some messy procedural code (reduce the size of /buypistol, /buyshipment, /buyammo and spawned_shipment sent) +[Changed by philxyz] Ensure that prices are always shown in the Notify() message after buying something (add it for drug labs, shipments and ammo) +[Changed by philxyz] Widen the scoreboard. I'm sure nobody plays in 640x480 any more... + +--Old +[Fix by philxyz] Always resetting a player's wallet to 500 on initial spawn (thanks to Sc00by22 and supe7nova for informing me of that) +[Fix by philxyz] Flickering text when inside a vehicle (credit goes to David Blaine on facepunch for the idea!) +[Fix by philxyz] Getting stuck in a vehicle when changing your job (found by Chrome Bolt) and being demoted (found by David Blaine) +[Fix by philxyz] Update the rest of the places where Sibre's name should appear +[Fix by philxyz] Lua errors in player_row.lua if a player disconnects while the player list is being viewed + + +DarkRP 2.2.14 +Oct 13, 2008 +Updated by: philxyz + +--News: There were still some bugs, thanks to Unib5 and Sc00by22 for informing me. + +--New +[Changed by philxyz] Clean up the salary code, it was messy and broken +[Changed by philxyz] Change one or two sentences to improve readability + +--Old +[Fix by philxyz] No pay received until you changed job for the first time +[Fix by philxyz] Clean up unemployment and bum / hobo mode +[Fix by philxyz] Delete a stray hidden file created by my file browser that shipped with darkrp accidently +[Fix by philxyz] Remove Money Printers on player disconnect to cut inflation + + +DarkRP 2.2.13 +Oct 9, 2008 +Updated by: philxyz + +--News: OK, I lied. THIS is my last release :-) + +--New +[Changed by philxyz] Include a money printer based on the RRPX version +[Changed by philxyz] Include spreadable fire from SeriousRP (for the money printer) +[Changed by philxyz] Can now demote a dead or jailed player (comes into force on next player spawn) + +--Old +[Fix by philxyz] Players getting hold of admin toolgun on spawn +[Fix by philxyz] Players escaping demotion by suiciding at the right moment +[Fix by philxyz] Non-Gun Dealers buying shipments when there are no other Gun Dealers +[Fix by philxyz] That annoying, seizure-inducing white flash +[Fix by philxyz] Change credits to reflect the person who truly fixed the /job exploit (Sibre) rather than Zorblet who actually re-hacked Sibre's version of DarkRP 2008 to introduce his own exploit (as removed in v2.22E, see changelog below). + + +DarkRP 2.2.12 +Aug 3, 2008 +Updated by: philxyz + +--News: Ricky has returned and will be continuing development, this is my last release for now. Something new is coming from both of us independently... watch this space! + +--New +[Changed by philxyz] No more escaping jail by changing job +[Changed by philxyz] Player loadout should occur on inital spawn +[Changed by philxyz] Blocked some more props by default +[Changed by philxyz] Switch to UNIX newlines for lua files (\n instead of \r\n) + +--Old +[Fix by philxyz] Lua errors when killed by a prop and rp_showdeaths = 0 +[Fix by philxyz] Some more awful, awful spelling +[Fix by philxyz] Non-admin wire to Shipment Crates +[Fix by philxyz] /setspawn was broken +[Fix by philxyz] Console message repetition +[Fix by philxyz] Nobody should have physguns at spawn unless admin enables it (a +s it always used to be) +[Removed] Loading of rp_* commands from data/DarkRP/servercfg.txt - it never worked, even once I'd fixed it. Stick to your custom scripts for now +[Removed] Wire Use input on shipment crates. They were admin only and it would have taken too much time to fix compared with the number of people that use the feature + + +DarkRP 2.2.11 +Aug 1, 2008 +Updated by: philxyz + +-- New +[Changed by philxyz] Prevent No-Collide tool on vehicles (rp_enforcevehiclenocollide 0 to disable) +[Changed by philxyz] Revert to allowing /buypistol for all players (rp_restrictbuypistol 1 to make /buypistol gundealer only if there are any) +[Changed by philxyz] Gun Dealers and Cooks now receive discounts for buying stock (12% pistol discount for gundealers, 18% food discount for cooks) +[Changed by philxyz] Gun Labs and Microwaves now display the price for the customer +[Changed by philxyz] Gun Labs and Microwaves have a new profit/loss based buy system +[Changed by philxyz] Add arrest notifications + +-- Old +[Fix by philxyz] Prevent changing material of restricted items. (As of this release, a bug in Garrys Mod means that this fix will not yet work) +[Fix by philxyz] Lua errors in cl_deathnotice.lua +[Fix by philxyz] Some more spelling and grammar fixes +[Fix by philxyz] Remove Zorblet from contributor list, all he did was include a backdoor (which was fixed ages ago) +[Fix by philxyz] RP Admins can no longer kick or ban other RP admins or server admins +[Fix by philxyz] Add /unlockdown to Mayor help menu + + +DarkRP 2.2.10 +July 20, 2008 +Updated by: philxyz + +-- New +[Changed by philxyz] Move DarkRP to SQLite by default +[Changed by philxyz] Upgrade the bundled "Simple RP Prop Protection" to version 1.4 +[Changed by philxyz] Add /removeletters command to remove your own letters +[Changed by philxyz] Add rp_removeletters [Name] command to enable admin removal of all or a specific player's letters +[Changed by philxyz] Add /rm command to allow a player to delete their own letters, gunlabs and druglabs (aim first) +[Changed by philxyz] Rework the prices to ensure rifles are more expensive than pistols +[Changed by philxyz] You can now set your own currency symbol in shared.lua +[Changed by philxyz] Add Wire "Use" input to gun shipment crates +[Changed by philxyz] Re-code the jailpos system to work with the new SQL backend cleanly +[Changed by philxyz] Add a default jailpos for RP_Downtown_V2 (set on map load) +[Changed by philxyz] Add rp_npckillpay command (amount of money paid out when killing an NPC) +[Changed by philxyz] Add the ability to register NPC prop kills to the player who created the prop +[Changed by philxyz] Completely rewrite the team change code in response to the 0 salary bug, preventing future similar problems +[Changed by philxyz] Block by default the large blue dumpster that mingebags use to trap other players +[Changed by philxyz] Add Earthquake magnitude reports (Notify) backported from my new gamemode (in development) +[Changed by philxyz] Add a group chat command (/g) that allows (police - chief - mayor) and (mobboss - gang) to talk together privately +[Changed by philxyz] If there are gundealers, you must buy pistols from them (/buypistol is disabled) +[Changed by philxyz] Remove rp_add temporary admin maker command (see below) +[Changed by philxyz] Recode the admin system so that you no longer have to restart DarkRP to add privileges for players (rp_grant and rp_revoke) +[Changed by philxyz] Merge rplol.lua and rprofl.lua into main.lua and merge commands.lua into admincc.lua +[Changed by philxyz] Make all "&&", "||" and "!" into "and", "or" and "not" (pure lua) +[Changed by philxyz] Arresting an NPC will now send it to the jailpos. Fun when it's a zombie and there are already prisoners in there! +[Changed by philxyz] Prevent the use of toolgun on drug labs. Suggested by Chuteuk +[Changed by philxyz] Prevent duplication of items that should never be duplicated +[Changed by philxyz] Only Admin or SuperAdmin can alter rp_chatprefix, rp_setmoney, rp_paydaytime or rp_setsalary (not DarkRP Admin) +[Changed by philxyz] Make it possible for ANY player to buy a drug lab. Corruption does not discriminate :P + +-- Old +[Fix by philxyz] Right click kill mode of stunstick +[Fix by philxyz] Properly remove gun labs, letters and microwaves belonging to a player when they disconnect +[Fix by philxyz] Prevent all manner of items from being spawned by a player under arrest +[Fix by philxyz] Prevent /mayor and /cp during a vote (prevents players exceeding the team quota) +[Fix by philxyz] Make the glock slightly less accurate +[Fix by philxyz] Make the sniper less accurate at close range +[Fix by philxyz] Demoted players no longer receive 0 salary +[Fix by philxyz] Remove Chief from Citizen property tax rate +[Fix by philxyz] Allow custom chief spawn positions +[Fix by philxyz] Mayor is now able to do /votecop +[Fix by philxyz] Chief was not receiving /r chat text +[Fix by philxyz] Fix a boolean logic error in the combine request (/cr) code +[Fix by philxyz] A whole bunch of tiny money related bugs +[Fix by philxyz] Make the gunlab interaction the same as the microwave interaction +[Fix by philxyz] Medics can still use /buyhealth themselves +[Fix by philxyz] All players now pay for props if rp_proppaying is enabled, not just the admins +[Fix by philxyz] Vehicle npc kills register as a kill by the driver +[Fix by philxyz] Some awful zombie related grammar issues +[Fix by philxyz] Player lookup by user ID (status in console) +[Removed] rp_rcon command. DarkRP admins are NOT meant to be super admins + + +DarkRP 2.2.9 +July 3, 2008 +Updated by: philxyz + +-- New +[Changed by philxyz] Add /warrant command for search warrants +[Changed by philxyz] Add rp_searchtime (default 30) for search warrant expiry +[Changed by philxyz] Add /wanted and /unwanted commands for CPs or the Mayor to highlight a wanted criminal (replaces playerwarrant) +[Changed by philxyz] Add /ao and /ro as aliases for /addowner and /removeowner respectively +[Changed by philxyz] Add more commands to the F2 menu + +-- Old +[Removed] /playerwarrant - It became a bizarre cross between arrest warrant (unnecessary) and search warrant + + +DarkRP 2.2.8 +June 30, 2008 +Updated by: philxyz + +-- New +[Changed by philxyz] Log Notify() output to server and client consoles (useful for /give proof of purchase) + +-- Old +[Fix by philxyz] Players could never re-take a job after demotion +[Fix by philxyz] Remove duplicate function definitions from util.lua + + +DarkRP 2.2.7 +May 08, 2008 +Updated by: philxyz + +-- New +[Changed by philxyz] A more successful attempt at fixing Simple Prop Protection compatibility - thanks to Spacetech for both his original work and his co-operation +[Changed by philxyz] Implement rp_maxdrugs and rp_maxfoods +[Changed by philxyz] Show the price when buying a melon +[Changed by philxyz] Remove letters on player disconnect + +-- Old +[Fix by philxyz] When unowning doors, don't show cents in the sale value +[Fix by philxyz] Fix an old typo that meant rp_gunlabcost was not working +[Fix by philxyz] Fix rp_microwavefoodcost + + +DarkRP 2.2.6 +May 05, 2008 +Updated by: philxyz + +-- New +[Changed by philxyz] Added /dropweapon, /weapondrop as aliases for /drop +[Changed by philxyz] /moneydrop is now /dropmoney (with an alias /moneydrop so as not to confuse people) +[Changed by philxyz] Allow help menu closing using /x in chat - it's much quicker + +-- Old +[Fix by philxyz] Remove the ability to screw up your money +[Fix by philxyz] CPs can smash open their own or unowned doors (important for rp_tb_city45 nexus) +[Fix by philxyz] Money should be owned by "World" according to Simple Prop Protection + + +DarkRP 2.2.5 +May 04, 2008 +Updated by: philxyz with contributions from [GNC] Matt + +-- New +[Changed by philxyz] Player has died in jail is now printed in the middle of the screen +[Changed by philxyz] Replace IsValid() with calls to G.ValidEntity() +[Changed by philxyz] Use the Unix find and sed commands to clean out the syntax even more (spacing, semicolons, if spacing etc) + +-- Old +[Changed by philxyz] Remove all traces of old bug reporting tools +[Fix by philxyz] Actually fix the druglab removal on player disconnect process +[Fix by philxyz] Remove spawned microwaves and gun labs on player disconnect +[Fix by philxyz] Fix the rest of the help screens not disappearing on job change +[Fix by [GNC] Matt] Simplify chat.lua +[Fix by [GNC] Matt] Fix a money related typo + + +DarkRP 2.2.4 +May 03, 2008 +Updated by: philxyz with contributions from [GNC] Matt + +-- New +[Changed by philxyz] Arrested players are now on suicide watch! (no kill in console) +[Changed by philxyz] Arrested players when killed, will not respawn until their time in jail is served +[Changed by philxyz] CPs can only smash in doors with a warrant set on one of the door's owners (rp_doorwarrants 1 default) +[Changed by philxyz] If there is a Mayor, he must approve /playerwarrant requests by CPs +[Changed by philxyz] When someone gets demoted, they can not rejoin that job until the timer is up (rp_demotetime 120) 2 mins by default +[Changed by philxyz] FindPlayer() is now case insensitive (suggestion by [GNC] Matt, something I missed) +[Changed by philxyz] Lockpick now has a random chance of success and can open vehicles +[Changed by philxyz] If a gangster or the mob boss steals a battering ram, they can use it as they wish +[Changed by philxyz] Shipments now come in crates +[Changed by [GNC] Matt] Police battering ram can now be used to force people out of vehicles + +--Old +[Fix by philxyz] Fix a bunch of Lua errors +[Fix by philxyz] Fix the baton view model errors +[Fix by philxyz] Fixed the last wrongly labelled "Max Gundealers Reached" Notify() +[Fix by philxyz] Backport /give and /moneydrop from my new unreleased gamemode. Only whole amounts are allowed and minimum give amount is 2 dollars +[Fix by philxyz] Disable mayor help on mayor death +[Fix by [GNC] Matt] Police Chief spawn position fix +[Fix by [GNC] Matt] Hit and run now registers as a kill by the driver +[Fix by [GNC] Matt] Keys work on vehicles at a greater distance + + +DarkRP 2.2.3 +April 27, 2008 +Updated by: philxyz + +--New +[Changed] Backported from my new upcoming gamemode: + - util.lua, enabling player lookups by name for in-game commands such as "/playerwarrant" + - chat.lua, allowing chat commands that start with the same character sequence as another command without causing a conflict + - F1 menu scroll direction fixed + - auto-enable pricing on server start (zero prices bug) + - /queryvar command to allow clients to query server CfgVar values + - anti-deathmatch feature rp_dm_autokick, rp_dm_maxkills and rp_dm_gracetime + +[Changed] Code cleanup: Remove useless semicolons and replace my indentation spaces with single tabs throughout +[Changed] Merge the sv_earthquake commands into the rp_ namespace +[Changed] Chief can now set the jailpos as well as admin +[Changed] Simplify the version number + +--Old +[Fix] Simple Prop protection was preventing people from picking up items from other players +[Fix] Only Gangs can spawn druglabs (the "DarkRP 2.1" way) + + +DarkRP 2.22 E +February 4, 2008 +Updated by: philxyz + +--Old +[Remove] What appears to be a very dirty cheat embedded into the game mode by some crafty person + + +DarkRP 2.21 E +February 3, 2008 +Updated by: philxyz + +--New +[Changed] Clean the code throughout the entire addon +[Changed] Reorder the CfgVars to make it easier to see which are bool and which are configurable +[Changed] Merge the old Checklist.txt into this changelog.txt file and delete Checklist.txt +[Add] Earthquake! sv_earthquakes / sv_earthquake_chance_is_1_in + +--Old +[Fix] Some grammar in hints.lua and other files so it reads much better +[Fix] Medics can now only heal other players, not just any entity +[Fix] Max Cooks Reached and Max Police Chiefs Reached labels were incorrectly set to Max Gundealers Reached +[Fix] Zombie Movement +[Removed] Zero byte modules/Hungermod/entity.lua + + +DarkRP 2.2 +January 26, 2008 +Updated by: Zorblet + +--New +[Fix] Critical exploit which allowed command execution on all connected clients + +--Old +[Add] rp_doorcost <amount> - The Cost to buy a door. +[Add] rp_zombiescore <amount> - The money awarded for a NPC Kill. +[Add] Player's now spawn with Cameras +[Add] Grenades to BuyShipment +[Add] Flashbangs to Buyshipment +[Add] /sleep for 10 seconds then /sleep again to unsleep +[Add] CfgVar "physgun" +[Add] CfgVar "adminjailpos" +[Add] rp_rtalk - To set Normal Talk Radius +[Add] rp_ryell - To Set Yell Radius +[Add] rp_rwhisper - To set Whisper Radius +[Add] A "Law" Group to be used when Cfgvar "adminjailpos" = 0 making Cp's and Ow's(mayor) able to set Jailpos, 1 making admin only +[Add] Hints telling the correct jailtime, not always 120 seconds. + +[Changed] Ow to Mayor - Makes more sense +[Changed] Dist between Cop and criminal needed for arrest/unarrest from 94 to 110 (should make arresting easier now) + +[Removed] Para - Created ridiculous amount of warefare. +[Removed] TMP - To make room in F2 for Flashbangs. +[Removed] Melee Damage of Lockpick (Created Crowbar Deathmatch) +[Removed] All Players spawn with NO WEAPONS, only job based ones (CPs get a Glock/USP) reason: Freeguns=bad economy and Deathmatchers as soon as people become Gangsters. + +[Fix] Maxdruglabs not working, There was no limit. +[Fix] /drop money not working, new command /moneydrop +[Fix] Many Typos and spelling errors in help menus and functions +[Fix] /agenda showing in chat, aswell as all the others that also do +[Fix] Drugs turn to food when Hungermod enabled +[Fix] Buying ammo not subtracting money for sale. +[Fix] "Bill HAMPTONhas a Warrant for their Arrest!" typo, added space. +[Fix] Hint Typos +[Fix] Description/Help in admins.lua +[Fix] Unneeded "Tip #1: " prefixes (they took up space in chat and looked horrible.) +[QuickFix] For no damage zombies: Made Only Fastzombies when zombies are on...(this will be completely fixed soon) + + +DarkRP 2.0 +June 30, 2007 +Updated by: Pcwizdan + +[Add] rp_doorcost <amount> - The Cost to buy a door. +[Add] rp_zombiescore <amount> - The money awarded for a NPC Kill. +1[Add] Player's now spawn with Cameras +1[Add] Grenades to BuyShipment +1[Add] Flashbangs to Buyshipment +1[Add] /sleep for 10 seconds then /sleep again to unsleep +[Add] flash/gren so only 2 spawns... +[Add] CfgVar "physgun" +[Add] CfgVar "adminjailpos" +[Add] rp_talkradius - To set Normal Talk Radius +[Add] rp_yellradius - To Set Yell Radius +[Add] rp_whispradius - To set Whisper Radius +[Add] A "Law" Group to be used when Cfgvar "adminjailpos" = 0 making Cp's and Ow's(mayor) able to set Jailpos, 1 making admin only +1[Add] Hints telling the correct jailtime, not always 120 seconds. + +1[Changed] Ow to Mayor - Makes more sense +1[Changed] Dist between Cop and criminal needed for arrest/unarrest from 94 to 115 (should make arresting easier now) + +1[Removed] Para - Created ridiculous amount of warfare. +1[Removed] TMP - To make room in F2 for Flashbangs. +1[Removed] Melee Damage of Lockpick (Created Crowbar Deathmatch) +1[Removed] All Players spawn with NO WEAPONS, only job based ones (CPs get a Glock/USP) reason: Freeguns=bad economy and Deathmatchers as soon as people become Gangsters. + +1[Fix] Maxdruglabs not working, There was no limit. +1[Fix] /drop money not working, new command /moneydrop +1[Fix] Many Typos and spelling errors in help menus and functions +1[Fix] /agenda showing in chat, aswell as all the others that also do +1[Fix] Drugs turn to food when Hungermod enabled +1[Fix] Buying ammo not subtracting money for sale. +1[Fix] "Bill HAMPTONhas a Warrant for their Arrest!" typo, added space. +1[Fix] Hint Typos +1[Fix] Description/Help in admins.lua +1[Fix] Unneeded "Tip #1: " prefixes (they took up space in chat and looked horrible.) +1[QuickFix] For no damage zombies: Made Only Fastzombies when zombies are on...(this will be completely fixed soon) + + +LightRP 1.3 +February 1, 2007 + +[Add] Help labels are created more efficiently, and allow for newer help commands added by modules +[Add] Amount console variables are created more efficiently with AddValueCommand +[Add] rp_tell <Name/Partial-Name> <Message> - Send an admin message to a player +[Add] "Hunger Mod" module +[Add] /write - Write letters. +[Add] Multiple owners for doors +[Add] OW votes +[Add] More toggleables for CP-voting/OW-voting +[Add] rp_enforcemodels <1 or 0> - Set if player model enforcement should be enabled (players can use zombie models, combine models, etc..) +[Add] rp_lock, rp_unlock - Lock/unlock doors you're facing +[Add] rp_own, rp_unown - Own/unown doors +[Add] rp_addowner <Name>, rp_removeowner <Name> - Add/remove co-owners +[Add] rp_adminsents <1 or 0> - Should SENTs be spawnable by only admins +[Add] /pm chat command +[Add] data/LightRP/servercfg.txt, allowing you to do rp admin commands on server start up without modifying the script. YOU HAVE TO CREATE THIS FILE MANUALLY. +[Add] Admin commands to toggle prop paying/change prop spawning price +[Add] /help chat command +[Fix] Console can now do rp admin commands +[Fix] Help menu reorganized +[Fix] Changing chat prefix would glitch up the help menu +[Fix] You can lock/unlock owned vehicles +[Fix] You can vote anytime you're in gui "clicker" mode. +[Fix] /give and /drop money can't do negative money + + +LightRP 1.2 +January 30, 2007 + +[Fix] Players couldnt spawn wheels/thrusters/lamps/etc... +[Add] rp_adminsweps, toggles whether all sweps should be admin only. +[Add] rp_chatprefix, change the chat prefix for commands, like /votecop to !votecop + +LightRP 1.1 +January 29, 2007 + +[Add] rp_toolgun - Toggle toolguns +[Add] Allowed props, rp_allowedprops (bannedprops.lua) +[Add] rp_propspawning +[Add] Module scripts (should be self explanatory to developers) +[Fix] Toggle concommands are scripted more efficiently + +------------- +-- LightRP +-- Rick Darkaliono aka DarkCybo1 +-- Jan 22, 2007 +-- Done Jan 26, 2007 +-- This script isn't a representation of my skillz +------------- +-- DarkRP v1.07 +-- By: Rickster +-- Done June 15, 2007 +------------- +-- v2.0 and up +-- By: Pcwizdan / Silent Inferno +-- All credit goes to Rickster, v2.0 just a Fix/Cleanup Mod +------------- +-- DarkRP 2008 v2.21 E +-- by philxyz +-- code cleanup +-- earthquake +------------- +-- DarkRP 2008 v2.22 E +-- by philxyz +------------- +-- DarkRP 2.2.3 +-- by philxyz +-- fix the name, it was getting stupid. +-- add a /queryvar command so clients can see what a given server var is are set to +-- chat.lua now checks whole command, not just first letters +-- fix simple prop protection compatibility +-- fix scroll direction in F1 menu +-- fix prices being 0 on initial load +-- chief can now set the jailpos as well as admin +-- anti-deathmatch feature rp_dm_autokick, rp_dm_maxkills and rp_dm_gracetime +-- move sv_earthquakes and sv_earthquake_chance_is_1_in to the rp_ namespace +-- allow searching by player name and steam ID for /playerwarrant etc +-- more code clean up (spaces for tabs, remove semicolons) +-- only gangs can spawn druglabs. Like in DarkRP 2.1 +------------- +-- DarkRP 2.2.4 +-- by philxyz and [GNC] Matt +-- philxyz: Arrested players are now on suicide watch! (no kill in console) +-- philxyz: Arrested players when killed, will not respawn until their time in jail is served +-- philxyz: CPs can only smash in doors with a warrant set on one of the door's owners (rp_doorwarrants 1 default) +-- philxyz: If a gangster or the mobboss steals a battering ram, they can use it as they wish +-- philxyz: If there is a Mayor, he must approve /playerwarrant requests by CPs +-- philxyz: When someone gets demoted, they can not rejoin that job until the timer is up (rp_demotetime 120) 2 mins by default +-- philxyz: Fix a bunch of Lua errors +-- philxyz: Fix the baton view model errors +-- philxyz: Fixed the last wrongly labelled "Max Gundealers Reached" Notify() +-- philxyz & [GNC] Matt: FindPlayer() is now case insensitive +-- philxyz: Backport /give and /moneydrop from my new unreleased gamemode. Fractional amounts are now not allowed and minimum give amount is 2 dollars +-- philxyz: Lockpick has random chance of success and can open vehicles +-- philxyz: Shipments now come in crates +-- philxyz: Disable mayor help on mayor death +-- [GNC] Matt: Police Chief spawn position fix +-- [GNC] Matt: Hit and run now registers as a kill by the driver +-- [GNC] Matt: Battering ram can force people out of vehicles +-- [GNC] Matt: Keys work on vehicles at a greater distance +------------- +-- DarkRP 2.2.5 +-- by philxyz and [GNC] Matt +-- [GNC] Matt: Simplify chat.lua +-- [GNC] Matt: Fix a money related typo +-- philxyz: Player has died in jail is now printed in the middle of the screen +-- philxyz: Replace IsValid() with calls to G.ValidEntity() +-- philxyz: Use the Unix find and sed commands to clean out the syntax even more (spacing, semicolons, if spacing etc) +-- philxyz: Remove all traces of old bug reporting tools +-- philxyz: Actually fix the druglab removal on player disconnect process +-- philxyz: Remove spawned microwaves and gun labs on player disconnect +-- philxyz: Fix the rest of the help screens not disappearing on job change +------------- +-- DarkRP 2.2.6 +-- by philxyz +-- philxyz: Remove the ability to screw up your money +-- philxyz: CPs can smash open their own or unowned doors (important for rp_tb_city45 nexus) +-- philxyz: allow help menu closing using /x in chat - it's much quicker +-- philxyz: Added /dropweapon, /weapondrop as aliases for /drop +-- philxyz: /moneydrop is now /dropmoney (with an alias /moneydrop so as not to confuse people) +-- philxyz: Money should be owned by "World" according to Simple Prop Protection +------------- +-- DarkRP 2.2.7 +-- philxyz: Implement rp_maxdrugs and rp_maxfoods +-- philxyz: A more successful attempt at fixing Simple Prop Protection compatibility - thanks to Spacetech for both his original work and his co-operation +-- philxyz: When unowning doors, don't show cents in the sale value +-- philxyz: Implement rp_maxdrugs and rp_maxfoods +-- philxyz: Show the price when buying a melon +-- philxyz: Remove letters on player disconnect +-- philxyz: When unowning doors, don't show cents in the sale value +-- philxyz: Fix an old typo that meant rp_gunlabcost was not working +-- philxyz: Fix rp_microwavefoodcost +------------ +-- DarkRP 2.2.8 +-- philxyz: Fix "Players could never re-take a job after demotion" +-- philxyz: Remove duplicate function definitions from util.lua +-- philxyz: Log Notify() output to server and client consoles (useful for /give proof of purchase) +------------ +-- DarkRP 2.2.9 +-- philxyz: Remove /playerwarrant - it became a bizarre cross between arrest warrant (unnecessary) and search warrant +-- philxyz: Add /warrant command for search warrants +-- philxyz: Add rp_searchtime (default 30) for search warrant expiry +-- philxyz: Add /wanted and /unwanted commands for CPs or the Mayor to highlight a wanted criminal (replaces playerwarrant) +-- philxyz: Add /ao and /ro as aliases for /addowner and /removeowner respectively. +-- philxyz: Add more commands to the F2 menu +------------ +-- DarkRP 2.2.10 +-- philxyz: For these and future changes, please refer to changelog.txt + diff --git a/gamemodes/darkrp/LICENSE.txt b/gamemodes/darkrp/LICENSE.txt new file mode 100644 index 0000000..c4540dc --- /dev/null +++ b/gamemodes/darkrp/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Falco Peijnenburg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/gamemodes/darkrp/README.md b/gamemodes/darkrp/README.md new file mode 100644 index 0000000..4ec4a72 --- /dev/null +++ b/gamemodes/darkrp/README.md @@ -0,0 +1,31 @@ +# DarkRP ![run-glualint](https://github.com/FPtje/DarkRP/workflows/run-glualint/badge.svg?branch=master) +A roleplay gamemode for Garry's Mod. + +## Getting DarkRP +Please use either git or the workshop. +Manually downloading DarkRP or using SVN is possible, but not recommended. + +The workshop version of DarkRP can be found here: + +https://steamcommunity.com/sharedfiles/filedetails/?id=248302805 + +## Modifying DarkRP +Check out the wiki! + +https://darkrp.miraheze.org/wiki/Main_Page + +Make sure to download the DarkRPMod: + +https://github.com/FPtje/darkrpmodification + +Do you want to create a gamemode based on DarkRP? +You probably shouldn't. If you insist, use the derived gamemode that can be downloaded here: + +https://github.com/FPtje/DarkRP/releases/tag/derived + +Just whatever you do, don't touch DarkRP's core files. + +## Getting help +Please head to the official Discord! + +https://darkrp.page.link/discord diff --git a/gamemodes/darkrp/content/materials/darkrp/darkrpderma.png b/gamemodes/darkrp/content/materials/darkrp/darkrpderma.png new file mode 100644 index 0000000..a4066d1 Binary files /dev/null and b/gamemodes/darkrp/content/materials/darkrp/darkrpderma.png differ diff --git a/gamemodes/darkrp/content/materials/fadmin/back.vmt b/gamemodes/darkrp/content/materials/fadmin/back.vmt new file mode 100644 index 0000000..1717a1f --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/back.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/back" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/back.vtf b/gamemodes/darkrp/content/materials/fadmin/back.vtf new file mode 100644 index 0000000..2e74cd5 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/back.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/access.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/access.vmt new file mode 100644 index 0000000..3e9fb3c --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/access.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/access" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/access.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/access.vtf new file mode 100644 index 0000000..45adcf7 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/access.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/ban.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/ban.vmt new file mode 100644 index 0000000..7bfaeeb --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/ban.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/ban" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/ban.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/ban.vtf new file mode 100644 index 0000000..b0f3215 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/ban.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/changeteam.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/changeteam.vmt new file mode 100644 index 0000000..ff01e12 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/changeteam.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/changeteam" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/changeteam.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/changeteam.vtf new file mode 100644 index 0000000..5e3083f Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/changeteam.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/chatmute.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/chatmute.vmt new file mode 100644 index 0000000..dbfe0c3 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/chatmute.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/chatmute" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/chatmute.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/chatmute.vtf new file mode 100644 index 0000000..0b104e1 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/chatmute.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/cleanup.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/cleanup.vmt new file mode 100644 index 0000000..c112cd9 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/cleanup.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/cleanup" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/cleanup.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/cleanup.vtf new file mode 100644 index 0000000..7194139 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/cleanup.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/cloak.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/cloak.vmt new file mode 100644 index 0000000..644226c --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/cloak.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/cloak" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/cloak.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/cloak.vtf new file mode 100644 index 0000000..94869cc Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/cloak.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/disable.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/disable.vmt new file mode 100644 index 0000000..d92b93a --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/disable.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/disable" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/disable.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/disable.vtf new file mode 100644 index 0000000..a0114fa Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/disable.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/freeze.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/freeze.vmt new file mode 100644 index 0000000..8746ff7 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/freeze.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/freeze" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/freeze.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/freeze.vtf new file mode 100644 index 0000000..533aa59 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/freeze.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/god.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/god.vmt new file mode 100644 index 0000000..a10e2ef --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/god.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/god" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/god.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/god.vtf new file mode 100644 index 0000000..7e190ad Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/god.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/ignite.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/ignite.vmt new file mode 100644 index 0000000..99037d4 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/ignite.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/ignite" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/ignite.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/ignite.vtf new file mode 100644 index 0000000..1aa5561 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/ignite.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/jail.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/jail.vmt new file mode 100644 index 0000000..9cd090f --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/jail.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/jail" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/jail.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/jail.vtf new file mode 100644 index 0000000..16d3621 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/jail.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/kick.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/kick.vmt new file mode 100644 index 0000000..3a366ea --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/kick.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/kick" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/kick.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/kick.vtf new file mode 100644 index 0000000..91f79ad Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/kick.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/message.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/message.vmt new file mode 100644 index 0000000..c05a791 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/message.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/message" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/message.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/message.vtf new file mode 100644 index 0000000..8f8a92c Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/message.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/motd.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/motd.vmt new file mode 100644 index 0000000..92d77c4 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/motd.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/motd" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/motd.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/motd.vtf new file mode 100644 index 0000000..ae5a115 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/motd.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/noclip.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/noclip.vmt new file mode 100644 index 0000000..11e00be --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/noclip.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/noclip" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/noclip.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/noclip.vtf new file mode 100644 index 0000000..71b63af Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/noclip.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/pickup.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/pickup.vmt new file mode 100644 index 0000000..81501e9 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/pickup.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/pickup" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/pickup.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/pickup.vtf new file mode 100644 index 0000000..9dbc53c Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/pickup.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/ragdoll.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/ragdoll.vmt new file mode 100644 index 0000000..4c1a472 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/ragdoll.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/ragdoll" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/ragdoll.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/ragdoll.vtf new file mode 100644 index 0000000..203e5d5 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/ragdoll.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/rcon.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/rcon.vmt new file mode 100644 index 0000000..b081447 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/rcon.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/rcon" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/rcon.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/rcon.vtf new file mode 100644 index 0000000..0722c06 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/rcon.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/serversetting.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/serversetting.vmt new file mode 100644 index 0000000..64087a7 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/serversetting.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/serversetting" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/serversetting.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/serversetting.vtf new file mode 100644 index 0000000..a5c5911 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/serversetting.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/slap.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/slap.vmt new file mode 100644 index 0000000..c08cdf1 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/slap.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/slap" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/slap.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/slap.vtf new file mode 100644 index 0000000..2550f67 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/slap.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/slay.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/slay.vmt new file mode 100644 index 0000000..1cd7283 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/slay.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/slay" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/slay.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/slay.vtf new file mode 100644 index 0000000..82c7e92 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/slay.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/spectate.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/spectate.vmt new file mode 100644 index 0000000..4fbbb98 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/spectate.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/spectate" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/spectate.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/spectate.vtf new file mode 100644 index 0000000..a6b4333 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/spectate.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/teleport.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/teleport.vmt new file mode 100644 index 0000000..1052972 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/teleport.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/teleport" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/teleport.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/teleport.vtf new file mode 100644 index 0000000..8bd0a82 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/teleport.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/voicemute.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/voicemute.vmt new file mode 100644 index 0000000..e010944 --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/voicemute.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/voicemute" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/voicemute.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/voicemute.vtf new file mode 100644 index 0000000..431e47c Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/voicemute.vtf differ diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/weapon.vmt b/gamemodes/darkrp/content/materials/fadmin/icons/weapon.vmt new file mode 100644 index 0000000..742438e --- /dev/null +++ b/gamemodes/darkrp/content/materials/fadmin/icons/weapon.vmt @@ -0,0 +1,8 @@ +"UnlitGeneric" +{ + "$basetexture" "fadmin/icons/weapon" + "$ignorez" 1 + "$vertexcolor" 1 + "$vertexalpha" 1 + "$nolod" 1 +} diff --git a/gamemodes/darkrp/content/materials/fadmin/icons/weapon.vtf b/gamemodes/darkrp/content/materials/fadmin/icons/weapon.vtf new file mode 100644 index 0000000..f19e832 Binary files /dev/null and b/gamemodes/darkrp/content/materials/fadmin/icons/weapon.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/arrest_stick.vmt b/gamemodes/darkrp/content/materials/vgui/entities/arrest_stick.vmt new file mode 100644 index 0000000..956b9df --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/arrest_stick.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/arrest_stick" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/arrest_stick.vtf b/gamemodes/darkrp/content/materials/vgui/entities/arrest_stick.vtf new file mode 100644 index 0000000..9a2b9d5 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/arrest_stick.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/door_ram.vmt b/gamemodes/darkrp/content/materials/vgui/entities/door_ram.vmt new file mode 100644 index 0000000..c022dbc --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/door_ram.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/door_ram" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/door_ram.vtf b/gamemodes/darkrp/content/materials/vgui/entities/door_ram.vtf new file mode 100644 index 0000000..ffc1d00 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/door_ram.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/keys.vmt b/gamemodes/darkrp/content/materials/vgui/entities/keys.vmt new file mode 100644 index 0000000..e73e744 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/keys.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/keys" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/keys.vtf b/gamemodes/darkrp/content/materials/vgui/entities/keys.vtf new file mode 100644 index 0000000..7e5c9e2 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/keys.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/lockpick.vmt b/gamemodes/darkrp/content/materials/vgui/entities/lockpick.vmt new file mode 100644 index 0000000..96da02a --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/lockpick.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/lockpick" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/lockpick.vtf b/gamemodes/darkrp/content/materials/vgui/entities/lockpick.vtf new file mode 100644 index 0000000..3febb2c Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/lockpick.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/ls_sniper.vmt b/gamemodes/darkrp/content/materials/vgui/entities/ls_sniper.vmt new file mode 100644 index 0000000..d9b37a0 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/ls_sniper.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/ls_sniper" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/ls_sniper.vtf b/gamemodes/darkrp/content/materials/vgui/entities/ls_sniper.vtf new file mode 100644 index 0000000..5e0b6c6 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/ls_sniper.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/med_kit.vmt b/gamemodes/darkrp/content/materials/vgui/entities/med_kit.vmt new file mode 100644 index 0000000..70af6cb --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/med_kit.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/med_kit" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/med_kit.vtf b/gamemodes/darkrp/content/materials/vgui/entities/med_kit.vtf new file mode 100644 index 0000000..717df3a Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/med_kit.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/pocket.vmt b/gamemodes/darkrp/content/materials/vgui/entities/pocket.vmt new file mode 100644 index 0000000..878c5d9 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/pocket.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/pocket" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/pocket.vtf b/gamemodes/darkrp/content/materials/vgui/entities/pocket.vtf new file mode 100644 index 0000000..b106f47 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/pocket.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/stunstick.vmt b/gamemodes/darkrp/content/materials/vgui/entities/stunstick.vmt new file mode 100644 index 0000000..a634c49 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/stunstick.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/stunstick" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/stunstick.vtf b/gamemodes/darkrp/content/materials/vgui/entities/stunstick.vtf new file mode 100644 index 0000000..092c3c5 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/stunstick.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/unarrest_stick.vmt b/gamemodes/darkrp/content/materials/vgui/entities/unarrest_stick.vmt new file mode 100644 index 0000000..f32a821 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/unarrest_stick.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/unarrest_stick" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/unarrest_stick.vtf b/gamemodes/darkrp/content/materials/vgui/entities/unarrest_stick.vtf new file mode 100644 index 0000000..89c67ac Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/unarrest_stick.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_ak472.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_ak472.vmt new file mode 100644 index 0000000..ad5842d --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_ak472.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_ak472" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_ak472.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_ak472.vtf new file mode 100644 index 0000000..0a49e9f Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_ak472.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_deagle2.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_deagle2.vmt new file mode 100644 index 0000000..eb969e8 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_deagle2.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_deagle2" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_deagle2.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_deagle2.vtf new file mode 100644 index 0000000..243a7ba Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_deagle2.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_fiveseven2.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_fiveseven2.vmt new file mode 100644 index 0000000..8b23d0a --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_fiveseven2.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_fiveseven2" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_fiveseven2.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_fiveseven2.vtf new file mode 100644 index 0000000..34c6281 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_fiveseven2.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_glock2.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_glock2.vmt new file mode 100644 index 0000000..79e17c5 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_glock2.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_glock2" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_glock2.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_glock2.vtf new file mode 100644 index 0000000..cb8fe0b Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_glock2.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_keypadchecker.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_keypadchecker.vmt new file mode 100644 index 0000000..8890af6 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_keypadchecker.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_keypadchecker" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_keypadchecker.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_keypadchecker.vtf new file mode 100644 index 0000000..4bcab7d Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_keypadchecker.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_m42.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_m42.vmt new file mode 100644 index 0000000..1b39ba2 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_m42.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_m42" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_m42.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_m42.vtf new file mode 100644 index 0000000..a3cba30 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_m42.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_mac102.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mac102.vmt new file mode 100644 index 0000000..b3a3a85 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mac102.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_mac102" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_mac102.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mac102.vtf new file mode 100644 index 0000000..90610e6 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mac102.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_mp52.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mp52.vmt new file mode 100644 index 0000000..f182baa --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mp52.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_mp52" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_mp52.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mp52.vtf new file mode 100644 index 0000000..6bb6366 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_mp52.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_p2282.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_p2282.vmt new file mode 100644 index 0000000..3b28453 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_p2282.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_p2282" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_p2282.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_p2282.vtf new file mode 100644 index 0000000..b49616d Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_p2282.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_pumpshotgun2.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weapon_pumpshotgun2.vmt new file mode 100644 index 0000000..6565ddf --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weapon_pumpshotgun2.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weapon_pumpshotgun2" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weapon_pumpshotgun2.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weapon_pumpshotgun2.vtf new file mode 100644 index 0000000..753d04d Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weapon_pumpshotgun2.vtf differ diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weaponchecker.vmt b/gamemodes/darkrp/content/materials/vgui/entities/weaponchecker.vmt new file mode 100644 index 0000000..de0fb92 --- /dev/null +++ b/gamemodes/darkrp/content/materials/vgui/entities/weaponchecker.vmt @@ -0,0 +1,7 @@ +"UnlitGeneric" +{ + "$basetexture" "VGUI/entities/weaponchecker" + "$vertexcolor" 1 + "$vertexalpha" 1 + "$translucent" 1 +} \ No newline at end of file diff --git a/gamemodes/darkrp/content/materials/vgui/entities/weaponchecker.vtf b/gamemodes/darkrp/content/materials/vgui/entities/weaponchecker.vtf new file mode 100644 index 0000000..da7f4f1 Binary files /dev/null and b/gamemodes/darkrp/content/materials/vgui/entities/weaponchecker.vtf differ diff --git a/gamemodes/darkrp/content/sound/earthquake.mp3 b/gamemodes/darkrp/content/sound/earthquake.mp3 new file mode 100644 index 0000000..4a041a1 Binary files /dev/null and b/gamemodes/darkrp/content/sound/earthquake.mp3 differ diff --git a/gamemodes/darkrp/darkrp.txt b/gamemodes/darkrp/darkrp.txt new file mode 100644 index 0000000..b7a455b --- /dev/null +++ b/gamemodes/darkrp/darkrp.txt @@ -0,0 +1,17 @@ +"darkrp" +{ + "base" "sandbox" + "title" "DarkRP" + "version" "2.7.0" + "category" "rp" + "menusystem" "1" + "workshopid" "248302805" + + "author_name" "FPtje Falco et al." + "author_email" "" + "author_url" "https://github.com/FPtje/DarkRP" + + "icon" "" + "info" "" + "hide" "0" +} diff --git a/gamemodes/darkrp/entities/entities/darkrp_billboard/cl_init.lua b/gamemodes/darkrp/entities/entities/darkrp_billboard/cl_init.lua new file mode 100644 index 0000000..8de06f5 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_billboard/cl_init.lua @@ -0,0 +1,60 @@ +include("shared.lua") + +ENT.DrawPos = Vector(1, -111, 58) + +ENT.Width = 558 +ENT.Height = 290 + +ENT.HeaderMargin = 10 +ENT.BodyMargin = 10 + +ENT.HeaderFont = "Trebuchet48" +ENT.BodyFont = "DermaLarge" + +function ENT:Draw() + self:DrawModel() + + local DrawPos = self:LocalToWorld(self.DrawPos) + + local DrawAngles = self:GetAngles() + DrawAngles:RotateAroundAxis(self:GetAngles():Forward(), 90) + DrawAngles:RotateAroundAxis(self:GetAngles():Up(), 90) + + local backgroundColor = self:GetBackgroundColor() * 255 + local barColor = self:GetBarColor() * 255 + local topText = DarkRP.textWrap(self:GetTopText(), self.HeaderFont, self.Width - self.BodyMargin * 2) + + local bottomText = string.gsub(string.gsub(self:GetBottomText() or "", "//", "\n"), "\\n", "\n") + bottomText = DarkRP.textWrap(string.Replace(bottomText, "\\n", "\n"), self.BodyFont, self.Width - self.BodyMargin * 2) + + if not self.HeaderFontHeight then self.HeaderFontHeight = draw.GetFontHeight(self.HeaderFont) end + + local barHeight = 1 + for _ in string.gmatch(topText, "\n") do barHeight = barHeight + 1 end + barHeight = self.HeaderMargin * 2 + barHeight * self.HeaderFontHeight + + local centerX = self.Width / 2 + + render.EnableClipping(true) + local normal = self:GetUp() + render.PushCustomClipPlane(normal, normal:Dot(DrawPos - normal * self.Height * 0.4)) + + cam.Start3D2D(DrawPos, DrawAngles, 0.4) + + surface.SetDrawColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 255) + surface.DrawRect(0, 0, self.Width, self.Height) + + draw.RoundedBox(0, 0, 0, self.Width, barHeight, Color(barColor.x, barColor.y, barColor.z)) + + draw.DrawText(topText, self.HeaderFont, centerX, self.HeaderMargin, color_white, TEXT_ALIGN_CENTER) + draw.DrawText(bottomText, self.BodyFont, centerX, barHeight + self.BodyMargin, color_white, TEXT_ALIGN_CENTER) + + cam.End3D2D() + + render.PopCustomClipPlane() + render.EnableClipping(false) +end + +language.Add("Cleaned_advert_billboards", "Cleaned up Advert Billboards") +language.Add("Cleanup_advert_billboards", "Advert Billboards") +language.Add("Undone_advert_billboard", "Undone Advert Billboard") diff --git a/gamemodes/darkrp/entities/entities/darkrp_billboard/init.lua b/gamemodes/darkrp/entities/entities/darkrp_billboard/init.lua new file mode 100644 index 0000000..b37855f --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_billboard/init.lua @@ -0,0 +1,100 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel(self.Model or "models/props/cs_assault/billboard.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:EnableMotion(false) + end +end + +function ENT:SetDefaults(txt) + txt = string.gsub(string.gsub(txt or "", "//", "\n"), "\\n", "\n") + local split = string.Split(txt, "\n") or {} + local hasTitle = #split > 1 + if not hasTitle then split = string.Split(txt, " ") end + + self:SetTopText(split[1] or "Placeholder") + self:SetBottomText(table.concat(split, hasTitle and "\n" or " ", 2)) + + self:SetBarColor(Vector(1, 0.5, 0)) +end + +local function canEditVariable(self, ent, ply, key, val, editor) + if self ~= ent then return end + return self:CPPICanPhysgun(ply) +end + +local function placeBillboard(ply, args) + local canEdit, message = hook.Call("canAdvert", nil, ply, args) + + if canEdit == false then + DarkRP.notify(ply, 1, 4, message or DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. "advert", "")) + return "" + end + + ply.DarkRP_advertboards = ply.DarkRP_advertboards or 0 + + if ply.DarkRP_advertboards >= GAMEMODE.Config.maxadvertbillboards then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", GAMEMODE.Config.chatCommandPrefix .. "advert")) + return "" + end + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + local ent = ents.Create("darkrp_billboard") + ent:SetPos(tr.HitPos + Vector(0, 0, (ply:GetPos().z - tr.HitPos.z) + 69)) + + local ang = ply:GetAngles() + ang:RotateAroundAxis(ang:Up(), 180) + ent:SetAngles(ang) + + ent:CPPISetOwner(ply) + ent.SID = ply.SID + + ent:SetDefaults(args) + hook.Add("CanEditVariable", ent, canEditVariable) + + ent:Spawn() + ent:Activate() + + if IsValid(ent) then + ply.DarkRP_advertboards = ply.DarkRP_advertboards + 1 + end + + ply:DeleteOnRemove(ent) + + undo.Create("advert_billboard") + undo.SetPlayer(ply) + undo.AddEntity(ent) + undo.Finish() + + ply:AddCleanup("advert_billboards", ent) + + hook.Call("playerAdverted", nil, ply, args, ent) + + return "" +end +DarkRP.defineChatCommand("advert", placeBillboard) + +function ENT:OnRemove() + local ply = Player(self.SID) + + if not IsValid(ply) then return end + + ply.DarkRP_advertboards = (ply.DarkRP_advertboards or 1) - 1 +end diff --git a/gamemodes/darkrp/entities/entities/darkrp_billboard/shared.lua b/gamemodes/darkrp/entities/entities/darkrp_billboard/shared.lua new file mode 100644 index 0000000..d5d83f0 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_billboard/shared.lua @@ -0,0 +1,113 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "DarkRP billboard" +ENT.Instructions = "Shows advertisements." +ENT.Author = "FPtje" + +ENT.Spawnable = false +ENT.Editable = true +ENT.IsDarkRPBillboard = true + +cleanup.Register("advert_billboards") + +function ENT:SetupDataTables() + self:NetworkVar("String", 0, "TopText", { + KeyName = "toptext", + Edit = { + type = "Generic", + title = "Top text", + category = "Text", + order = 0 + } + }) + + self:NetworkVar("String", 1, "BottomText", { + KeyName = "bottomtext", + Edit = { + type = "Generic", + title = "Bottom text", + category = "Text", + order = 1 + } + }) + + self:NetworkVar("Vector", 0, "BackgroundColor", { + KeyName = "backgroundcolor", + Edit = { + type = "VectorColor", + title = "Background color", + category = "Color", + order = 0 + } + }) + + self:NetworkVar("Vector", 1, "BarColor", { + KeyName = "barcolor", + Edit = { + type = "VectorColor", + title = "Top bar color", + category = "Color", + order = 1 + } + }) +end + +DarkRP.declareChatCommand{ + command = "advert", + description = "Create a billboard holding an advertisement.", + delay = 1.5 +} + +DarkRP.hookStub{ + name = "canAdvert", + description = "Whether someone can place an advertisement billboard.", + parameters = { + { + name = "player", + description = "The player trying to advertise.", + type = "Player" + }, + { + name = "arguments", + description = "The advertisement itself.", + type = "table" + } + }, + returns = { + { + name = "canAdvert", + description = "A yes or no as to whether the player can place the billboard.", + type = "boolean" + }, + { + name = "message", + description = "The message that is shown when they can't place the billboard.", + type = "string" + } + }, + realm = "Server" +} + +DarkRP.hookStub{ + name = "playerAdverted", + description = "Called when a player placed an advertisement billboard.", + parameters = { + { + name = "player", + description = "The player.", + type = "Player" + }, + { + name = "arguments", + description = "The advertisement itself.", + type = "string" + }, + { + name = "entity", + description = "The placed advertisement billboard.", + type = "Entity" + } + }, + returns = {}, + realm = "Server" +} diff --git a/gamemodes/darkrp/entities/entities/darkrp_cheque/cl_init.lua b/gamemodes/darkrp/entities/entities/darkrp_cheque/cl_init.lua new file mode 100644 index 0000000..e931274 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_cheque/cl_init.lua @@ -0,0 +1,31 @@ +include("shared.lua") + +ENT.TextColors = { + OtherToSelf = Color(0, 255, 0, 255), + SelfToSelf = Color(255, 255, 0, 255), + SelfToOther = Color(0, 0, 255, 255), + OtherToOther = Color(255, 0, 0, 255) +} + +function ENT:Draw() + self:DrawModel() + + local owner = self:Getowning_ent() + local recipient = self:Getrecipient() + local ownerplayer = owner:IsPlayer() + local recipientplayer = recipient:IsPlayer() + local localplayer = LocalPlayer() + + local Pos = self:GetPos() + local Ang = self:GetAngles() + local Up = Ang:Up() + Up:Mul(0.9) + Pos:Add(Up) + + surface.SetFont("ChatFont") + local text = DarkRP.getPhrase("cheque_pay", recipientplayer and recipient:Nick() or DarkRP.getPhrase("unknown")) .. "\n" .. DarkRP.formatMoney(self:Getamount()) .. "\n" .. DarkRP.getPhrase("signed", ownerplayer and owner:Nick() or DarkRP.getPhrase("unknown")) + + cam.Start3D2D(Pos, Ang, 0.1) + draw.DrawNonParsedText(text, "ChatFont", surface.GetTextSize(text) * -0.5, -25, localplayer:IsValid() and (ownerplayer and localplayer == owner and (recipientplayer and localplayer == recipient and self.TextColors.SelfToSelf or self.TextColors.SelfToOther) or recipientplayer and localplayer == recipient and self.TextColors.OtherToSelf) or self.TextColors.OtherToOther, 0) + cam.End3D2D() +end \ No newline at end of file diff --git a/gamemodes/darkrp/entities/entities/darkrp_cheque/init.lua b/gamemodes/darkrp/entities/entities/darkrp_cheque/init.lua new file mode 100644 index 0000000..c82981b --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_cheque/init.lua @@ -0,0 +1,147 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/props_lab/clipboard.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self.nodupe = true + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + hook.Add("PlayerDisconnected", self, self.onPlayerDisconnected) +end + +function ENT:Use(activator, caller) + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + local owner = self:Getowning_ent() + local recipient = self:Getrecipient() + local amount = self:Getamount() or 0 + + if (IsValid(activator) and IsValid(recipient)) and activator == recipient then + owner = (IsValid(owner) and owner:Nick()) or DarkRP.getPhrase("disconnected_player") + DarkRP.notify(activator, 0, 4, DarkRP.getPhrase("found_cheque", DarkRP.formatMoney(amount), "", owner)) + activator:addMoney(amount) + hook.Call("playerPickedUpCheque", nil, activator, recipient, amount or 0, true, self) + self:Remove() + elseif (IsValid(owner) and IsValid(recipient)) and owner ~= activator then + DarkRP.notify(activator, 0, 4, DarkRP.getPhrase("cheque_details", recipient:Nick())) + hook.Call("playerPickedUpCheque", nil, activator, recipient, amount or 0, false, self) + elseif IsValid(owner) and owner == activator then + DarkRP.notify(activator, 0, 4, DarkRP.getPhrase("cheque_torn")) + owner:addMoney(self:Getamount()) -- return the money on the cheque to the owner. + hook.Call("playerToreUpCheque", nil, activator, recipient, amount, self) + self:Remove() + elseif not IsValid(recipient) then self:Remove() + end +end + +function ENT:StartTouch(ent) + -- the .USED var is also used in other mods for the same purpose + if ent:GetClass() ~= "darkrp_cheque" or self.USED or ent.USED or self.hasMerged or ent.hasMerged then return end + if ent:Getowning_ent() ~= self:Getowning_ent() then return end + if ent:Getrecipient() ~= self:Getrecipient() then return end + + -- Both hasMerged and USED are used by third party mods. Keep both in. + ent.USED = true + ent.hasMerged = true + + ent:Remove() + self:Setamount(self:Getamount() + ent:Getamount()) +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + local typ = dmg:GetDamageType() + if bit.band(typ, bit.bor(DMG_FALL, DMG_VEHICLE, DMG_DROWN, DMG_RADIATION, DMG_PHYSGUN)) > 0 then return end + + self.USED = true + self.hasMerged = true + self:Remove() +end + +function ENT:onPlayerDisconnected(ply) + if self:Getowning_ent() == ply or self:Getrecipient() == ply then + self:Remove() + end +end + +DarkRP.hookStub{ + name = "playerPickedUpCheque", + description = "Called when a player picks up a cheque.", + parameters = { + { + name = "player", + description = "The player who attempted to pick up the cheque.", + type = "Player" + }, + { + name = "player", + description = "The player who the cheque was written to.", + type = "Player" + }, + { + name = "amount", + description = "The amount of money the cheque has.", + type = "number" + }, + { + name = "success", + description = "Whether the player was allowed to cash the cheque.", + type = "bool" + }, + { + name = "entity", + description = "The entity of the cheque.", + type = "Entity" + } + }, + returns = { + }, + realm = "Server" +} + +DarkRP.hookStub{ + name = "playerToreUpCheque", + description = "Called when a player tears up a cheque.", + parameters = { + { + name = "player", + description = "The player who tore up the cheque.", + type = "Player" + }, + { + name = "player", + description = "The player who the cheque was written to.", + type = "Player" + }, + { + name = "amount", + description = "The amount of money the cheque has.", + type = "number" + }, + { + name = "entity", + description = "The entity of the cheque.", + type = "Entity" + } + }, + returns = { + }, + realm = "Server" +} diff --git a/gamemodes/darkrp/entities/entities/darkrp_cheque/shared.lua b/gamemodes/darkrp/entities/entities/darkrp_cheque/shared.lua new file mode 100644 index 0000000..d42dd8f --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_cheque/shared.lua @@ -0,0 +1,12 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Cheque" +ENT.Author = "Eusion" +ENT.Spawnable = false +ENT.IsDarkRPCheque = true + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "owning_ent") + self:NetworkVar("Entity", 1, "recipient") + self:NetworkVar("Int", 0, "amount") +end diff --git a/gamemodes/darkrp/entities/entities/darkrp_laws/cl_init.lua b/gamemodes/darkrp/entities/entities/darkrp_laws/cl_init.lua new file mode 100644 index 0000000..19ea12f --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_laws/cl_init.lua @@ -0,0 +1,72 @@ +include("shared.lua") + +local Laws = {} + +ENT.DrawPos = Vector(1, -111, 58) + +local color_navy_200 = Color(0, 0, 70, 200) +local color_red = Color(255, 0, 0, 255) +local color_white = color_white + +function ENT:Draw() + self:DrawModel() + + local DrawPos = self:LocalToWorld(self.DrawPos) + + local DrawAngles = self:GetAngles() + DrawAngles:RotateAroundAxis(self:GetAngles():Forward(), 90) + DrawAngles:RotateAroundAxis(self:GetAngles():Up(), 90) + + cam.Start3D2D(DrawPos, DrawAngles, 0.4) + + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(0, 0, 558, 290) + + draw.RoundedBox(4, 0, 0, 558, 30, color_navy_200) + + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("laws_of_the_land"), "Roboto20", 279, 5, color_red, TEXT_ALIGN_CENTER) + + local lastHeight = 0 + for k, v in ipairs(Laws) do + draw.DrawNonParsedText(string.format("%u. %s", k, v), "Roboto20", 5, 35 + lastHeight, color_white) + lastHeight = lastHeight + (fn.ReverseArgs(string.gsub(v, "\n", "")) + 1) * 21 + end + + cam.End3D2D() +end + +local function addLaw(inLaw) + local law = DarkRP.textWrap(inLaw, "Roboto20", 522) + + local lawNumber = table.insert(Laws, law) + hook.Run("addLaw", lawNumber, inLaw) +end + +local function umAddLaw(um) + local law = um:ReadString() + timer.Simple(0, fn.Curry(addLaw, 2)(law)) +end +usermessage.Hook("DRP_AddLaw", umAddLaw) + +local function umRemoveLaw(um) + local i = um:ReadShort() + + local removed = table.remove(Laws, i) + hook.Run("removeLaw", i, removed) +end +usermessage.Hook("DRP_RemoveLaw", umRemoveLaw) + +local function umResetLaws(um) + Laws = {} + fn.Foldl(function(val,v) addLaw(v) end, nil, GAMEMODE.Config.DefaultLaws) + hook.Run("resetLaws") +end +usermessage.Hook("DRP_ResetLaws", umResetLaws) + +function DarkRP.getLaws() + return Laws +end + +timer.Simple(0, function() + fn.Foldl(function(val,v) addLaw(v) end, nil, GAMEMODE.Config.DefaultLaws) +end) diff --git a/gamemodes/darkrp/entities/entities/darkrp_laws/init.lua b/gamemodes/darkrp/entities/entities/darkrp_laws/init.lua new file mode 100644 index 0000000..01c4992 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_laws/init.lua @@ -0,0 +1,193 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +local Laws = {} +local FixedLaws = {} + +timer.Simple(0, function() + Laws = table.Copy(GAMEMODE.Config.DefaultLaws) + FixedLaws = table.Copy(Laws) +end) + +local hookCanEditLaws = {canEditLaws = function(_, ply, action, args) + if IsValid(ply) and (not RPExtraTeams[ply:Team()] or not RPExtraTeams[ply:Team()].mayor) then + return false, DarkRP.getPhrase("incorrect_job", GAMEMODE.Config.chatCommandPrefix .. action) + end + return true +end} + +function ENT:Initialize() + self:SetModel("models/props/cs_assault/Billboard.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:EnableMotion(false) + end +end + +local function addLaw(ply, args) + local canEdit, message = hook.Call("canEditLaws", hookCanEditLaws, ply, "addLaw", args) + + if not canEdit then + DarkRP.notify(ply, 1, 4, message ~= nil and message or DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. "addLaw", "")) + return "" + end + + if not args or args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + if string.len(args) < 3 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("law_too_short")) + return "" + end + + if #Laws >= 12 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("laws_full")) + return "" + end + + local num = table.insert(Laws, args) + + umsg.Start("DRP_AddLaw") + umsg.String(args) + umsg.End() + + hook.Run("addLaw", num, args, ply) + + DarkRP.notify(ply, 0, 2, DarkRP.getPhrase("law_added")) + + return "" +end +DarkRP.defineChatCommand("addLaw", addLaw) + +local function removeLaw(ply, args) + local canEdit, message = hook.Call("canEditLaws", hookCanEditLaws, ply, "removeLaw", args) + + if not canEdit then + DarkRP.notify(ply, 1, 4, message ~= nil and message or DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. "removeLaw", "")) + return "" + end + + local i = DarkRP.toInt(args) + + if not i or not Laws[i] then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + if FixedLaws[i] then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("default_law_change_denied")) + return "" + end + + local law = Laws[i] + + table.remove(Laws, i) + + umsg.Start("DRP_RemoveLaw") + umsg.Short(i) + umsg.End() + + hook.Run("removeLaw", i, law, ply) + + DarkRP.notify(ply, 0, 2, DarkRP.getPhrase("law_removed")) + + return "" +end +DarkRP.defineChatCommand("removeLaw", removeLaw) + +function DarkRP.resetLaws() + Laws = table.Copy(FixedLaws) + + umsg.Start("DRP_ResetLaws") + umsg.End() +end + +local function resetLaws(ply, args) + local canEdit, message = hook.Call("canEditLaws", hookCanEditLaws, ply, "resetLaws", args) + + if not canEdit then + DarkRP.notify(ply, 1, 4, message ~= nil and message or DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. "resetLaws", "")) + return "" + end + + hook.Run("resetLaws", ply) + + DarkRP.resetLaws() + + DarkRP.notify(ply, 0, 2, DarkRP.getPhrase("law_reset")) + + return "" +end +DarkRP.defineChatCommand("resetLaws", resetLaws) + +local numlaws = 0 +local function placeLaws(ply, args) + local canEdit, message = hook.Call("canEditLaws", hookCanEditLaws, ply, "placeLaws", args) + + if not canEdit then + DarkRP.notify(ply, 1, 4, message ~= nil and message or DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. "placeLaws", "")) + return "" + end + + if numlaws >= GAMEMODE.Config.maxlawboards then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", GAMEMODE.Config.chatCommandPrefix .. "placeLaws")) + return "" + end + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + local ent = ents.Create("darkrp_laws") + ent:SetPos(tr.HitPos + Vector(0, 0, 100)) + + local ang = ply:GetAngles() + ang:RotateAroundAxis(ang:Up(), 180) + ent:SetAngles(ang) + + ent:CPPISetOwner(ply) + ent.SID = ply.SID + + ent:Spawn() + ent:Activate() + + if IsValid(ent) then + numlaws = numlaws + 1 + end + + ply.lawboards = ply.lawboards or {} + table.insert(ply.lawboards, ent) + + return "" +end +DarkRP.defineChatCommand("placeLaws", placeLaws) + +function ENT:OnRemove() + numlaws = numlaws - 1 +end + +hook.Add("PlayerInitialSpawn", "SendLaws", function(ply) + for i, law in ipairs(Laws) do + if FixedLaws[i] then continue end + + umsg.Start("DRP_AddLaw", ply) + umsg.String(law) + umsg.End() + end +end) + +function DarkRP.getLaws() + return Laws +end diff --git a/gamemodes/darkrp/entities/entities/darkrp_laws/shared.lua b/gamemodes/darkrp/entities/entities/darkrp_laws/shared.lua new file mode 100644 index 0000000..3168cad --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_laws/shared.lua @@ -0,0 +1,161 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "DarkRP Laws" +ENT.Instructions = "Use /addlaws to add a custom law, /removelaw <num> to remove a law." +ENT.Author = "Drakehawke" + +ENT.Spawnable = false + +local plyMeta = FindMetaTable("Player") +DarkRP.declareChatCommand{ + command = "addlaw", + description = "Add a law to the laws board.", + delay = 1.5, + condition = plyMeta.isMayor +} + +DarkRP.declareChatCommand{ + command = "removelaw", + description = "Remove a law from the laws board.", + delay = 1.5, + condition = plyMeta.isMayor +} + +DarkRP.declareChatCommand{ + command = "placelaws", + description = "Place a laws board.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "resetlaws", + description = "Reset all laws.", + delay = 1.5 +} + +DarkRP.getLaws = DarkRP.stub{ + name = "getLaws", + description = "Get the table of all current laws.", + parameters = { + }, + returns = { + { + name = "laws", + description = "A table of all current laws.", + type = "table" + } + }, + metatable = DarkRP, + realm = "Shared" +} + +DarkRP.resetLaws = DarkRP.stub{ + name = "resetLaws", + description = "Reset to default laws.", + parameters = { + }, + returns = { + }, + metatable = DarkRP, + realm = "Server" +} + +DarkRP.hookStub{ + name = "addLaw", + description = "Called when a law is added.", + parameters = { + { + name = "index", + description = "Index of the law", + type = "number" + }, + { + name = "law", + description = "Law string", + type = "string" + }, + { + name = "player", + description = "The player who added the law", + type = "Player" + } + }, + returns = { + }, + realm = "Shared" +} + +DarkRP.hookStub{ + name = "removeLaw", + description = "Called when a law is removed.", + parameters = { + { + name = "index", + description = "Index of law", + type = "number" + }, + { + name = "law", + description = "Law string", + type = "string" + }, + { + name = "player", + description = "The player who removed the law", + type = "Player" + } + }, + returns = { + }, + realm = "Shared" +} + +DarkRP.hookStub{ + name = "resetLaws", + description = "Called when laws are reset.", + parameters = { + { + name = "player", + description = "The player resetting the laws.", + type = "Player" + } + }, + returns = { + }, + realm = "Shared" +} + +DarkRP.hookStub{ + name = "canEditLaws", + description = "Whether someone can edit laws.", + parameters = { + { + name = "player", + description = "The player trying to edit laws.", + type = "Player" + }, + { + name = "action", + description = "How the player is trying to edit laws.", + type = "string" + }, + { + name = "arguments", + description = "Arguments related to editing laws.", + type = "table" + } + }, + returns = { + { + name = "canEdit", + description = "A yes or no as to whether the player can edit the law.", + type = "boolean" + }, + { + name = "message", + description = "The message that is shown when they can't edit the law.", + type = "string" + } + }, + realm = "Server" +} diff --git a/gamemodes/darkrp/entities/entities/darkrp_tip_jar/cl_init.lua b/gamemodes/darkrp/entities/entities/darkrp_tip_jar/cl_init.lua new file mode 100644 index 0000000..2bb53cf --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_tip_jar/cl_init.lua @@ -0,0 +1,137 @@ +include("shared.lua") + +function ENT:Initialize() + self:initVars() + self:initVarsClient() +end + +function ENT:initVarsClient() + self.colorBackground = Color(140, 0, 0, 100) + self.colorText = color_white + self.donateAnimColor = Color(20, 100, 20) + + self.rotationSpeed = 130 + self.rotationOffset = 0 + self:InitCsModel() + + self.firstDonateAnimation = nil + self.lastDonateAnimation = nil + self.donateAnimSpeed = 0.3 +end + +function ENT:InitCsModel() + self.csModel = ClientsideModel(self.model) + self.csModel:SetPos(self:GetPos()) + self.csModel:SetParent(self) + self.csModel:SetModelScale(1.5, 0) + self.csModel:SetNoDraw(true) + self:CallOnRemove("csModel", fp{SafeRemoveEntity, self.csModel}) +end + +function ENT:Draw() + local Pos = self:GetPos() + local Ang = self:GetAngles() + local sysTime = SysTime() + local eyepos = EyePos() + local planeNormal = Ang:Up() + + local rotAng = Angle(Ang) + self.rotationOffset = sysTime % 360 * self.rotationSpeed + rotAng:RotateAroundAxis(planeNormal, self.rotationOffset) + + -- Something about cs models getting removed on their own... + if not IsValid(self.csModel) then + self:InitCsModel() + end + self.csModel:SetPos(Pos) + self.csModel:SetAngles(rotAng) + if not self:IsDormant() then + self.csModel:DrawModel() + end + + + local owner = self:Getowning_ent() + owner = (IsValid(owner) and owner:Nick()) or DarkRP.getPhrase("unknown") + local title = DarkRP.getPhrase("tip_jar") + + surface.SetFont("HUDNumber5") + local titleTextWidth, titleTextHeight = surface.GetTextSize(title) + local ownerTextWidth = surface.GetTextSize(owner) + + Ang:RotateAroundAxis(Ang:Forward(), 90) + + -- The text can be considered to be "standing" on a plane with normal = + -- Ang:Up(). The vector towards the player's EyePos is projected onto that + -- plane, normalised and rotated to have the text face the user. + local relativeEye = eyepos - Pos + local relativeEyeOnPlane = relativeEye - planeNormal * relativeEye:Dot(planeNormal) + local textAng = relativeEyeOnPlane:AngleEx(planeNormal) + + textAng:RotateAroundAxis(textAng:Up(), 90) + textAng:RotateAroundAxis(textAng:Forward(), 90) + + + cam.Start3D2D(Pos - Ang:Right() * 11.5 , textAng, 0.2) + draw.WordBox(2, -titleTextWidth * 0.5, -72 , title, "HUDNumber5", self.colorBackground, self.colorText) + draw.WordBox(2, -ownerTextWidth * 0.5, -72 + titleTextHeight + 4, owner, "HUDNumber5", self.colorBackground, self.colorText) + + self:DrawAnims(sysTime) + cam.End3D2D() +end + +function ENT:DrawAnims(sysTime) + local anim = self.firstDonateAnimation + + while anim do + if anim.progress > 1 then + anim = anim.nextDonateAnimation + self.firstDonateAnimation = anim + + continue + end + + draw.SimpleText( + anim.amount, + "DarkRP_tipjar", + -anim.textWidth / 2, + -100 - anim.progress * 200, + ColorAlpha(self.donateAnimColor, Lerp(anim.progress, 1024, 0)), + 0 + ) + + anim.progress = (sysTime - anim.start) * self.donateAnimSpeed + + anim = anim.nextDonateAnimation + end + + if not self.firstDonateAnimation then + self.lastDonateAnimation = nil + end +end + +function ENT:Donated(ply, amount) + local txtAmount = DarkRP.formatMoney(amount) + + surface.SetFont("DarkRP_tipjar") + + local anim = { + amount = txtAmount, + start = SysTime(), + textWidth = surface.GetTextSize(txtAmount), + progress = 0, + nextDonateAnimation = nil, + } + + if self.lastDonateAnimation then + self.lastDonateAnimation.nextDonateAnimation = anim + else + self.firstDonateAnimation = anim + end + + self.lastDonateAnimation = anim + + self:AddDonation(ply:Nick(), amount) +end + +-- Disable halos +function ENT:Think() end diff --git a/gamemodes/darkrp/entities/entities/darkrp_tip_jar/init.lua b/gamemodes/darkrp/entities/entities/darkrp_tip_jar/init.lua new file mode 100644 index 0000000..6a145dd --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_tip_jar/init.lua @@ -0,0 +1,46 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +function ENT:Initialize() + self:initVars() + + self:SetModel(self.model) + + self:SetModelScale(1.5, 0) + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + self:Activate() + + self.nodupe = true +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + self.damage = (self.damage or 100) - dmg:GetDamage() + if self.damage <= 0 then + self:Remove() + end +end + +function ENT:Use(activator, caller) + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + net.Start("DarkRP_TipJarUI") + net.WriteEntity(self) + net.Send(activator) +end diff --git a/gamemodes/darkrp/entities/entities/darkrp_tip_jar/shared.lua b/gamemodes/darkrp/entities/entities/darkrp_tip_jar/shared.lua new file mode 100644 index 0000000..567f614 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/darkrp_tip_jar/shared.lua @@ -0,0 +1,80 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Tip Jar" +ENT.Author = "FPtje" +ENT.Spawnable = false +ENT.IsTipjar = true + +function ENT:initVars() + self.model = "models/props_lab/jar01a.mdl" + self.damage = 100 + self.callOnRemoveId = "tipjar_activedonation_" .. self:EntIndex() .. "_" + + self.activeDonations = {} + self.madeDonations = {} + + self.PlayerUse = true +end + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 0, "owning_ent") +end + +function ENT:UpdateActiveDonation(ply, amount) + local old = self.activeDonations[ply] + self.activeDonations[ply] = amount + + self:PruneActiveDonations() + + ply:CallOnRemove(self.callOnRemoveId .. ply:UserID(), function() + if not IsValid(self) then return end + + self:ExitActiveDonation(ply) + end) + + hook.Call("tipjarUpdateActiveDonation", DarkRP.hooks, self, ply, amount, old) +end + +function ENT:ExitActiveDonation(ply) + local old = self.activeDonations[ply] + + self.activeDonations[ply] = nil + + self:PruneActiveDonations() + hook.Call("tipjarExitActiveDonation", DarkRP.hooks, self, ply, old) + + self:RemoveCallOnRemove(self.callOnRemoveId .. ply:UserID()) +end + +function ENT:ClearActiveDonations() + table.Empty(self.activeDonations) + hook.Call("tipjarClearActiveDonation", DarkRP.hooks, self) +end + +function ENT:PruneActiveDonations() + for ply, _ in pairs(self.activeDonations) do + if not IsValid(ply) then self.activeDonations[ply] = nil end + end +end + +function ENT:AddDonation(name, amount) + local lastDonation = self.madeDonations[#self.madeDonations] + + if lastDonation and lastDonation.name == name then + lastDonation.amount = lastDonation.amount + amount + else + table.insert(self.madeDonations, { + name = name, + amount = amount, + }) + end + + -- Enforce maximum of 100 donations + while #self.madeDonations > 100 do + table.remove(self.madeDonations, 1) + end +end + +function ENT:ClearDonations() + table.Empty(self.madeDonations) +end diff --git a/gamemodes/darkrp/entities/entities/drug/cl_init.lua b/gamemodes/darkrp/entities/entities/drug/cl_init.lua new file mode 100644 index 0000000..f271e16 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/drug/cl_init.lua @@ -0,0 +1,53 @@ +include("shared.lua") + +function ENT:Initialize() +end + +local color_red = Color(140, 0, 0, 100) +local color_white = color_white + +function ENT:Draw() + self:DrawModel() + + local Pos = self:GetPos() + local Ang = self:GetAngles() + + local owner = self:Getowning_ent() + owner = (IsValid(owner) and owner:Nick()) or DarkRP.getPhrase("unknown") + + surface.SetFont("HUDNumber5") + local text = DarkRP.getPhrase("drugs") + local text2 = DarkRP.getPhrase("priceTag", DarkRP.formatMoney(self:Getprice()), "") + local TextWidth = surface.GetTextSize(text) + local TextWidth2 = surface.GetTextSize(text2) + + Ang:RotateAroundAxis(Ang:Forward(), 90) + local TextAng = Ang + + TextAng:RotateAroundAxis(TextAng:Right(), CurTime() * -180) + + cam.Start3D2D(Pos + Ang:Right() * -15, TextAng, 0.1) + draw.WordBox(2, -TextWidth * 0.5 + 5, -30, text, "HUDNumber5", color_red, color_white) + draw.WordBox(2, -TextWidth2 * 0.5 + 5, 18, text2, "HUDNumber5", color_red, color_white) + cam.End3D2D() +end + +function ENT:Think() +end + +local function drugEffects(um) + local toggle = um:ReadBool() + + LocalPlayer().isDrugged = toggle + + if toggle then + hook.Add("RenderScreenspaceEffects", "drugged", function() + DrawSharpen(-1, 2) + DrawMaterialOverlay("models/props_lab/Tank_Glass001", 0) + DrawMotionBlur(0.13, 1, 0.00) + end) + else + hook.Remove("RenderScreenspaceEffects", "drugged") + end +end +usermessage.Hook("DrugEffects", drugEffects) diff --git a/gamemodes/darkrp/entities/entities/drug/init.lua b/gamemodes/darkrp/entities/entities/drug/init.lua new file mode 100644 index 0000000..ba5a9f8 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/drug/init.lua @@ -0,0 +1,105 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +local function UnDrugPlayer(ply) + if not IsValid(ply) then return end + ply.isDrugged = false + local IDSteam = ply:SteamID64() + + timer.Remove(IDSteam .. "DruggedHealth") + + SendUserMessage("DrugEffects", ply, false) +end + +hook.Add("PlayerDeath", "UndrugPlayers", function(ply) if ply.isDrugged then UnDrugPlayer(ply) end end) + +local function DrugPlayer(ply) + if not IsValid(ply) then return end + + SendUserMessage("DrugEffects", ply, true) + + ply.isDrugged = true + + local IDSteam = ply:SteamID64() + + if not timer.Exists(IDSteam .. "DruggedHealth") then + ply:SetHealth(ply:Health() + 100) + + timer.Create(IDSteam .. "DruggedHealth", 60 / (100 + 5), 100 + 5, function() + if not IsValid(ply) then return end + ply:SetHealth(ply:Health() - 1) + + if ply:Health() <= 0 then + ply:Kill() + end + + if timer.RepsLeft(IDSteam .. "DruggedHealth") == 0 then + UnDrugPlayer(ply) + end + end) + end +end + +function ENT:Initialize() + self:SetModel("models/props_lab/jar01a.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self.CanUse = true + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + self.damage = 10 + self:Setprice(self:Getprice() or 100) + self.SeizeReward = GAMEMODE.Config.pricemin or 35 +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + self.damage = self.damage - dmg:GetDamage() + + if self.damage <= 0 then + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetMagnitude(2) + effectdata:SetScale(2) + effectdata:SetRadius(3) + util.Effect("Sparks", effectdata) + self:Remove() + end +end + +function ENT:Use(activator, caller) + if not self.CanUse then return end + local Owner = self:Getowning_ent() + if not IsValid(Owner) then return end + + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + if activator ~= Owner then + if not activator:canAfford(self:Getprice()) then return end + DarkRP.payPlayer(activator, Owner, self:Getprice()) + DarkRP.notify(activator, 0, 4, DarkRP.getPhrase("you_bought", DarkRP.getPhrase("drugs"), DarkRP.formatMoney(self:Getprice()), "")) + DarkRP.notify(Owner, 0, 4, DarkRP.getPhrase("you_received_x", DarkRP.formatMoney(self:Getprice()), DarkRP.getPhrase("drugs"))) + end + DrugPlayer(caller) + self.CanUse = false + self:Remove() +end + +function ENT:OnRemove() + local ply = self:Getowning_ent() + if not IsValid(ply) then return end + ply.maxDrugs = ply.maxDrugs - 1 +end diff --git a/gamemodes/darkrp/entities/entities/drug/shared.lua b/gamemodes/darkrp/entities/entities/drug/shared.lua new file mode 100644 index 0000000..d6db2d0 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/drug/shared.lua @@ -0,0 +1,23 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Drugs" +ENT.Author = "Rickster" +ENT.Spawnable = false + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "price") + self:NetworkVar("Entity", 1, "owning_ent") +end + +hook.Add("Move", "DruggedPlayer", function(ply, mv) + if not ply.isDrugged then return end + + mv:SetMaxSpeed(mv:GetMaxSpeed() * 2) + mv:SetMaxClientSpeed(mv:GetMaxClientSpeed() * 2) + + if ply:IsOnGround() and mv:KeyPressed(IN_JUMP) then + local vec = mv:GetVelocity() + vec.z = 100 -- Adds on to the jump power + mv:SetVelocity(vec) + end +end) diff --git a/gamemodes/darkrp/entities/entities/drug_lab/init.lua b/gamemodes/darkrp/entities/entities/drug_lab/init.lua new file mode 100644 index 0000000..8c6c50d --- /dev/null +++ b/gamemodes/darkrp/entities/entities/drug_lab/init.lua @@ -0,0 +1,40 @@ +AddCSLuaFile("shared.lua") + +include("shared.lua") + +DEFINE_BASECLASS("lab_base") + +ENT.SeizeReward = 350 +ENT.SpawnOffset = Vector(0, 0, 35) + +function ENT:Initialize() + BaseClass.Initialize(self) + self.SID = self:Getowning_ent().SID +end + +function ENT:SalePrice(activator) + return math.random(math.Round(self:Getprice() / 8), math.Round(self:Getprice() / 4)) +end + +function ENT:canUse(activator) + if activator.maxDrugs and activator.maxDrugs >= GAMEMODE.Config.maxdrugs then + DarkRP.notify(activator, 1, 3, DarkRP.getPhrase("limit", self.itemPhrase)) + return false + end + return true +end + +function ENT:createItem(activator) + local drugPos = self:GetPos() + self.SpawnOffset + local drug = ents.Create("drug") + drug:SetPos(drugPos) + drug:Setowning_ent(activator) + drug.SID = activator.SID + drug.nodupe = true + drug:Setprice(self:Getprice() or self.initialPrice) + drug:Spawn() + if not activator.maxDrugs then + activator.maxDrugs = 0 + end + activator.maxDrugs = activator.maxDrugs + 1 +end diff --git a/gamemodes/darkrp/entities/entities/drug_lab/shared.lua b/gamemodes/darkrp/entities/entities/drug_lab/shared.lua new file mode 100644 index 0000000..53ae48b --- /dev/null +++ b/gamemodes/darkrp/entities/entities/drug_lab/shared.lua @@ -0,0 +1,11 @@ +ENT.Base = "lab_base" +ENT.PrintName = "Drug Lab" + +function ENT:initVars() + self.model = "models/props_lab/crematorcase.mdl" + self.initialPrice = GAMEMODE.Config.druglabdrugcost + self.labPhrase = DarkRP.getPhrase("drug_lab") + self.itemPhrase = DarkRP.getPhrase("drugs") + self.noIncome = true + self.camMul = -39 +end diff --git a/gamemodes/darkrp/entities/entities/fadmin_jail/init.lua b/gamemodes/darkrp/entities/entities/fadmin_jail/init.lua new file mode 100644 index 0000000..5de9129 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/fadmin_jail/init.lua @@ -0,0 +1,52 @@ +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:EnableMotion(false) + end + + self.SolidPos = self:GetPos() + self.SolidAng = self:GetAngles() +end + +function ENT:SetCanRemove(bool) + self.CanRemove = bool +end + +function ENT:OnRemove() + if FAdmin.shuttingDown or self.CanRemove or not IsValid(self.target) then return end + + local Replace = ents.Create("fadmin_jail") + if (not Replace:IsValid()) then return end + + Replace:SetPos(self.SolidPos) + Replace:SetAngles(self.SolidAng) + Replace:SetModel(self:GetModel()) + Replace:Spawn() + Replace:Activate() + + Replace.target = self.target + Replace.targetPos = self.targetPos + + self.target.FAdminJailProps = self.target.FAdminJailProps or {} + self.target.FAdminJailProps[self] = nil + self.target.FAdminJailProps[Replace] = true + + if self.targetPos then self.target:SetPos(self.targetPos) end -- Back in jail you! :V +end + +function ENT:Think() + if not IsValid(self.target) then + self:SetCanRemove(true) + self:Remove() + return + end +end diff --git a/gamemodes/darkrp/entities/entities/fadmin_jail/shared.lua b/gamemodes/darkrp/entities/entities/fadmin_jail/shared.lua new file mode 100644 index 0000000..0a29559 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/fadmin_jail/shared.lua @@ -0,0 +1,13 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "fadmin_jail" +ENT.Author = "FPtje" +ENT.Spawnable = false + +function ENT:CanTool() + return false +end + +function ENT:PhysgunPickup(ply) + return false +end diff --git a/gamemodes/darkrp/entities/entities/fadmin_motd/cl_init.lua b/gamemodes/darkrp/entities/entities/fadmin_motd/cl_init.lua new file mode 100644 index 0000000..fdf5ccf --- /dev/null +++ b/gamemodes/darkrp/entities/entities/fadmin_motd/cl_init.lua @@ -0,0 +1,182 @@ +include("shared.lua") +local defaultHTML + +-- I love the garry's mod wiki! +-- Credits to whoever made this function! +local function WorldToScreen(vWorldPos, vPos, vScale, aRot) + vWorldPos = vWorldPos - vPos + vWorldPos:Rotate(Angle(0, -aRot.y, 0)) + vWorldPos:Rotate(Angle(-aRot.p, 0, 0)) + vWorldPos:Rotate(Angle(0, 0, -aRot.r)) + + return vWorldPos.x / vScale, (-vWorldPos.y) / vScale +end + +function ENT:LoadPage() + local Page = self.MOTDPage:GetString() + if string.lower(Page) == "data/fadmin/motd.txt" or string.lower(Page) == "default" then + self.HTML:SetHTML(defaultHTML) + elseif string.lower(string.sub(Page, -4)) == ".txt" and string.lower(string.sub(Page, 1, 5)) == "data/" then -- If it's a text file somewhere in data... + Page = string.sub(Page, 6) + self.HTML:SetHTML(file.Read(Page, "DATA") or "") + else + self.HTML:OpenURL(Page) + end +end + +function ENT:Initialize() + self.MOTDPage = GetConVar("_FAdmin_MOTDPage") + self.Disabled = true + self.LastDrawn = CurTime() + self.HTML = self.HTMLControl or vgui.Create("HTML") + self.HTML:SetPaintedManually(false) + self.HTML:SetPos(-512, -256) + self.HTMLWidth = 1448 + self.HTMLHeight = 724 + self.HTML:SetSize(self.HTMLWidth, self.HTMLHeight) + self:LoadPage() + + self.HTML:SetVisible(false) + self.HTML:SetKeyboardInputEnabled(false) + timer.Simple(0, function() -- Fix areas of the FAdmin scoreboard coming unclickable + self.HTML:SetPaintedManually(true) + end) +end + +function ENT:Think() + if not self.HTML or self.Disabled or self.HTMLCloseButton then + self.HTMLMat = nil + else + self.HTML:UpdateHTMLTexture() + self.HTMLMat = self.HTML:GetHTMLMaterial() + end + self:NextThink(CurTime() + 0.1) +end + +local gripTexture = surface.GetTextureID("sprites/grip") +local ArrowTexture = surface.GetTextureID("gui/arrow") +local color_white = color_white +local color_darkgrey = Color(100, 100, 100, 255) + +function ENT:Draw() + self:DrawModel() + + local pos = self:GetPos() + local ply = LocalPlayer() + if pos:DistToSqr(ply:GetShootPos()) > 90000 then return end + + if CurTime() - self.LastDrawn > 0.5 then + self.Disabled = true --Disable it again when you stop looking at it + end + + self.LastDrawn = CurTime() + local IsAdmin = ply:IsAdmin() + local HasPhysgun = ply:GetActiveWeapon():IsValid() and ply:GetActiveWeapon():GetClass() == "weapon_physgun" + local isUsing = (HasPhysgun and ply:KeyDown(IN_ATTACK)) or ply:KeyDown(IN_USE) + + surface.SetFont("Roboto20") + local TextPosX = surface.GetTextSize("Physgun/use the button to see the MOTD!") * (-0.5) + + local ang = self:GetAngles() + ang:RotateAroundAxis(ang:Right(), -90) + ang:RotateAroundAxis(ang:Up(), 90) + + local posX, posY = WorldToScreen(ply:GetEyeTrace().HitPos, self:GetPos() + ang:Up() * 3, 0.25, ang) + render.SuppressEngineLighting(true) + cam.Start3D2D(self:GetPos() + ang:Up() * 3, ang, 0.25) + + if self.Disabled then + surface.SetDrawColor(0, 0, 0, 255) + surface.DrawRect(-512, 256, 1024, -512) + surface.SetTextColor(255, 255, 255, 255) + surface.SetTextPos(TextPosX, 0) + surface.DrawNonParsedText("Physgun/use the button to see the MOTD!") + + draw.WordBox(4, -16, 24, "Click!", "default", color_darkgrey, color_white) + + surface.SetDrawColor(255, 255, 255, 255) + if IsAdmin and HasPhysgun then + surface.SetTexture(gripTexture) + surface.DrawTexturedRect(-10, 240, 16, 16) + end + if isUsing then + + posX, posY = math.Clamp(posX, -506, 506), math.Clamp(posY, -250, 250) + surface.SetTexture(ArrowTexture) + surface.DrawTexturedRectRotated(posX + 5, posY + 5, 16, 16, 45) + + -- Clicking button + if posX > -16 and posX < 16 and posY > 24 and posY < 48 then + self:LoadPage() + self.Disabled = false + self.CanClickAgain = CurTime() + 1 + end + end + elseif not self.HTMLMat then + self.HTML:SetVisible(true) + self.HTML:SetKeyboardInputEnabled(true) + self.HTML:SetPaintedManually(false) + self.HTML:UpdateHTMLTexture() + + timer.Simple(0, function() -- Fix HTML material + self.HTML:SetPaintedManually(true) + self.HTML:SetVisible(false) + self.HTML:SetKeyboardInputEnabled(false) + end) + + else + surface.SetMaterial(self.HTMLMat) + surface.SetDrawColor(255, 255, 255, 255) + surface.DrawTexturedRect(-512, -256, self.HTMLWidth, self.HTMLHeight) + end + + cam.End3D2D() + render.SuppressEngineLighting(false) + if self.HTMLCloseButton then return end + + --Drawing the actual HTML panel: + + if isUsing and posX > -500 and posX < 500 and posY < 250 and posY > -250 and + not self.Disabled and self.HTML and self.HTML:IsValid() and self.CanClickAgain and CurTime() > self.CanClickAgain then + self.CanClickAgain = CurTime() + 1 + self.HTML:SetPaintedManually(false) + self.HTML:SetPos(0, 100) + self.HTML:SetSize(ScrW(), ScrH() - 100) + gui.EnableScreenClicker(true) + -- gui.SetMousePos(posX/1024*ScrW(), posY/512*(ScrH() - 100) + 100) + self.HTMLCloseButton = self.HTMLCloseButton or vgui.Create("DButton") + self.HTMLCloseButton:SetPos(ScrW() - 100, 0) + self.HTMLCloseButton:SetSize(100, 100) + self.HTMLCloseButton:SetText("X") + self.HTMLCloseButton:SetVisible(true) + self.HTML:SetVisible(true) + self.HTML:RequestFocus() + self.HTML:SetKeyboardInputEnabled(true) + self.HTML:MakePopup() + + function self.HTMLCloseButton.DoClick() -- Revert to drawing on the prop + self.HTML:SetPos(-512, -256) + self.HTML:SetSize(self.HTMLWidth, self.HTMLHeight) + self.HTML:SetPaintedManually(true) + self.HTML:SetKeyboardInputEnabled(false) + self.HTML:SetVisible(false) + gui.EnableScreenClicker(false) + self.HTMLCloseButton:Remove() + self.HTMLCloseButton = nil + end + end +end + +defaultHTML = [[ +<html> +<title>MOTD! + +

Example MOTD/Instructions on how to set a proper MOTD

+

Of course you have to be superadmin or owner.

+
    +
  1. Copy the website URL to the clipboard
  2. +
  3. Enter the command: FAdmin MOTDPage "your website here"

  4. +Example:
    +FAdmin MOTDPage "www.facepunch.com" + +]] diff --git a/gamemodes/darkrp/entities/entities/fadmin_motd/init.lua b/gamemodes/darkrp/entities/entities/fadmin_motd/init.lua new file mode 100644 index 0000000..4906de1 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/fadmin_motd/init.lua @@ -0,0 +1,54 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/props_wasteland/interior_fence002d.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:EnableMotion(false) + end + + self.SolidPos = self:GetPos() + self.SolidAng = self:GetAngles() + self:SetMaterial("models/props_lab/warp_sheet") +end + +function ENT:OnRemove() + if not self.CanRemove and IsValid(self.target) then + local Replace = ents.Create("fadmin_motd") + + Replace:SetPos(self.SolidPos) + Replace:SetAngles(self.SolidAng) + Replace:Spawn() + Replace:SetModel(self:GetModel()) + end +end + +function ENT:OnPhysgunFreeze(Weapon, PhysObj, ent, ply) + FAdmin.MOTD.SaveMOTD(ent, ply) +end + +function ENT:SpawnFunction(ply, tr) + if not tr.Hit then return end + for _, v in ipairs(ents.FindByClass("fadmin_motd")) do + v.CanRemove = true + v:Remove() --There can only be one motd per level + end + + local SpawnPos = tr.HitPos + tr.HitNormal * 16 + Vector(0,0,50) + + local ent = ents.Create("fadmin_motd") + ent:SetPos(SpawnPos) + local Ang = ply:EyeAngles() + ent:SetAngles(Angle(0, Ang.y-180, Ang.r)) + + ent:Spawn() + ent:Activate() +end diff --git a/gamemodes/darkrp/entities/entities/fadmin_motd/shared.lua b/gamemodes/darkrp/entities/entities/fadmin_motd/shared.lua new file mode 100644 index 0000000..2b6a265 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/fadmin_motd/shared.lua @@ -0,0 +1,21 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "fadmin MOTD" +ENT.Information = "Place this MOTD somewhere, freeze it and it will be saved automatically" +ENT.Author = "FPtje" +ENT.Spawnable = false + +function ENT:CanTool(ply, trace, tool) + if ply:IsAdmin() and tool == "remover" then + self.CanRemove = true + if SERVER then FAdmin.MOTD.RemoveMOTD(self, ply) end + return true + end + return false +end + +local PickupPos = Vector(1.8079, -0.6743, -62.3193) +function ENT:PhysgunPickup(ply) + if ply:IsAdmin() and PickupPos:DistToSqr(self:WorldToLocal(ply:GetEyeTrace().HitPos)) < 49 then return true end + return false +end diff --git a/gamemodes/darkrp/entities/entities/food/init.lua b/gamemodes/darkrp/entities/entities/food/init.lua new file mode 100644 index 0000000..5d043d0 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/food/init.lua @@ -0,0 +1,54 @@ +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/props_junk/garbage_takeoutcarton001a.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + self.damage = 10 +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + self.damage = self.damage - dmg:GetDamage() + + if (self.damage <= 0) then + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetMagnitude(2) + effectdata:SetScale(2) + effectdata:SetRadius(3) + util.Effect("Sparks", effectdata) + self:Remove() + end +end + +function ENT:Use(activator, caller) + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + caller:setSelfDarkRPVar("Energy", 100) + umsg.Start("AteFoodIcon", caller) + umsg.End() + + self:Remove() + activator:EmitSound(self.EatSound, 100, 100) +end + +function ENT:OnRemove() + local ply = self:Getowning_ent() + ply.maxFoods = ply.maxFoods and ply.maxFoods - 1 or 0 +end diff --git a/gamemodes/darkrp/entities/entities/food/shared.lua b/gamemodes/darkrp/entities/entities/food/shared.lua new file mode 100644 index 0000000..d2bfc18 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/food/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Food" +ENT.Author = "Pcwizdan" +ENT.Spawnable = false +ENT.EatSound = "vo/sandwicheat09.mp3" -- Requires Team Fortress 2 + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 1, "owning_ent") +end diff --git a/gamemodes/darkrp/entities/entities/gunlab/init.lua b/gamemodes/darkrp/entities/entities/gunlab/init.lua new file mode 100644 index 0000000..0922eae --- /dev/null +++ b/gamemodes/darkrp/entities/entities/gunlab/init.lua @@ -0,0 +1,17 @@ +AddCSLuaFile("shared.lua") + +include("shared.lua") + +ENT.SpawnOffset = Vector(0, 0, 27) + +function ENT:createItem() + local gun = ents.Create("spawned_weapon") + + local wep = weapons.Get(GAMEMODE.Config.gunlabweapon) + gun:SetModel(wep and wep.WorldModel or "models/weapons/w_pist_p228.mdl") + gun:SetWeaponClass(GAMEMODE.Config.gunlabweapon) + local gunPos = self:GetPos() + self.SpawnOffset + gun:SetPos(gunPos) + gun.nodupe = true + gun:Spawn() +end diff --git a/gamemodes/darkrp/entities/entities/gunlab/shared.lua b/gamemodes/darkrp/entities/entities/gunlab/shared.lua new file mode 100644 index 0000000..de3fbbd --- /dev/null +++ b/gamemodes/darkrp/entities/entities/gunlab/shared.lua @@ -0,0 +1,9 @@ +ENT.Base = "lab_base" +ENT.PrintName = "Gun Lab" + +function ENT:initVars() + self.model = "models/props_c17/TrapPropeller_Engine.mdl" + self.initialPrice = GAMEMODE.Config.gunlabguncost + self.labPhrase = DarkRP.getPhrase("gun_lab") + self.itemPhrase = DarkRP.getPhrase("gun") +end diff --git a/gamemodes/darkrp/entities/entities/lab_base/cl_init.lua b/gamemodes/darkrp/entities/entities/lab_base/cl_init.lua new file mode 100644 index 0000000..e501e1c --- /dev/null +++ b/gamemodes/darkrp/entities/entities/lab_base/cl_init.lua @@ -0,0 +1,34 @@ +include("shared.lua") + +function ENT:Initialize() + self:initVars() +end + +local color_red = Color(140, 0, 0, 100) +local color_white = color_white + +function ENT:DrawTranslucent() + self:DrawModel() + + local Pos = self:GetPos() + local Ang = self:GetAngles() + + local owner = self:Getowning_ent() + owner = (IsValid(owner) and owner:Nick()) or DarkRP.getPhrase("unknown") + + surface.SetFont("HUDNumber5") + local text = self.labPhrase + local text2 = DarkRP.getPhrase("priceTag", DarkRP.formatMoney(self:Getprice()), "") + local TextWidth = surface.GetTextSize(text) + local TextWidth2 = surface.GetTextSize(text2) + + Ang:RotateAroundAxis(Ang:Forward(), 90) + local TextAng = Ang + + TextAng:RotateAroundAxis(TextAng:Right(), CurTime() * -180) + + cam.Start3D2D(Pos + Ang:Right() * self.camMul, TextAng, 0.2) + draw.WordBox(2, -TextWidth * 0.5 + 5, -30, text, "HUDNumber5", color_red, color_white) + draw.WordBox(2, -TextWidth2 * 0.5 + 5, 18, text2, "HUDNumber5", color_red, color_white) + cam.End3D2D() +end diff --git a/gamemodes/darkrp/entities/entities/lab_base/init.lua b/gamemodes/darkrp/entities/entities/lab_base/init.lua new file mode 100644 index 0000000..e09f53b --- /dev/null +++ b/gamemodes/darkrp/entities/entities/lab_base/init.lua @@ -0,0 +1,149 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:initVars() + + self:SetModel(self.model) + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + self.sparking = false + self.damage = 100 + self:Setprice(math.Clamp(self.initialPrice, (GAMEMODE.Config.pricemin ~= 0 and GAMEMODE.Config.pricemin) or self.initialPrice, (GAMEMODE.Config.pricecap ~= 0 and GAMEMODE.Config.pricecap) or self.initialPrice)) +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + self.damage = self.damage - dmg:GetDamage() + if self.damage <= 0 and not self.Destructed then + self.Destructed = true + self:Destruct() + self:Remove() + end +end + +function ENT:Destruct() + local vPoint = self:GetPos() + + util.BlastDamage(self, self, vPoint, self.blastRadius, self.blastDamage) + util.ScreenShake(vPoint, 512, 255, 1.5, 200) + + local effectdata = EffectData() + effectdata:SetStart(vPoint) + effectdata:SetOrigin(vPoint) + effectdata:SetScale(1) + util.Effect(self:WaterLevel() > 1 and "WaterSurfaceExplosion" or "Explosion", effectdata) + util.Decal("Scorch", vPoint, vPoint - Vector(0, 0, 25), self) +end + +function ENT:SalePrice(activator) + local owner = self:Getowning_ent() + + if activator == owner then + if self.allowed and istable(self.allowed) and table.HasValue(self.allowed, activator:Team()) then + return math.ceil(self:Getprice() * 0.8) + else + return math.ceil(self:Getprice() * 0.9) + end + else + return self:Getprice() + end +end + +ENT.Once = false +function ENT:Use(activator, caller) + -- The lab cannot be used by non-players (e.g. wire user) + -- The player must be known for the lab to work. + if not activator:IsPlayer() then return end + + if self.Once then return end + + local owner = self:Getowning_ent() + + if not IsValid(owner) then + DarkRP.notify(activator, 1, 3, DarkRP.getPhrase("disabled", self.labPhrase, DarkRP.getPhrase("disconnected_player"))) + return + end + + local cost = self:SalePrice(activator) + + if not activator:canAfford(cost) then + DarkRP.notify(activator, 1, 3, DarkRP.getPhrase("cant_afford", self.itemPhrase)) + return + end + + local diff = cost - self:SalePrice(owner) + if not self.noIncome and diff < 0 and not owner:canAfford(math.abs(diff)) then + DarkRP.notify(activator, 1, 3, DarkRP.getPhrase("owner_poor", self.labPhrase)) + return + end + + if not self:canUse(activator) then return end + + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + self.Once = true + self.sparking = true + + activator:addMoney(-cost) + DarkRP.notify(activator, 0, 3, DarkRP.getPhrase("you_bought", self.itemPhrase, DarkRP.formatMoney(cost))) + + if activator ~= owner and not self.noIncome then + if diff == 0 then + DarkRP.notify(owner, 0, 3, DarkRP.getPhrase("you_received_x", DarkRP.formatMoney(0) .. " " .. DarkRP.getPhrase("profit"), self.itemPhrase)) + else + owner:addMoney(diff) + local word = DarkRP.getPhrase("profit") + if diff < 0 then word = DarkRP.getPhrase("loss") end + DarkRP.notify(owner, 0, 3, DarkRP.getPhrase("you_received_x", DarkRP.formatMoney(math.abs(diff)) .. " " .. word, self.itemPhrase)) + end + end + + timer.Create(self:EntIndex() .. self.itemPhrase, 1, 1, function() + if not IsValid(self) then return end + if IsValid(activator) then + self:createItem(activator) + end + self.Once = false + self.sparking = false + end) +end + +function ENT:canUse(owner, activator) + return true +end + +function ENT:createItem(activator) + -- Implement this function +end + +function ENT:Think() + if self.sparking then + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetMagnitude(1) + effectdata:SetScale(1) + effectdata:SetRadius(2) + util.Effect("Sparks", effectdata) + end +end + +function ENT:OnRemove() + timer.Remove(self:EntIndex() .. self.itemPhrase) +end diff --git a/gamemodes/darkrp/entities/entities/lab_base/shared.lua b/gamemodes/darkrp/entities/entities/lab_base/shared.lua new file mode 100644 index 0000000..f1c599b --- /dev/null +++ b/gamemodes/darkrp/entities/entities/lab_base/shared.lua @@ -0,0 +1,27 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Lab" +ENT.Author = "DarkRP Developers" +ENT.Spawnable = false +ENT.CanSetPrice = true + +-- These are variables that should be set in entities that base from this +ENT.model = "" +ENT.initialPrice = 0 +ENT.labPhrase = "" +ENT.itemPhrase = "" +ENT.noIncome = false +ENT.camMul = -30 +ENT.blastRadius = 200 +ENT.blastDamage = 200 + +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + +function ENT:initVars() + -- Implement this to set the above variables +end + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "price") + self:NetworkVar("Entity", 1, "owning_ent") +end diff --git a/gamemodes/darkrp/entities/entities/letter/cl_init.lua b/gamemodes/darkrp/entities/entities/letter/cl_init.lua new file mode 100644 index 0000000..4b9a3b0 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/letter/cl_init.lua @@ -0,0 +1,76 @@ +include("shared.lua") + +local frame +local SignButton + +function ENT:Draw() + self:DrawModel() +end + +local function KillLetter(msg) + hook.Remove("HUDPaint", "ShowLetter") + frame:Remove() +end +usermessage.Hook("KillLetter", KillLetter) + +local function ShowLetter(msg) + if frame then + frame:Remove() + end + + local LetterMsg = "" + local Letter = msg:ReadEntity() + local LetterType = msg:ReadShort() + local LetterPos = msg:ReadVector() + local sectionCount = msg:ReadShort() + local LetterY = ScrH() / 2 - 300 + local LetterAlpha = 255 + + Letter:CallOnRemove("Kill letter HUD on remove", KillLetter) + + for k = 1, sectionCount, 1 do + LetterMsg = LetterMsg .. msg:ReadString() + end + + frame = vgui.Create("DFrame") + frame:SetTitle("") + frame:ShowCloseButton(false) + + SignButton = vgui.Create("DButton", frame) + SignButton:SetText(DarkRP.getPhrase("sign_this_letter")) + frame:SetPos(ScrW() - 256, ScrH() - 256) + SignButton:SetSize(256, 256) + frame:SetSize(256, 256) + SignButton:SetSkin(GAMEMODE.Config.DarkRPSkin) + frame:SizeToContents() + frame:MakePopup() + frame:SetKeyboardInputEnabled(false) + + function SignButton:DoClick() + RunConsoleCommand("_DarkRP_SignLetter", Letter:EntIndex()) + SignButton:SetDisabled(true) + end + SignButton:SetDisabled(IsValid(Letter:Getsigned())) + + hook.Add("HUDPaint", "ShowLetter", function() + if not Letter.dt then KillLetter() return end + if LetterAlpha < 255 then + LetterAlpha = math.Clamp(LetterAlpha + 400 * FrameTime(), 0, 255) + end + + local font = (LetterType == 1 and "AckBarWriting") or "Default" + + draw.RoundedBox(2, ScrW() * .2, LetterY, ScrW() * .8 - (ScrW() * .2), ScrH(), Color(255, 255, 255, math.Clamp(LetterAlpha, 0, 200))) + draw.DrawNonParsedText(LetterMsg .. "\n\n\n" .. DarkRP.getPhrase("signed", IsValid(Letter:Getsigned()) and Letter:Getsigned():Nick() or DarkRP.getPhrase("no_one")), font, ScrW() * .25 + 20, LetterY + 80, Color(0, 0, 0, LetterAlpha), 0) + + if LocalPlayer():GetPos():DistToSqr(LetterPos) > 10000 then + LetterY = Lerp(0.1, LetterY, ScrH()) + LetterAlpha = Lerp(0.1, LetterAlpha, 0) + if frame and frame.Close then frame:Close() end + if math.Round(LetterAlpha) <= 10 then + KillLetter() + end + end + end) +end +usermessage.Hook("ShowLetter", ShowLetter) diff --git a/gamemodes/darkrp/entities/entities/letter/commands.lua b/gamemodes/darkrp/entities/entities/letter/commands.lua new file mode 100644 index 0000000..52ff967 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/letter/commands.lua @@ -0,0 +1,94 @@ +local function MakeLetter(ply, args, type) + if not GAMEMODE.Config.letters then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", "/write / /type", "")) + return "" + end + + if ply.maxletters and ply.maxletters >= GAMEMODE.Config.maxletters then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", "letter")) + return "" + end + + if CurTime() - ply:GetTable().LastLetterMade < 3 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(3 - (CurTime() - ply:GetTable().LastLetterMade)), "/write / /type")) + return "" + end + + ply:GetTable().LastLetterMade = CurTime() + + -- Instruct the player's letter window to open + + local ftext = string.gsub(args, "//", "\n") + ftext = string.gsub(ftext, "\\n", "\n") .. "\n\n" .. DarkRP.getPhrase("signed_yours") .. "\n" .. ply:Nick() + local length = string.len(ftext) + + local numParts = math.floor(length / 39) + 1 + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + local letter = ents.Create("letter") + letter:SetModel("models/props_c17/paper01.mdl") + letter:SetPos(tr.HitPos) + letter:Setowning_ent(ply) + letter.nodupe = true + letter:Spawn() + + letter:GetTable().Letter = true + letter.type = type + letter.numPts = numParts + + DarkRP.placeEntity(letter, tr, ply) + + local startpos = 1 + local endpos = 39 + letter.Parts = {} + + for k = 1, numParts, 1 do + table.insert(letter.Parts, string.sub(ftext, startpos, endpos)) + startpos = startpos + 39 + endpos = endpos + 39 + end + + letter.SID = ply.SID + + DarkRP.printMessageAll(2, DarkRP.getPhrase("created_x", ply:Nick(), "mail")) + if not ply.maxletters then + ply.maxletters = 0 + end + ply.maxletters = ply.maxletters + 1 + timer.Simple(600, function() if IsValid(letter) then letter:Remove() end end) +end + +local function WriteLetter(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + MakeLetter(ply, args, 1) + return "" +end +DarkRP.defineChatCommand("write", WriteLetter) + +local function TypeLetter(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + MakeLetter(ply, args, 2) + return "" +end +DarkRP.defineChatCommand("type", TypeLetter) + +local function RemoveLetters(ply) + for k, v in ipairs(ents.FindByClass("letter")) do + if v.SID == ply.SID then v:Remove() end + end + DarkRP.notify(ply, 4, 4, DarkRP.getPhrase("cleaned_up", "mails")) + return "" +end +DarkRP.defineChatCommand("removeletters", RemoveLetters) diff --git a/gamemodes/darkrp/entities/entities/letter/init.lua b/gamemodes/darkrp/entities/entities/letter/init.lua new file mode 100644 index 0000000..2c1b169 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/letter/init.lua @@ -0,0 +1,98 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") +include("commands.lua") + +function ENT:Initialize() + self:SetModel("models/props_c17/paper01.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + hook.Add("PlayerDisconnected", self, self.onPlayerDisconnected) +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + local typ = dmg:GetDamageType() + if bit.band(typ, bit.bor(DMG_FALL, DMG_VEHICLE, DMG_DROWN, DMG_RADIATION, DMG_PHYSGUN)) > 0 then return end + + self:Remove() +end + +function ENT:OnRemove() + local ply = self:Getowning_ent() + if not IsValid(ply) then return end + if not ply.maxletters then + ply.maxletters = 0 + end + ply.maxletters = ply.maxletters - 1 +end + +function ENT:Use(ply) + if not ply:KeyDown(IN_ATTACK) then + umsg.Start("ShowLetter", ply) + umsg.Entity(self) + umsg.Short(self.type) + umsg.Vector(self:GetPos()) + local numParts = self.numPts + umsg.Short(numParts) + for _, b in ipairs(self.Parts) do umsg.String(b) end + umsg.End() + else + umsg.Start("KillLetter", ply) + umsg.End() + end +end + +function ENT:SignLetter(ply) + self:Setsigned(ply) +end + +function ENT:onPlayerDisconnected(ply) + if self:Getowning_ent() == ply then + self:Remove() + end +end + +concommand.Add("_DarkRP_SignLetter", function(ply, cmd, args) + if not args[1] or ply:EntIndex() == 0 then return end + local letter = ents.GetByIndex(tonumber(args[1])) + if not IsValid(letter) or letter:GetClass() ~= "letter" then return end + letter:SignLetter(ply) +end) + +local function removeLetters(ply, args) + local target = DarkRP.findPlayer(args) + + if target then + for _, v in ipairs(ents.FindByClass("letter")) do + if v.SID == target.SID then v:Remove() end + end + else + -- Remove ALL letters + for _, v in ipairs(ents.FindByClass("letter")) do + v:Remove() + end + end + + if ply:EntIndex() == 0 then + DarkRP.log("Console force-removed all letters", Color(30, 30, 30)) + else + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-removed all letters", Color(30, 30, 30)) + end + + DarkRP.notify(ply, 0, 4, "All letters removed") +end +DarkRP.definePrivilegedChatCommand("removeletters", "DarkRP_AdminCommands", removeLetters) diff --git a/gamemodes/darkrp/entities/entities/letter/shared.lua b/gamemodes/darkrp/entities/entities/letter/shared.lua new file mode 100644 index 0000000..f0cedcd --- /dev/null +++ b/gamemodes/darkrp/entities/entities/letter/shared.lua @@ -0,0 +1,28 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "letter" +ENT.Author = "Pcwizdan" +ENT.Spawnable = false + +function ENT:SetupDataTables() + self:NetworkVar("Entity",1,"owning_ent") + self:NetworkVar("Entity",2,"signed") +end + +DarkRP.declareChatCommand{ + command = "write", + description = "Write a letter.", + delay = 5 +} + +DarkRP.declareChatCommand{ + command = "type", + description = "Type a letter.", + delay = 5 +} + +DarkRP.declareChatCommand{ + command = "removeletters", + description = "Remove all of your letters.", + delay = 5 +} diff --git a/gamemodes/darkrp/entities/entities/meteor/cl_init.lua b/gamemodes/darkrp/entities/entities/meteor/cl_init.lua new file mode 100644 index 0000000..132e410 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/meteor/cl_init.lua @@ -0,0 +1,36 @@ +ENT.Spawnable = false +ENT.AdminSpawnable = false + +include("shared.lua") + +language.Add("meteor", "meteor") + +function ENT:Initialize() + local mx, mn = self:GetRenderBounds() + self:SetRenderBounds(mn + Vector(0,0,128), mx, 0) + self.emitter = ParticleEmitter(LocalPlayer():GetShootPos()) +end + +local color_grey = Color(200, 200, 210) + +function ENT:Think() + local vOffset = self:LocalToWorld(VectorRand(-3, 3)) + VectorRand(-3, 3) + local vNormal = (vOffset - self:GetPos()):GetNormalized() + + if not self.emitter then return end + + local particle = self.emitter:Add(Model("particles/smokey"), vOffset) + particle:SetVelocity(vNormal * math.Rand(10, 30)) + particle:SetDieTime(2.0) + particle:SetStartAlpha(math.Rand(50, 150)) + particle:SetStartSize(math.Rand(8, 16)) + particle:SetEndSize(math.Rand(32, 64)) + particle:SetRoll(math.Rand(-0.2, 0.2)) + particle:SetColor(color_grey) +end + +function ENT:OnRemove() + if IsValid(self.emitter) then + self.emitter:Finish() + end +end diff --git a/gamemodes/darkrp/entities/entities/meteor/init.lua b/gamemodes/darkrp/entities/entities/meteor/init.lua new file mode 100644 index 0000000..67cbf75 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/meteor/init.lua @@ -0,0 +1,69 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel("models/props_junk/Rock001a.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:Ignite(20, 0) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + phys:EnableMotion(true) + phys:SetMass(1000) + phys:EnableGravity(false) +end + +function ENT:SetMeteorTarget(ent) + local foundSky = util.IsInWorld(ent:GetPos()) + local zPos = ent:GetPos().z + + for a = 1, 30, 1 do + zPos = zPos + 100 + foundSky = util.IsInWorld(Vector(ent:GetPos().x ,ent:GetPos().y ,zPos)) + if (foundSky == false) then + zPos = zPos - 120 + break + end + end + + self:SetPos(Vector(ent:GetPos().x + math.random(-4000,4000),ent:GetPos().y + math.random(-4000,4000), zPos)) + local speed = 100000000 + local VNormal = (Vector(ent:GetPos().x + math.random(-500,500),ent:GetPos().y + math.random(-500,500),ent:GetPos().z) - self:GetPos()):GetNormal() + self:GetPhysicsObject():ApplyForceCenter(VNormal * speed) +end + +function ENT:Destruct(notexplode) + if not notexplode then + util.BlastDamage(self, self, self:GetPos(), 200, 60) + end + + self:Extinguish() + local vPoint = self:GetPos() + local effectdata = EffectData() + effectdata:SetStart(vPoint) + effectdata:SetOrigin(vPoint) + effectdata:SetScale(1) + util.Effect("Explosion", effectdata) + -- You get warnings about changing collision rule when removing immediately + -- https://github.com/FPtje/DarkRP/issues/2832 + timer.Simple(0, fp{SafeRemoveEntity, self}) +end + +function ENT:OnTakeDamage(dmg) + if (dmg:GetDamage() > 5) then + self:Destruct(true) + end +end + +function ENT:PhysicsCollide(data, physobj) + if data.HitEntity:GetClass() == "func_breakable_surf" then self:Remove() return end + self:Destruct() +end diff --git a/gamemodes/darkrp/entities/entities/meteor/shared.lua b/gamemodes/darkrp/entities/entities/meteor/shared.lua new file mode 100644 index 0000000..97a6e24 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/meteor/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Meteor" +ENT.Author = "Rickster" +ENT.Contact = "Rickyman35@hotmail.com" +ENT.Spawnable = false diff --git a/gamemodes/darkrp/entities/entities/microwave/init.lua b/gamemodes/darkrp/entities/entities/microwave/init.lua new file mode 100644 index 0000000..348684a --- /dev/null +++ b/gamemodes/darkrp/entities/entities/microwave/init.lua @@ -0,0 +1,26 @@ +AddCSLuaFile("shared.lua") + +include("shared.lua") + +ENT.SpawnOffset = Vector(0, 0, 23) + +function ENT:canUse(activator) + if activator.maxFoods and activator.maxFoods >= GAMEMODE.Config.maxfoods then + DarkRP.notify(activator, 1, 3, DarkRP.getPhrase("limit", self.itemPhrase)) + return false + end + return true +end + +function ENT:createItem(activator) + local foodPos = self:GetPos() + self.SpawnOffset + local food = ents.Create("food") + food:SetPos(foodPos) + food:Setowning_ent(activator) + food.nodupe = true + food:Spawn() + if not activator.maxFoods then + activator.maxFoods = 0 + end + activator.maxFoods = activator.maxFoods + 1 +end diff --git a/gamemodes/darkrp/entities/entities/microwave/shared.lua b/gamemodes/darkrp/entities/entities/microwave/shared.lua new file mode 100644 index 0000000..4456570 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/microwave/shared.lua @@ -0,0 +1,9 @@ +ENT.Base = "lab_base" +ENT.PrintName = "Microwave" + +function ENT:initVars() + self.model = "models/props/cs_office/microwave.mdl" + self.initialPrice = GAMEMODE.Config.microwavefoodcost + self.labPhrase = DarkRP.getPhrase("microwave") + self.itemPhrase = DarkRP.getPhrase("food") +end diff --git a/gamemodes/darkrp/entities/entities/money_printer/cl_init.lua b/gamemodes/darkrp/entities/entities/money_printer/cl_init.lua new file mode 100644 index 0000000..641bf11 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/money_printer/cl_init.lua @@ -0,0 +1,41 @@ +include("shared.lua") + +function ENT:Initialize() + self:initVars() + if not self.DisplayName or self.DisplayName == "" then + self.DisplayName = DarkRP.getPhrase("money_printer") + end +end + +local camStart3D2D = cam.Start3D2D +local camEnd3D2D = cam.End3D2D +local drawWordBox = draw.WordBox +local IsValid = IsValid + +local color_red = Color(140,0,0,100) +local color_white = color_white + +function ENT:Draw() + self:DrawModel() + + local Pos = self:GetPos() + local Ang = self:GetAngles() + + local owner = self:Getowning_ent() + owner = (IsValid(owner) and owner:Nick()) or DarkRP.getPhrase("unknown") + + surface.SetFont("HUDNumber5") + local text = self.DisplayName + local TextWidth = surface.GetTextSize(text) + local TextWidth2 = surface.GetTextSize(owner) + + Ang:RotateAroundAxis(Ang:Up(), 90) + + camStart3D2D(Pos + Ang:Up() * 11.5, Ang, 0.11) + drawWordBox(2, -TextWidth * 0.5, -30, text, "HUDNumber5", color_red, color_white) + drawWordBox(2, -TextWidth2 * 0.5, 18, owner, "HUDNumber5", color_red, color_white) + camEnd3D2D() +end + +function ENT:Think() +end diff --git a/gamemodes/darkrp/entities/entities/money_printer/init.lua b/gamemodes/darkrp/entities/entities/money_printer/init.lua new file mode 100644 index 0000000..5baa1fe --- /dev/null +++ b/gamemodes/darkrp/entities/entities/money_printer/init.lua @@ -0,0 +1,144 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") +include("shared.lua") + +ENT.SpawnOffset = Vector(15, 0, 15) + +local function PrintMore(ent) + if not IsValid(ent) then return end + + ent.sparking = true + timer.Simple(3, function() + if not IsValid(ent) then return end + ent:CreateMoneybag() + end) +end + +function ENT:StartSound() + self.sound = CreateSound(self, Sound("ambient/levels/labs/equipment_printer_loop1.wav")) + self.sound:SetSoundLevel(52) + self.sound:PlayEx(1, 100) +end + +function ENT:PostInit() + --Dumb things what you want to run on printer spawn +end + +function ENT:Initialize() + self:initVars() + self:SetModel(self.model) + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + timer.Simple(math.random(self.MinTimer, self.MaxTimer), function() PrintMore(self) end) + self:StartSound() + self:PostInit() +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + if self.burningup then return end + + self.damage = (self.damage or 100) - dmg:GetDamage() + if self.damage <= 0 then + local rnd = math.random(1, 10) + if rnd < 3 then + self:BurstIntoFlames() + else + self:Destruct() + self:Remove() + end + end +end + +function ENT:Destruct() + local vPoint = self:GetPos() + local effectdata = EffectData() + effectdata:SetStart(vPoint) + effectdata:SetOrigin(vPoint) + effectdata:SetScale(1) + util.Effect("Explosion", effectdata) + if IsValid(self:Getowning_ent()) then DarkRP.notify(self:Getowning_ent(), 1, 4, DarkRP.getPhrase("money_printer_exploded")) end +end + +function ENT:BurstIntoFlames() + if hook.Run("moneyPrinterCatchFire", self) == true then return end + + if IsValid(self:Getowning_ent()) then DarkRP.notify(self:Getowning_ent(), 0, 4, DarkRP.getPhrase("money_printer_overheating")) end + self.burningup = true + local burntime = math.random(8, 18) + self:Ignite(burntime, 0) + timer.Simple(burntime, function() self:Fireball() end) +end + +function ENT:Fireball() + if not self:IsOnFire() then self.burningup = false return end + local dist = math.random(20, 280) -- Explosion radius + self:Destruct() + for k, v in ipairs(ents.FindInSphere(self:GetPos(), dist)) do + if not v:IsPlayer() and not v:IsWeapon() and v:GetClass() ~= "predicted_viewmodel" and not v.IsMoneyPrinter then + v:Ignite(math.random(5, 22), 0) + elseif v:IsPlayer() then + local distance = v:GetPos():Distance(self:GetPos()) + v:TakeDamage(distance / dist * 100, self, self) + end + end + self:Remove() +end + +function ENT:CreateMoneybag() + if self:IsOnFire() then return end + + local amount = self.MoneyCount or (GAMEMODE.Config.mprintamount ~= 0 and GAMEMODE.Config.mprintamount or 250) + local prevent, hookAmount = hook.Run("moneyPrinterPrintMoney", self, amount) + if prevent == true then return end + + local MoneyPos = self:GetPos() + self.SpawnOffset + amount = hookAmount or amount + + if self.OverheatChance and self.OverheatChance > 0 then + local overheatchance + if self.OverheatChance <= 3 then + overheatchance = 22 + else + overheatchance = self.OverheatChance or 22 + end + if math.random(1, overheatchance) == 3 then self:BurstIntoFlames() end + end + + local moneybag = DarkRP.createMoneyBag(MoneyPos, amount) + hook.Run("moneyPrinterPrinted", self, moneybag) + self.sparking = false + timer.Simple(math.random(self.MinTimer, self.MaxTimer), function() PrintMore(self) end) +end + +function ENT:Think() + if self:WaterLevel() > 0 then + self:Destruct() + self:Remove() + return + end + self:StartSound() + if not self.sparking then return end + + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetMagnitude(1) + effectdata:SetScale(1) + effectdata:SetRadius(2) + util.Effect("Sparks", effectdata) +end + +function ENT:OnRemove() + if self.sound then + self.sound:Stop() + end +end diff --git a/gamemodes/darkrp/entities/entities/money_printer/shared.lua b/gamemodes/darkrp/entities/entities/money_printer/shared.lua new file mode 100644 index 0000000..e23770e --- /dev/null +++ b/gamemodes/darkrp/entities/entities/money_printer/shared.lua @@ -0,0 +1,94 @@ +--Static Vars +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Money Printer" +ENT.Author = "DarkRP Developers" +ENT.Spawnable = false +ENT.sparking = false +ENT.IsMoneyPrinter = true + +function ENT:initVars() + self.MoneyCount = GAMEMODE.Config.mprintamount + self.OverheatChance = GAMEMODE.Config.printeroverheatchance + self.model = "models/props_c17/consolebox01a.mdl" + self.damage = 100 + self.DisplayName = "Money Printer" + self.MinTimer = 100 + self.MaxTimer = 350 + self.SeizeReward = GAMEMODE.Config.printerreward +end + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "price") + self:NetworkVar("Entity", 0, "owning_ent") +end + +DarkRP.hookStub{ + name = "moneyPrinterCatchFire", + description = "Called when a money printer is about to catch fire.", + parameters = { + { + name = "moneyprinter", + description = "The money printer that is about to catch fire", + type = "Entity" + } + }, + returns = { + { + name = "prevent", + description = "Set to true to prevent the money printer from catching fire", + type = "boolean" + } + }, + realm = "Server" +} + +DarkRP.hookStub{ + name = "moneyPrinterPrintMoney", + description = "Called when a money printer is about to print money.", + parameters = { + { + name = "moneyprinter", + description = "The money printer that is about to print money", + type = "Entity" + }, + { + name = "amount", + description = "The amount to be printed", + type = "number" + } + }, + returns = { + { + name = "prevent", + description = "Set to true to prevent the money printer from printing the money.", + type = "boolean" + }, + { + name = "amount", + description = "Optionally override the amount of money that will be printed.", + type = "number" + } + }, + realm = "Server" +} + +DarkRP.hookStub{ + name = "moneyPrinterPrinted", + description = "Called after a money printer is has printed money.", + parameters = { + { + name = "moneyprinter", + description = "The money printer", + type = "Entity" + }, + { + name = "moneybag", + description = "The moneybag produced by the printer.", + type = "Entity" + } + }, + returns = { + }, + realm = "Server" +} diff --git a/gamemodes/darkrp/entities/entities/spawned_ammo/init.lua b/gamemodes/darkrp/entities/entities/spawned_ammo/init.lua new file mode 100644 index 0000000..a2cc6e2 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_ammo/init.lua @@ -0,0 +1,74 @@ +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + local phys = self:GetPhysicsObject() + phys:Wake() +end + +function ENT:Use(activator, caller) + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + hook.Call("playerPickedUpAmmo", nil, activator, self.amountGiven, self.ammoType, self) + + activator:GiveAmmo(self.amountGiven, self.ammoType) + self:Remove() +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) +end + +function ENT:StartTouch(ent) + -- the .USED var is also used in other mods for the same purpose + if ent.IsSpawnedAmmo ~= true or + self.ammoType ~= ent.ammoType or + self.hasMerged or ent.hasMerged then return end + + ent.hasMerged = true + ent.USED = true + + local selfAmount, entAmount = self.amountGiven, ent.amountGiven + local totalAmount = selfAmount + entAmount + self.amountGiven = totalAmount + + ent:Remove() +end + +DarkRP.hookStub{ + name = "playerPickedUpAmmo", + description = "When a player picks up ammo.", + parameters = { + { + name = "ply", + description = "The player who picked up ammo.", + type = "Player" + }, + { + name = "amount", + description = "Ammo amount.", + type = "number" + }, + { + name = "type", + description = "Ammo type.", + type = "number" + }, + { + name = "spawnedAmmo", + description = "Entity of spawned ammo.", + type = "Entity" + } + }, + returns = { + }, +} diff --git a/gamemodes/darkrp/entities/entities/spawned_ammo/shared.lua b/gamemodes/darkrp/entities/entities/spawned_ammo/shared.lua new file mode 100644 index 0000000..7ffa339 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_ammo/shared.lua @@ -0,0 +1,6 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Spawned Ammo" +ENT.Author = "FPtje" +ENT.Spawnable = false +ENT.IsSpawnedAmmo = true diff --git a/gamemodes/darkrp/entities/entities/spawned_food/init.lua b/gamemodes/darkrp/entities/entities/spawned_food/init.lua new file mode 100644 index 0000000..42f9bb3 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_food/init.lua @@ -0,0 +1,68 @@ +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end +end + +function ENT:OnTakeDamage(dmg) + self:Remove() +end + +function ENT:Use(activator, caller) + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + local override = self.foodItem.onEaten and self.foodItem.onEaten(self, activator, self.foodItem) + + if override then + self:Remove() + return + end + + hook.Call("playerAteFood", nil, activator, self.foodItem, self) + + activator:setSelfDarkRPVar("Energy", math.Clamp((activator:getDarkRPVar("Energy") or 100) + (self.FoodEnergy or 1), 0, 100)) + umsg.Start("AteFoodIcon", activator) + umsg.End() + + self:Remove() + activator:EmitSound(self.EatSound, 100, 100) +end + +DarkRP.hookStub{ + name = "playerAteFood", + description = "When a player eats food.", + parameters = { + { + name = "ply", + description = "The player who ate food.", + type = "Player" + }, + { + name = "food", + description = "Food table.", + type = "table" + }, + { + name = "spawnedfood", + description = "Entity of spawned food.", + type = "Entity" + }, + }, + returns = { + }, +} diff --git a/gamemodes/darkrp/entities/entities/spawned_food/shared.lua b/gamemodes/darkrp/entities/entities/spawned_food/shared.lua new file mode 100644 index 0000000..1233c8b --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_food/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Spawned Food" +ENT.Author = "Rickster" +ENT.Spawnable = false +ENT.IsSpawnedFood = true +ENT.EatSound = "vo/sandwicheat09.mp3" -- Requires Team Fortress 2 + +function ENT:SetupDataTables() + self:NetworkVar("Entity", 1, "owning_ent") +end diff --git a/gamemodes/darkrp/entities/entities/spawned_money/cl_init.lua b/gamemodes/darkrp/entities/entities/spawned_money/cl_init.lua new file mode 100644 index 0000000..1f5722f --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_money/cl_init.lua @@ -0,0 +1,32 @@ +include("shared.lua") + +local color_red = Color(140, 0, 0, 100) +local color_white = color_white + +function ENT:Draw() + self:DrawModel() + + -- Do not draw labels when a different model is used. + -- If you want a different model with labels, make your own money entity and use GM.Config.MoneyClass. + if self:GetModel() ~= "models/props/cs_assault/money.mdl" then return end + + local Pos = self:GetPos() + local Ang = self:GetAngles() + + surface.SetFont("ChatFont") + local text = DarkRP.formatMoney(self:Getamount()) + local TextWidth = surface.GetTextSize(text) + + cam.Start3D2D(Pos + Ang:Up() * 0.82, Ang, 0.1) + draw.WordBox(2, -TextWidth * 0.5, -10, text, "ChatFont", color_red, color_white) + cam.End3D2D() + + Ang:RotateAroundAxis(Ang:Right(), 180) + + cam.Start3D2D(Pos, Ang, 0.1) + draw.WordBox(2, -TextWidth * 0.5, -10, text, "ChatFont", color_red, color_white) + cam.End3D2D() +end + +function ENT:Think() +end diff --git a/gamemodes/darkrp/entities/entities/spawned_money/init.lua b/gamemodes/darkrp/entities/entities/spawned_money/init.lua new file mode 100644 index 0000000..8dc747f --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_money/init.lua @@ -0,0 +1,126 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + self:SetModel(GAMEMODE.Config.moneyModel or "models/props/cs_assault/money.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + self:SetCollisionGroup(COLLISION_GROUP_WEAPON) + + self.nodupe = true + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end +end + +function ENT:Use(activator, caller) + if self.USED or self.hasMerged then return end + + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + self.USED = true + local amount = self:Getamount() + + hook.Call("playerPickedUpMoney", nil, activator, amount or 0, self) + + activator:addMoney(amount or 0) + DarkRP.notify(activator, 0, 4, DarkRP.getPhrase("found_money", DarkRP.formatMoney(self:Getamount()))) + self:Remove() +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + + local typ = dmg:GetDamageType() + if bit.band(typ, bit.bor(DMG_FALL, DMG_VEHICLE, DMG_DROWN, DMG_RADIATION, DMG_PHYSGUN)) > 0 then return end + + self.USED = true + self.hasMerged = true + self:Remove() +end + +function ENT:StartTouch(ent) + -- the .USED var is also used in other mods for the same purpose + if ent:GetClass() ~= "spawned_money" or self.USED or ent.USED or self.hasMerged or ent.hasMerged then return end + + -- Both hasMerged and USED are used by third party mods. Keep both in. + ent.USED = true + ent.hasMerged = true + + ent:Remove() + self:Setamount(self:Getamount() + ent:Getamount()) + if GAMEMODE.Config.moneyRemoveTime and GAMEMODE.Config.moneyRemoveTime ~= 0 then + timer.Adjust("RemoveEnt" .. self:EntIndex(), GAMEMODE.Config.moneyRemoveTime, 1, fn.Partial(SafeRemoveEntity, self)) + end +end + +DarkRP.hookStub{ + name = "playerPickedUpMoney", + description = "Called when a player picked up money.", + parameters = { + { + name = "player", + description = "The player who picked up the money.", + type = "Player" + }, + { + name = "amount", + description = "The amount of money picked up.", + type = "number" + }, + { + name = "entity", + description = "The entity of the money picked up itself.", + type = "Entity" + } + }, + returns = { + }, + realm = "Server" +} + +DarkRP.hookStub{ + name = "canDarkRPUse", + description = "When a player uses an entity.", + parameters = { + { + name = "ply", + description = "The player who tries to use the entity.", + type = "Player" + }, + { + name = "entity", + description = "The actual entity the player attempts to use.", + type = "Entity" + }, + { + name = "caller", + description = "The entity that directly triggered the input.", + type = "Player" + } + }, + returns = { + { + name = "canUse", + description = "Whether the entity should be used or not.", + type = "boolean" + }, + { + name = "reason", + description = "Why the entity cannot be used.", + optional = true, + type = "string" + }, + }, +} diff --git a/gamemodes/darkrp/entities/entities/spawned_money/shared.lua b/gamemodes/darkrp/entities/entities/spawned_money/shared.lua new file mode 100644 index 0000000..9ae63a9 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_money/shared.lua @@ -0,0 +1,10 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Spawned Money" +ENT.Author = "FPtje" +ENT.Spawnable = false +ENT.IsSpawnedMoney = true + +function ENT:SetupDataTables() + self:NetworkVar("Int",0,"amount") +end \ No newline at end of file diff --git a/gamemodes/darkrp/entities/entities/spawned_shipment/cl_init.lua b/gamemodes/darkrp/entities/entities/spawned_shipment/cl_init.lua new file mode 100644 index 0000000..d95c31e --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_shipment/cl_init.lua @@ -0,0 +1,161 @@ +include("shared.lua") + +local matBallGlow = Material("models/props_combine/tpballglow") +function ENT:Draw() + self.height = self.height or 0 + self.colr = self.colr or 1 + self.colg = self.colg or 0 + self.StartTime = self.StartTime or CurTime() + + if GAMEMODE.Config.shipmentspawntime > 0 and self.height < self:OBBMaxs().z then + self:drawSpawning() + else + self:DrawModel() + end + + self:drawFloatingGun() + self:drawInfo() +end + +net.Receive("DarkRP_shipmentSpawn", function() + local ent = net.ReadEntity() + if not IsValid(ent) or not ent.IsSpawnedShipment then return end + + ent.height = 0 + ent.StartTime = CurTime() +end) + +function ENT:drawSpawning() + render.MaterialOverride(matBallGlow) + + render.SetColorModulation(self.colr, self.colg, 0) + + self:DrawModel() + + render.MaterialOverride() + self.colr = 1 - ((CurTime() - self.StartTime) / GAMEMODE.Config.shipmentspawntime) + self.colg = (CurTime() - self.StartTime) / GAMEMODE.Config.shipmentspawntime + + render.SetColorModulation(1, 1, 1) + + render.MaterialOverride() + + local normal = - self:GetAngles():Up() + local pos = self:LocalToWorld(Vector(0, 0, self:OBBMins().z + self.height)) + local distance = normal:Dot(pos) + self.height = self:OBBMaxs().z * ((CurTime() - self.StartTime) / GAMEMODE.Config.shipmentspawntime) + render.EnableClipping(true) + render.PushCustomClipPlane(normal, distance) + + self:DrawModel() + + render.PopCustomClipPlane() +end + +function ENT:drawFloatingGun() + local contents = CustomShipments[self:Getcontents() or ""] + if not contents or not IsValid(self:GetgunModel()) then return end + self:GetgunModel():SetNoDraw(true) + + local pos = self:GetPos() + local ang = self:GetAngles() + + -- Position the gun + local gunPos = self:GetAngles():Up() * 40 + ang:Up() * (math.sin(CurTime() * 3) * 8) + self:GetgunModel():SetPos(pos + gunPos) + + + -- Make it dance + ang:RotateAroundAxis(ang:Up(), (CurTime() * 180) % 360) + self:GetgunModel():SetAngles(ang) + + -- Draw the model + if self:Getgunspawn() < CurTime() - 2 then + self:GetgunModel():DrawModel() + return + elseif self:Getgunspawn() < CurTime() then -- Not when a gun just spawned + return + end + + -- Draw the spawning effect + local delta = self:Getgunspawn() - CurTime() + local min, max = self:GetgunModel():OBBMins(), self:GetgunModel():OBBMaxs() + min, max = self:GetgunModel():LocalToWorld(min), self:GetgunModel():LocalToWorld(max) + + -- Draw the ghosted weapon + render.MaterialOverride(matBallGlow) + render.SetColorModulation(1 - delta, delta, 0) -- From red to green + self:GetgunModel():DrawModel() + render.MaterialOverride() + render.SetColorModulation(1, 1, 1) + + -- Draw the cut-off weapon + render.EnableClipping(true) + -- The clipping plane only draws objects that face the plane + local normal = -self:GetgunModel():GetAngles():Forward() + local cutPosition = LerpVector(delta, max, min) -- Where it cuts + local cutDistance = normal:Dot(cutPosition) -- Project the vector onto the normal to get the shortest distance between the plane and origin + + -- Activate the plane + render.PushCustomClipPlane(normal, cutDistance); + -- Draw the partial model + self:GetgunModel():DrawModel() + -- Remove the plane + render.PopCustomClipPlane() + + render.EnableClipping(false) +end + +local color_red = Color(140, 0, 0, 100) +local color_white = color_white + +function ENT:drawInfo() + local Pos = self:GetPos() + local Ang = self:GetAngles() + + local content = self:Getcontents() or "" + local contents = CustomShipments[content] + if not contents then return end + contents = contents.name + + surface.SetFont("HUDNumber5") + local text = DarkRP.getPhrase("contents") + local TextWidth = surface.GetTextSize(text) + local TextWidth2 = surface.GetTextSize(contents) + + cam.Start3D2D(Pos + Ang:Up() * 25, Ang, 0.2) + draw.WordBox(2, -TextWidth * 0.5 + 5, -30, text, "HUDNumber5", color_red, color_white) + draw.WordBox(2, -TextWidth2 * 0.5 + 5, 18, contents, "HUDNumber5", color_red, color_white) + cam.End3D2D() + + Ang:RotateAroundAxis(Ang:Forward(), 90) + + text = DarkRP.getPhrase("amount") + TextWidth = surface.GetTextSize(text) + TextWidth2 = surface.GetTextSize(self:Getcount()) + + cam.Start3D2D(Pos + Ang:Up() * 17, Ang, 0.14) + draw.WordBox(2, -TextWidth * 0.5 + 5, -150, text, "HUDNumber5", color_red, color_white) + draw.WordBox(2, -TextWidth2 * 0.5 + 0, -102, self:Getcount(), "HUDNumber5", color_red, color_white) + cam.End3D2D() +end + +--[[--------------------------------------------------------------------------- +Create a shipment from a spawned_weapon +---------------------------------------------------------------------------]] +properties.Add("splitShipment", +{ + MenuLabel = DarkRP.getPhrase("splitshipment"), + Order = 2004, + MenuIcon = "icon16/arrow_divide.png", + + Filter = function(self, ent, ply) + if not IsValid(ent) then return false end + return ent.IsSpawnedShipment + end, + + Action = function(self, ent) + if not IsValid(ent) then return end + RunConsoleCommand("darkrp", "splitshipment", ent:EntIndex()) + end +}) diff --git a/gamemodes/darkrp/entities/entities/spawned_shipment/commands.lua b/gamemodes/darkrp/entities/entities/spawned_shipment/commands.lua new file mode 100644 index 0000000..0edf907 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_shipment/commands.lua @@ -0,0 +1,103 @@ +--[[--------------------------------------------------------------------------- +Create a shipment from a spawned_weapon +---------------------------------------------------------------------------]] +local function createShipment(ply, args) + local id = tonumber(args) or -1 + local ent = Entity(id) + + ent = IsValid(ent) and ent or ply:GetEyeTrace().Entity + + if not IsValid(ent) or not ent.IsSpawnedWeapon or ent.PlayerUse == false then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return + end + + local pos = ent:GetPos() + + if pos:DistToSqr(ply:GetShootPos()) > 16900 or not pos:isInSight({ent, ply} , ply) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("distance_too_big")) + return + end + + ent.PlayerUse = false + + local shipID + for k, v in pairs(CustomShipments) do + if v.entity == ent:GetWeaponClass() then + shipID = k + break + end + end + + if not shipID or ent.USED then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/makeshipment", "")) + return + end + + local crate = ents.Create(CustomShipments[shipID].shipmentClass or "spawned_shipment") + crate.SID = ply.SID + crate:SetPos(ent:GetPos()) + crate.nodupe = true + crate:SetContents(shipID, ent:Getamount()) + crate:Spawn() + crate:SetPlayer(ply) + crate.clip1 = ent.clip1 + crate.clip2 = ent.clip2 + crate.ammoadd = ent.ammoadd or 0 + + SafeRemoveEntity(ent) + + local phys = crate:GetPhysicsObject() + phys:Wake() +end +DarkRP.defineChatCommand("makeshipment", createShipment, 0.3) + +--[[--------------------------------------------------------------------------- +Split a shipment in two +---------------------------------------------------------------------------]] +local function splitShipment(ply, args) + local id = tonumber(args) or -1 + local ent = Entity(id) + + ent = IsValid(ent) and ent or ply:GetEyeTrace().Entity + + if not IsValid(ent) or not ent.IsSpawnedShipment then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return + end + + if ent:Getcount() < 2 or ent.locked or ent.USED then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("shipment_cannot_split")) + return + end + + local pos = ent:GetPos() + + if pos:DistToSqr(ply:GetShootPos()) > 16900 or not pos:isInSight({ent, ply} , ply) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("distance_too_big")) + return + end + + local count = math.floor(ent:Getcount() / 2) + ent:Setcount(ent:Getcount() - count) + + ent:StartSpawning() + + local crate = ents.Create("spawned_shipment") + crate.locked = true + crate.SID = ply.SID + crate:SetPos(ent:GetPos()) + crate.nodupe = true + crate:SetContents(ent:Getcontents(), count) + crate:SetPlayer(ply) + + crate.clip1 = ent.clip1 + crate.clip2 = ent.clip2 + crate.ammoadd = ent.ammoadd + + crate:Spawn() + + local phys = crate:GetPhysicsObject() + phys:Wake() +end +DarkRP.defineChatCommand("splitshipment", splitShipment, 0.3) diff --git a/gamemodes/darkrp/entities/entities/spawned_shipment/init.lua b/gamemodes/darkrp/entities/entities/spawned_shipment/init.lua new file mode 100644 index 0000000..02a80bd --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_shipment/init.lua @@ -0,0 +1,257 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") +include("commands.lua") + +util.AddNetworkString("DarkRP_shipmentSpawn") + +function ENT:Initialize() + local contents = CustomShipments[self:Getcontents() or ""] + + self.Destructed = false + self:SetModel(contents and contents.shipmodel or "models/Items/item_item_crate.mdl") + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + + self:StartSpawning() + self.damage = 100 + + local phys = self:GetPhysicsObject() + + if phys:IsValid() then + phys:Wake() + end + + -- Create a serverside gun model + -- it's required serverside to be able to get OBB information clientside + self:SetgunModel(IsValid(self:GetgunModel()) and self:GetgunModel() or ents.Create("prop_physics")) + self:GetgunModel():SetModel(contents.model) + self:GetgunModel():SetPos(self:GetPos()) + self:GetgunModel():Spawn() + self:GetgunModel():Activate() + self:GetgunModel():SetSolid(SOLID_NONE) + self:GetgunModel():SetParent(self) + + phys = self:GetgunModel():GetPhysicsObject() + + if phys:IsValid() then + phys:EnableMotion(false) + end + + -- The following code should not be reached + if self:Getcount() < 1 then + self.PlayerUse = false + SafeRemoveEntity(self) + if not contents then + DarkRP.error("Shipment created with zero or fewer elements.", 2) + else + DarkRP.error(string.format("Some smartass thought they were clever by setting the 'amount' of shipment '%s' to 0.\nWhat the fuck do you expect the use of an empty shipment to be?", contents.name), 2) + end + end +end + +function ENT:StartSpawning() + self.locked = true + timer.Simple(0, function() + net.Start("DarkRP_shipmentSpawn") + net.WriteEntity(self) + net.Broadcast() + end) + + timer.Simple(0, function() self.locked = true end) -- when spawning through pocket it might be unlocked + timer.Simple(GAMEMODE.Config.shipmentspawntime, function() if IsValid(self) then self.locked = false end end) +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) + if not self.locked then + self.damage = self.damage - dmg:GetDamage() + if self.damage <= 0 then + self:Destruct() + end + end +end + +function ENT:SetContents(s, c) + self:Setcontents(s) + self:Setcount(c) +end + +function ENT:Use(activator, caller) + if self.IsPocketed then return end + if isfunction(self.PlayerUse) then + local val = self:PlayerUse(activator, caller) + if val ~= nil then return val end + elseif self.PlayerUse ~= nil then + return self.PlayerUse + end + + if self.locked or self.USED then return end + + local canUse, reason = hook.Call("canDarkRPUse", nil, activator, self, caller) + if canUse == false then + if reason then DarkRP.notify(activator, 1, 4, reason) end + return + end + + hook.Call("playerOpenedShipment", nil, activator, self) + + self.locked = true -- One activation per second + self.USED = true + self.sparking = true + self:Setgunspawn(CurTime() + 1) + timer.Create(self:EntIndex() .. "crate", 1, 1, function() + if not IsValid(self) then return end + self.SpawnItem(self) + end) +end + +local function calculateAmmo(class, shipment) + local clip1, ammoadd = shipment.clip1, shipment.ammoadd + + local defaultClip, clipSize = 0, 0 + local wep_tbl = weapons.Get(class) + if wep_tbl and istable(wep_tbl.Primary) then + defaultClip = wep_tbl.Primary.DefaultClip or -1 + clipSize = wep_tbl.Primary.ClipSize or -1 + end + ammoadd = ammoadd or defaultClip + + if not clip1 then + clip1 = clipSize + end + return ammoadd, clip1 +end + +function ENT:SpawnItem() + timer.Remove(self:EntIndex() .. "crate") + self.sparking = false + local count = self:Getcount() + if count <= 1 then self:Remove() end + local contents = self:Getcontents() + + if CustomShipments[contents] and CustomShipments[contents].spawn then self.USED = false return CustomShipments[contents].spawn(self, CustomShipments[contents]) end + + local weapon = ents.Create("spawned_weapon") + + local weaponAng = self:GetAngles() + local weaponPos = self:GetAngles():Up() * 40 + weaponAng:Up() * (math.sin(CurTime() * 3) * 8) + weaponAng:RotateAroundAxis(weaponAng:Up(), (CurTime() * 180) % 360) + + if not CustomShipments[contents] then + weapon:Remove() + self:Remove() + return + end + + local class = CustomShipments[contents].entity + local model = CustomShipments[contents].model + + weapon:SetWeaponClass(class) + weapon:SetModel(model) + + weapon.ammoadd, weapon.clip1 = calculateAmmo(class, self) + weapon.clip2 = self.clip2 + weapon:SetPos(self:GetPos() + weaponPos) + weapon:SetAngles(weaponAng) + weapon.nodupe = true + weapon:Spawn() + count = count - 1 + self:Setcount(count) + self.locked = false + self.USED = nil +end + +function ENT:Think() + if self.sparking then + local effectdata = EffectData() + effectdata:SetOrigin(self:GetPos()) + effectdata:SetMagnitude(1) + effectdata:SetScale(1) + effectdata:SetRadius(2) + util.Effect("Sparks", effectdata) + end +end + +function ENT:Destruct() + if self.Destructed then return end + self.Destructed = true + local vPoint = self:GetPos() + local contents = self:Getcontents() + local count = self:Getcount() + local class = nil + local model = nil + + if CustomShipments[contents] then + class = CustomShipments[contents].entity + model = CustomShipments[contents].model + else + self:Remove() + return + end + + local weapon = ents.Create("spawned_weapon") + weapon:SetModel(model) + weapon:SetWeaponClass(class) + weapon:SetPos(Vector(vPoint.x, vPoint.y, vPoint.z + 5)) + weapon.ammoadd, weapon.clip1 = calculateAmmo(class, self) + weapon.clip2 = self.clip2 + weapon.nodupe = true + weapon:Spawn() + weapon:Setamount(count) + + self:Remove() +end + +function ENT:StartTouch(ent) + -- the .USED var is also used in other mods for the same purpose + if not ent.IsSpawnedShipment or + self:Getcontents() ~= ent:Getcontents() or + self.locked or ent.locked or + self.USED or ent.USED or + self.hasMerged or ent.hasMerged then return end + + -- Both hasMerged and USED are used by third party mods. Keep both in. + ent.hasMerged = true + ent.USED = true + + local selfCount, entCount = self:Getcount(), ent:Getcount() + local count = selfCount + entCount + self:Setcount(count) + + -- Merge ammo information (avoid ammo exploits) + if self.clip1 or ent.clip1 then -- If neither have a clip, use default clip, otherwise merge the two + self.clip1 = math.floor(((ent.clip1 or 0) * entCount + (self.clip1 or 0) * selfCount) / count) + end + + if self.clip2 or ent.clip2 then + self.clip2 = math.floor(((ent.clip2 or 0) * entCount + (self.clip2 or 0) * selfCount) / count) + end + + if self.ammoadd or ent.ammoadd then + self.ammoadd = math.floor(((ent.ammoadd or 0) * entCount + (self.ammoadd or 0) * selfCount) / count) + end + + ent:Remove() +end + +DarkRP.hookStub{ + name = "playerOpenedShipment", + description = "When a player opens a shipment.", + parameters = { + { + name = "player", + description = "The player who opens a shipment.", + type = "Player" + }, + { + name = "entity", + description = "Entity of spawned shipment.", + type = "Entity" + } + }, + returns = { + }, +} diff --git a/gamemodes/darkrp/entities/entities/spawned_shipment/shared.lua b/gamemodes/darkrp/entities/entities/spawned_shipment/shared.lua new file mode 100644 index 0000000..cd39a44 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_shipment/shared.lua @@ -0,0 +1,26 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Shipment" +ENT.Author = "philxyz" +ENT.Spawnable = false +ENT.IsSpawnedShipment = true + +function ENT:SetupDataTables() + self:NetworkVar("Int",0,"contents") + self:NetworkVar("Int",1,"count") + self:NetworkVar("Float", 0, "gunspawn") + self:NetworkVar("Entity", 0, "owning_ent") + self:NetworkVar("Entity", 1, "gunModel") +end + +DarkRP.declareChatCommand{ + command = "splitshipment", + description = "Split the shipment you're looking at.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "makeshipment", + description = "Create a shipment from a dropped weapon.", + delay = 1.5 +} diff --git a/gamemodes/darkrp/entities/entities/spawned_weapon/cl_init.lua b/gamemodes/darkrp/entities/entities/spawned_weapon/cl_init.lua new file mode 100644 index 0000000..548c5ab --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_weapon/cl_init.lua @@ -0,0 +1,76 @@ +include("shared.lua") + +local color_red = Color(140, 0, 0, 100) +local color_white = color_white + +function ENT:Draw() + local ret = hook.Call("onDrawSpawnedWeapon", nil, self) + if ret ~= nil then return end + self:DrawModel() + + local amount = self:Getamount() + if amount == 1 then return end + + local Pos = self:GetPos() + local Ang = self:GetAngles() + local text = DarkRP.getPhrase("amount") .. amount + + surface.SetFont("HUDNumber5") + local TextWidth = surface.GetTextSize(text) + + Ang:RotateAroundAxis(Ang:Forward(), 90) + + cam.Start3D2D(Pos + Ang:Up(), Ang, 0.11) + draw.WordBox(2, 0, -40, text, "HUDNumber5", color_red, color_white) + cam.End3D2D() + + Ang:RotateAroundAxis(Ang:Right(), 180) + + cam.Start3D2D(Pos + Ang:Up() * 3, Ang, 0.11) + draw.WordBox(2, -TextWidth, -40, text, "HUDNumber5", color_red, color_white) + cam.End3D2D() +end + +--[[--------------------------------------------------------------------------- +Create a shipment from a spawned_weapon +---------------------------------------------------------------------------]] +properties.Add("createShipment", + { + MenuLabel = DarkRP.getPhrase("createshipment"), + Order = 2003, + MenuIcon = "icon16/add.png", + + Filter = function(self, ent, ply) + if not IsValid(ent) then return false end + return ent.IsSpawnedWeapon + end, + + Action = function(self, ent) + if not IsValid(ent) then return end + RunConsoleCommand("darkrp", "makeshipment", ent:EntIndex()) + end + } +) + +--[[--------------------------------------------------------------------------- +Interface +---------------------------------------------------------------------------]] +DarkRP.hookStub{ + name = "onDrawSpawnedWeapon", + description = "Draw spawned weapons.", + realm = "Client", + parameters = { + { + name = "weapon", + description = "The weapon to perform drawing operations on.", + type = "Player" + } + }, + returns = { + { + name = "value", + description = "Return a value to completely override drawing", + type = "any" + } + } +} diff --git a/gamemodes/darkrp/entities/entities/spawned_weapon/init.lua b/gamemodes/darkrp/entities/entities/spawned_weapon/init.lua new file mode 100644 index 0000000..d78d018 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_weapon/init.lua @@ -0,0 +1,163 @@ +AddCSLuaFile("cl_init.lua") +AddCSLuaFile("shared.lua") + +include("shared.lua") + +function ENT:Initialize() + DarkRP.ValidatedPhysicsInit(self, SOLID_VPHYSICS, + string.format("The issue lies with weapon \"%s\"", self:GetWeaponClass() or "unknown")) + self:SetMoveType(MOVETYPE_VPHYSICS) + self:SetSolid(SOLID_VPHYSICS) + self:SetUseType(SIMPLE_USE) + local phys = self:GetPhysicsObject() + phys:Wake() + + if self:Getamount() == 0 then + self:Setamount(1) + end +end + +function ENT:DecreaseAmount() + local amount = self:Getamount() - 1 + self:Setamount(amount) + + if amount <= 0 then + self:Remove() + self.PlayerUse = false + self.Removed = true -- because it is not removed immediately + end +end + +function ENT:OnTakeDamage(dmg) + self:TakePhysicsDamage(dmg) +end + +function ENT:Use(activator, caller) + if isfunction(self.PlayerUse) then + local val = self:PlayerUse(activator, caller) + if val ~= nil then return val end + elseif self.PlayerUse ~= nil then + return self.PlayerUse + end + + local class = self:GetWeaponClass() + local weapon = ents.Create(class) + + if not weapon:IsWeapon() then + weapon:SetPos(self:GetPos()) + weapon:SetAngles(self:GetAngles()) + weapon:Spawn() + weapon:Activate() + self:DecreaseAmount() + return + end + + local CanPickup = hook.Call("PlayerCanPickupWeapon", GAMEMODE, activator, weapon) + local ShouldntContinue = hook.Call("PlayerPickupDarkRPWeapon", nil, activator, self, weapon) + if not CanPickup or ShouldntContinue then + weapon:Remove() + return + end + + weapon:Remove() + + weapon = activator:Give(class, true) + + -- The player already had the weapon when the result of :Give() is not a + -- valid weapon + local activatorHadWeapon = not weapon:IsValid() + weapon = activatorHadWeapon and activator:GetWeapon(class) or weapon + + hook.Call("playerPickedUpWeapon", nil, activator, self, weapon) + + self:GivePlayerAmmo(activator, weapon, activatorHadWeapon) + + self:DecreaseAmount() +end + +function ENT:GivePlayerAmmo(ply, weapon, playerHadWeapon) + local primaryAmmoType = weapon:GetPrimaryAmmoType() + local secondaryAmmoType = weapon:GetSecondaryAmmoType() + local clip1, clip2 = self.clip1, self.clip2 + + if playerHadWeapon then + if clip2 and clip2 > 0 and weapon:Clip2() ~= -1 then + weapon:SetClip2(weapon:Clip2() + clip2) + clip2 = 0 + end + else + if clip1 and clip1 ~= -1 and weapon:Clip1() ~= -1 then + weapon:SetClip1(clip1) + clip1 = 0 + end + if clip2 and clip2 ~= -1 and weapon:Clip2() ~= -1 then + weapon:SetClip2(self.clip2) + clip2 = 0 + end + end + + if primaryAmmoType > 0 then + local primAmmo = ply:GetAmmoCount(primaryAmmoType) + primAmmo = primAmmo + (self.ammoadd or 0) + (clip1 or 0) -- Gets rid of any ammo given during weapon pickup + ply:SetAmmo(primAmmo, primaryAmmoType) + end + + if secondaryAmmoType > 0 then + local secAmmo = ply:GetAmmoCount(secondaryAmmoType) + (clip2 or 0) + ply:SetAmmo(secAmmo, secondaryAmmoType) + end +end + +function ENT:StartTouch(ent) + -- the .USED var is also used in other mods for the same purpose + if ent.IsSpawnedWeapon ~= true or + self:GetWeaponClass() ~= ent:GetWeaponClass() or + self.hasMerged or ent.hasMerged then return end + + ent.hasMerged = true + ent.USED = true + + local selfAmount, entAmount = self:Getamount(), ent:Getamount() + local totalAmount = selfAmount + entAmount + self.ammoadd, ent.ammoadd = self.ammoadd or 0, ent.ammoadd or 0 + + -- ammoAdd will be the floored average of both weapons' ammoadd + -- Some ammo might get lost there. + self.ammoadd = math.floor((self.ammoadd * selfAmount + ent.ammoadd * entAmount) / totalAmount) + + -- If neither have a clip, use default clip, otherwise merge the two + if self.clip1 or ent.clip1 then + self.clip1 = math.floor(((self.clip1 or 0) * selfAmount + (ent.clip1 or 0) * entAmount) / totalAmount) + end + + if self.clip2 or ent.clip2 then + self.clip2 = math.floor(((self.clip2 or 0) * selfAmount + (ent.clip2 or 0) * entAmount) / totalAmount) + end + + self:Setamount(totalAmount) + ent:Remove() +end + +DarkRP.hookStub{ + name = "playerPickedUpWeapon", + description = "When a player picks up a weapon.", + parameters = { + { + name = "player", + description = "The player who picks up the weapon.", + type = "Player" + }, + { + name = "entity", + description = "Entity of spawned weapon.", + type = "Entity" + }, + { + name = "weapon", + description = "The weapon entity that the player is holding after picking up the weapon.", + type = "Weapon" + } + }, + returns = { + }, +} diff --git a/gamemodes/darkrp/entities/entities/spawned_weapon/shared.lua b/gamemodes/darkrp/entities/entities/spawned_weapon/shared.lua new file mode 100644 index 0000000..74d0d69 --- /dev/null +++ b/gamemodes/darkrp/entities/entities/spawned_weapon/shared.lua @@ -0,0 +1,11 @@ +ENT.Type = "anim" +ENT.Base = "base_gmodentity" +ENT.PrintName = "Spawned Weapon" +ENT.Author = "Rickster" +ENT.Spawnable = false +ENT.IsSpawnedWeapon = true + +function ENT:SetupDataTables() + self:NetworkVar("Int", 0, "amount") + self:NetworkVar("String", 0, "WeaponClass") +end diff --git a/gamemodes/darkrp/entities/weapons/arrest_stick/shared.lua b/gamemodes/darkrp/entities/weapons/arrest_stick/shared.lua new file mode 100644 index 0000000..8d226c4 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/arrest_stick/shared.lua @@ -0,0 +1,139 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Slot = 1 + SWEP.SlotPos = 3 +end + +DEFINE_BASECLASS("stick_base") + +SWEP.Instructions = "Left click to arrest\nRight click to switch batons" +SWEP.IsDarkRPArrestStick = true + +SWEP.PrintName = "Arrest Baton" +SWEP.Spawnable = true +SWEP.Category = "DarkRP (Utility)" + +SWEP.StickColor = Color(255, 0, 0) + +SWEP.Switched = true + +DarkRP.hookStub{ + name = "canArrest", + description = "Whether someone can arrest another player.", + parameters = { + { + name = "arrester", + description = "The player trying to arrest someone.", + type = "Player" + }, + { + name = "arrestee", + description = "The player being arrested.", + type = "Player" + } + }, + returns = { + { + name = "canArrest", + description = "A yes or no as to whether the arrester can arrest the arestee.", + type = "boolean" + }, + { + name = "message", + description = "The message that is shown when they can't arrest the player.", + type = "string" + } + }, + realm = "Server" +} + +DarkRP.hookStub{ + name = "setArrestStickTime", + description = "Sets arrest time for an arrest made via the arrest stick", + parameters = { + { + name = "arrest_stick", + description = "The arrest strick weapon with which the arrestee was arrested.", + type = "Weapon" + }, + { + name = "arrester", + description = "The player trying to arrest someone.", + type = "Player" + }, + { + name = "arrestee", + description = "The player being arrested.", + type = "Player" + } + }, + returns = { + { + name = "time", + description = "The time to arrest the player.", + type = "integer" + } + }, + realm = "Server" +} + +function SWEP:Deploy() + self.Switched = true + return BaseClass.Deploy(self) +end + +function SWEP:PrimaryAttack() + BaseClass.PrimaryAttack(self) + + if CLIENT then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + Owner:LagCompensation(true) + local trace = util.QuickTrace(Owner:EyePos(), Owner:GetAimVector() * 90, {Owner}) + Owner:LagCompensation(false) + + local ent = trace.Entity + if IsValid(ent) and ent.onArrestStickUsed then + ent:onArrestStickUsed(Owner) + return + end + + ent = Owner:getEyeSightHitEntity(nil, nil, function(p) return p ~= Owner and p:IsPlayer() and p:Alive() and p:IsSolid() end) + + local stickRange = self.stickRange * self.stickRange + if not IsValid(ent) or (Owner:EyePos():DistToSqr(ent:GetPos()) > stickRange) or not ent:IsPlayer() then + return + end + + local canArrest, message = hook.Call("canArrest", DarkRP.hooks, Owner, ent) + if not canArrest then + if message then DarkRP.notify(Owner, 1, 5, message) end + return + end + + local time = hook.Call("setArrestStickTime", DarkRP.hooks, self, Owner, ent) + ent:arrest(time, Owner) + DarkRP.notify(ent, 0, 20, DarkRP.getPhrase("youre_arrested_by", Owner:Nick())) + + if Owner.SteamName then + DarkRP.log(Owner:Nick() .. " (" .. Owner:SteamID() .. ") arrested " .. ent:Nick(), Color(0, 255, 255)) + end +end + +function SWEP:startDarkRPCommand(usrcmd) + local Owner = self:GetOwner() + if not IsValid(Owner) then return end + + if game.SinglePlayer() and CLIENT then return end + if usrcmd:KeyDown(IN_ATTACK2) then + if not self.Switched and Owner:HasWeapon("unarrest_stick") then + usrcmd:SelectWeapon(Owner:GetWeapon("unarrest_stick")) + end + else + self.Switched = false + end +end diff --git a/gamemodes/darkrp/entities/weapons/door_ram/shared.lua b/gamemodes/darkrp/entities/weapons/door_ram/shared.lua new file mode 100644 index 0000000..203b4ea --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/door_ram/shared.lua @@ -0,0 +1,319 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Slot = 5 + SWEP.SlotPos = 1 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +-- Variables that are used on both client and server +DEFINE_BASECLASS("weapon_cs_base2") + +SWEP.PrintName = "Battering Ram" +SWEP.Author = "DarkRP Developers" +SWEP.Instructions = "Left click to break open doors/unfreeze props or get people out of their vehicles\nRight click to raise" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.IsDarkRPDoorRam = true + +SWEP.IconLetter = "" + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.ViewModel = Model("models/weapons/c_rpg.mdl") +SWEP.WorldModel = Model("models/weapons/w_rocket_launcher.mdl") +SWEP.AnimPrefix = "rpg" + +SWEP.UseHands = true + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = "DarkRP (Utility)" + +SWEP.Sound = Sound("physics/wood/wood_box_impact_hard3.wav") + +SWEP.Primary.ClipSize = -1 -- Size of a clip +SWEP.Primary.DefaultClip = 0 -- Default number of bullets in a clip +SWEP.Primary.Automatic = false -- Automatic/Semi Auto +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 -- Size of a clip +SWEP.Secondary.DefaultClip = 0 -- Default number of bullets in a clip +SWEP.Secondary.Automatic = false -- Automatic/Semi Auto +SWEP.Secondary.Ammo = "" + +--[[--------------------------------------------------------- +Name: SWEP:Initialize() +Desc: Called when the weapon is first loaded +---------------------------------------------------------]] +function SWEP:Initialize() + if CLIENT then self.LastIron = CurTime() end + self:SetHoldType("normal") +end + +function SWEP:Holster() + self:SetIronsights(false) + + return true +end + +-- Check whether an object of this player can be rammed +local function canRam(ply) + return IsValid(ply) and (ply.warranted == true or ply:isWanted() or ply:isArrested()) +end + +-- Ram action when ramming a door +local function ramDoor(ply, trace, ent) + if ply:EyePos():DistToSqr(trace.HitPos) > 2025 or (not GAMEMODE.Config.canforcedooropen and ent:getKeysNonOwnable()) then return false end + + local allowed = false + + -- if we need a warrant to get in + if GAMEMODE.Config.doorwarrants and ent:isKeysOwned() and not ent:isKeysOwnedBy(ply) then + -- if anyone who owns this door has a warrant for their arrest + -- allow the police to smash the door in + for _, v in ipairs(player.GetAll()) do + if ent:isKeysOwnedBy(v) and canRam(v) then + allowed = true + break + end + end + else + -- door warrants not needed, allow warrantless entry + allowed = true + end + + -- Be able to open the door if any member of the door group is warranted + local keysDoorGroup = ent:getKeysDoorGroup() + if GAMEMODE.Config.doorwarrants and keysDoorGroup then + local teamDoors = RPExtraTeamDoors[keysDoorGroup] + if teamDoors then + allowed = false + for _, v in ipairs(player.GetAll()) do + if table.HasValue(teamDoors, v:Team()) and canRam(v) then + allowed = true + break + end + end + end + end + + if CLIENT then return allowed end + + -- Do we have a warrant for this player? + if not allowed then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("warrant_required")) + + return false + end + + ent:keysUnLock() + ent:Fire("open", "", .6) + ent:Fire("setanimation", "open", .6) + + return true +end + +-- Ram action when ramming a vehicle +local function ramVehicle(ply, trace, ent) + if ply:EyePos():DistToSqr(trace.HitPos) > 10000 then return false end + + if CLIENT then return false end -- Ideally this would return true after ent:GetDriver() check + + local driver = ent:GetDriver() + if not IsValid(driver) or not driver.ExitVehicle then return false end + + driver:ExitVehicle() + ent:keysLock() + + return true +end + +-- Ram action when ramming a fading door +local function ramFadingDoor(ply, trace, ent) + if ply:EyePos():DistToSqr(trace.HitPos) > 10000 then return false end + + local Owner = ent:CPPIGetOwner() + + if CLIENT then return canRam(Owner) end + + if not canRam(Owner) then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("warrant_required")) + return false + end + + if not ent.fadeActive then + ent:fadeActivate() + timer.Simple(5, function() if IsValid(ent) and ent.fadeActive then ent:fadeDeactivate() end end) + end + + return true +end + +-- Ram action when ramming a frozen prop +local function ramProp(ply, trace, ent) + if ply:EyePos():DistToSqr(trace.HitPos) > 10000 then return false end + if ent:GetClass() ~= "prop_physics" then return false end + + local Owner = ent:CPPIGetOwner() + + if CLIENT then return canRam(Owner) end + + if not canRam(Owner) then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase(GAMEMODE.Config.copscanunweld and "warrant_required_unweld" or "warrant_required_unfreeze")) + return false + end + + if GAMEMODE.Config.copscanunweld then + constraint.RemoveConstraints(ent, "Weld") + end + + if GAMEMODE.Config.copscanunfreeze then + ent:GetPhysicsObject():EnableMotion(true) + end + + return true +end + +-- Decides the behaviour of the ram function for the given entity +local function getRamFunction(ply, trace) + local ent = trace.Entity + + if not IsValid(ent) then return fp{fn.Id, false} end + + local override = hook.Call("canDoorRam", nil, ply, trace, ent) + + return + override ~= nil and fp{fn.Id, override} or + ent:isDoor() and fp{ramDoor, ply, trace, ent} or + ent:IsVehicle() and fp{ramVehicle, ply, trace, ent} or + ent.fadeActivate and fp{ramFadingDoor, ply, trace, ent} or + ent:GetPhysicsObject():IsValid() and not ent:GetPhysicsObject():IsMoveable() + and fp{ramProp, ply, trace, ent} or + fp{fn.Id, false} -- no ramming was performed +end + +--[[--------------------------------------------------------- +Name: SWEP:PrimaryAttack() +Desc: +attack1 has been pressed +---------------------------------------------------------]] +function SWEP:PrimaryAttack() + if not self:GetIronsights() then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + self:SetNextPrimaryFire(CurTime() + 0.1) + + Owner:LagCompensation(true) + local trace = Owner:GetEyeTrace() + Owner:LagCompensation(false) + + local hasRammed = getRamFunction(Owner, trace)() + + if SERVER then + hook.Call("onDoorRamUsed", GAMEMODE, hasRammed, Owner, trace) + end + + if not hasRammed then return end + + self:SetNextPrimaryFire(CurTime() + 2.5) + + self:SetTotalUsedMagCount(self:GetTotalUsedMagCount() + 1) + + Owner:SetAnimation(PLAYER_ATTACK1) + Owner:EmitSound(self.Sound) + Owner:ViewPunch(Angle(-10, math.Round(util.SharedRandom("DarkRP_DoorRam" .. self:EntIndex() .. "_" .. self:GetTotalUsedMagCount(), -5, 5)), 0)) +end + +function SWEP:SecondaryAttack() + if CLIENT then self.LastIron = CurTime() end + self:SetNextSecondaryFire(CurTime() + 0.30) + self:SetIronsights(not self:GetIronsights()) + if self:GetIronsights() then + self:SetHoldType("rpg") + else + self:SetHoldType("normal") + end +end + +function SWEP:GetViewModelPosition(pos, ang) + local Mul = 1 + + if self.LastIron > CurTime() - 0.25 then + Mul = math.Clamp((CurTime() - self.LastIron) / 0.25, 0, 1) + end + + if self:GetIronsights() then + Mul = 1-Mul + end + + ang:RotateAroundAxis(ang:Right(), - 15 * Mul) + return pos,ang +end + +DarkRP.hookStub{ + name = "canDoorRam", + description = "Called when a player attempts to ram something. Use this to override ram behaviour or to disallow ramming.", + parameters = { + { + name = "ply", + description = "The player using the door ram.", + type = "Player" + }, + { + name = "trace", + description = "The trace containing information about the hit position and ram entity.", + type = "table" + }, + { + name = "ent", + description = "Short for the entity that is about to be hit by the door ram.", + type = "Entity" + } + }, + returns = { + { + name = "override", + description = "Return true to override behaviour, false to disallow ramming and nil (or no value) to defer the decision.", + type = "boolean" + } + }, + realm = "Shared" +} + +if SERVER then + DarkRP.hookStub{ + name = "onDoorRamUsed", + description = "Called when the door ram has been used.", + parameters = { + { + name = "success", + description = "Whether the door ram has been successful in ramming.", + type = "boolean" + }, + { + name = "ply", + description = "The player that used the door ram.", + type = "Player" + }, + { + name = "trace", + description = "The trace containing information about the hit position and ram entity.", + type = "table" + } + }, + returns = { + + } + } +end + +hook.Add("SetupMove", "DarkRP_DoorRamJump", function(ply, mv) + local wep = ply:GetActiveWeapon() + if not wep:IsValid() or wep:GetClass() ~= "door_ram" or not wep.GetIronsights or not wep:GetIronsights() then return end + + mv:SetButtons(bit.band(mv:GetButtons(), bit.bnot(IN_JUMP))) +end) diff --git a/gamemodes/darkrp/entities/weapons/gmod_tool/stools/shareprops.lua b/gamemodes/darkrp/entities/weapons/gmod_tool/stools/shareprops.lua new file mode 100644 index 0000000..530b36e --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/gmod_tool/stools/shareprops.lua @@ -0,0 +1,54 @@ +TOOL.Category = "Falco Prop Protection" +TOOL.Name = "Share props" +TOOL.Command = nil +TOOL.ConfigName = "" + +function TOOL:RightClick(trace) + local ent = trace.Entity + if not IsValid(ent) or CLIENT then return true end + + ent.SharePhysgun1 = nil + ent.ShareGravgun1 = nil + ent.SharePlayerUse1 = nil + ent.ShareEntityDamage1 = nil + ent.ShareToolgun1 = nil + + ent.AllowedPlayers = nil + return true +end + +function TOOL:LeftClick(trace) + local ent = trace.Entity + if not IsValid(ent) or CLIENT then return true end + + local ply = self:GetOwner() + + local Physgun = ent.SharePhysgun1 or false + local GravGun = ent.ShareGravgun1 or false + local PlayerUse = ent.SharePlayerUse1 or false + local Damage = ent.ShareEntityDamage1 or false + local Toolgun = ent.ShareToolgun1 or false + + -- This big usermessage will be too big if you select 63 players, since that will not happen I can't be arsed to solve it + umsg.Start("FPP_ShareSettings", ply) + umsg.Entity(ent) + umsg.Bool(Physgun) + umsg.Bool(GravGun) + umsg.Bool(PlayerUse) + umsg.Bool(Damage) + umsg.Bool(Toolgun) + if ent.AllowedPlayers then + umsg.Long(#ent.AllowedPlayers) + for k,v in pairs(ent.AllowedPlayers) do + umsg.Entity(v) + end + end + umsg.End() + return true +end + +if CLIENT then + language.Add("Tool.shareprops.name", "Share tool") + language.Add("Tool.shareprops.desc", "Change sharing settings per prop") + language.Add("Tool.shareprops.0", "Left click: shares a prop. Right click unshares a prop") +end diff --git a/gamemodes/darkrp/entities/weapons/keys/cl_menu.lua b/gamemodes/darkrp/entities/weapons/keys/cl_menu.lua new file mode 100644 index 0000000..879a686 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/keys/cl_menu.lua @@ -0,0 +1,210 @@ +local function AddButtonToFrame(Frame) + Frame:SetTall(Frame:GetTall() + 110) + + local button = vgui.Create("DButton", Frame) + button:SetPos(10, Frame:GetTall() - 110) + button:SetSize(180, 100) + + Frame.buttonCount = (Frame.buttonCount or 0) + 1 + Frame.lastButton = button + return button +end + +DarkRP.stub{ + name = "openKeysMenu", + description = "Open the keys/F2 menu.", + parameters = {}, + realm = "Client", + returns = {}, + metatable = DarkRP +} + +DarkRP.hookStub{ + name = "onKeysMenuOpened", + description = "Called when the keys menu is opened.", + parameters = { + { + name = "ent", + description = "The door entity.", + type = "Entity" + }, + { + name = "Frame", + description = "The keys menu frame.", + type = "Panel" + } + }, + returns = { + }, + realm = "Client" +} + +local KeyFrameVisible = false + +local function openMenu(setDoorOwnerAccess, doorSettingsAccess) + if KeyFrameVisible then return end + local trace = LocalPlayer():GetEyeTrace() + local ent = trace.Entity + -- Don't open the menu if the entity is not ownable, the entity is too far away or the door settings are not loaded yet + if not IsValid(ent) or not ent:isKeysOwnable() or trace.HitPos:DistToSqr(LocalPlayer():EyePos()) > 40000 then return end + + KeyFrameVisible = true + local Frame = vgui.Create("DFrame") + Frame:SetSize(200, 30) -- Base size + Frame.btnMaxim:SetVisible(false) + Frame.btnMinim:SetVisible(false) + Frame:SetVisible(true) + Frame:MakePopup() + Frame:ParentToHUD() + + function Frame:Think() + local tr = LocalPlayer():GetEyeTrace() + local LAEnt = tr.Entity + if not IsValid(LAEnt) or not LAEnt:isKeysOwnable() or tr.HitPos:DistToSqr(LocalPlayer():EyePos()) > 40000 then + self:Close() + end + if not self.Dragging then return end + local x = gui.MouseX() - self.Dragging[1] + local y = gui.MouseY() - self.Dragging[2] + x = math.Clamp(x, 0, ScrW() - self:GetWide()) + y = math.Clamp(y, 0, ScrH() - self:GetTall()) + self:SetPos(x, y) + end + + local entType = DarkRP.getPhrase(ent:IsVehicle() and "vehicle" or "door") + Frame:SetTitle(DarkRP.getPhrase("x_options", entType:gsub("^%a", string.upper))) + + function Frame:Close() + KeyFrameVisible = false + self:SetVisible(false) + self:Remove() + end + + -- All the buttons + + if ent:isKeysOwnedBy(LocalPlayer()) then + local Owndoor = AddButtonToFrame(Frame) + Owndoor:SetText(DarkRP.getPhrase("sell_x", entType)) + Owndoor.DoClick = function() RunConsoleCommand("darkrp", "toggleown") Frame:Close() end + + local AddOwner = AddButtonToFrame(Frame) + AddOwner:SetText(DarkRP.getPhrase("add_owner")) + AddOwner.DoClick = function() + local menu = DermaMenu() + menu.found = false + for _, v in ipairs(DarkRP.nickSortedPlayers()) do + if not ent:isKeysOwnedBy(v) and not ent:isKeysAllowedToOwn(v) then + local steamID = v:SteamID() + menu.found = true + menu:AddOption(v:Nick(), function() RunConsoleCommand("darkrp", "ao", steamID) end) + end + end + if not menu.found then + menu:AddOption(DarkRP.getPhrase("noone_available"), function() end) + end + menu:Open() + end + + local RemoveOwner = AddButtonToFrame(Frame) + RemoveOwner:SetText(DarkRP.getPhrase("remove_owner")) + RemoveOwner.DoClick = function() + local menu = DermaMenu() + for _, v in ipairs(DarkRP.nickSortedPlayers()) do + if (ent:isKeysOwnedBy(v) and not ent:isMasterOwner(v)) or ent:isKeysAllowedToOwn(v) then + local steamID = v:SteamID() + menu.found = true + menu:AddOption(v:Nick(), function() RunConsoleCommand("darkrp", "ro", steamID) end) + end + end + if not menu.found then + menu:AddOption(DarkRP.getPhrase("noone_available"), function() end) + end + menu:Open() + end + if not ent:isMasterOwner(LocalPlayer()) then + RemoveOwner:SetDisabled(true) + end + end + + if doorSettingsAccess then + local DisableOwnage = AddButtonToFrame(Frame) + DisableOwnage:SetText(DarkRP.getPhrase(ent:getKeysNonOwnable() and "allow_ownership" or "disallow_ownership")) + DisableOwnage.DoClick = function() Frame:Close() RunConsoleCommand("darkrp", "toggleownable") end + end + + if doorSettingsAccess and (ent:isKeysOwned() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or hasTeams) or ent:isKeysOwnedBy(LocalPlayer()) then + local DoorTitle = AddButtonToFrame(Frame) + DoorTitle:SetText(DarkRP.getPhrase("set_x_title", entType)) + DoorTitle.DoClick = function() + Derma_StringRequest(DarkRP.getPhrase("set_x_title", entType), DarkRP.getPhrase("set_x_title_long", entType), "", function(text) + RunConsoleCommand("darkrp", "title", text) + if IsValid(Frame) then + Frame:Close() + end + end, + function() end, DarkRP.getPhrase("ok"), DarkRP.getPhrase("cancel")) + end + end + + if not ent:isKeysOwned() and not ent:getKeysNonOwnable() and not ent:getKeysDoorGroup() and not ent:getKeysDoorTeams() or not ent:isKeysOwnedBy(LocalPlayer()) and ent:isKeysAllowedToOwn(LocalPlayer()) then + local Owndoor = AddButtonToFrame(Frame) + Owndoor:SetText(DarkRP.getPhrase("buy_x", entType)) + Owndoor.DoClick = function() RunConsoleCommand("darkrp", "toggleown") Frame:Close() end + end + + if doorSettingsAccess then + local EditDoorGroups = AddButtonToFrame(Frame) + EditDoorGroups:SetText(DarkRP.getPhrase("edit_door_group")) + EditDoorGroups.DoClick = function() + local menu = DermaMenu() + local groups = menu:AddSubMenu(DarkRP.getPhrase("door_groups")) + local teams = menu:AddSubMenu(DarkRP.getPhrase("jobs")) + local add = teams:AddSubMenu(DarkRP.getPhrase("add")) + local remove = teams:AddSubMenu(DarkRP.getPhrase("remove")) + + menu:AddOption(DarkRP.getPhrase("none"), function() + RunConsoleCommand("darkrp", "togglegroupownable") + if IsValid(Frame) then Frame:Close() end + end) + + for k in pairs(RPExtraTeamDoors) do + groups:AddOption(k, function() + RunConsoleCommand("darkrp", "togglegroupownable", k) + if IsValid(Frame) then Frame:Close() end + end) + end + + local doorTeams = ent:getKeysDoorTeams() + for k, v in pairs(RPExtraTeams) do + local which = (not doorTeams or not doorTeams[k]) and add or remove + which:AddOption(v.name, function() + RunConsoleCommand("darkrp", "toggleteamownable", k) + if IsValid(Frame) then Frame:Close() end + end) + end + + menu:Open() + end + end + + if Frame.buttonCount == 1 then + Frame.lastButton:DoClick() + elseif Frame.buttonCount == 0 or not Frame.buttonCount then + Frame:Close() + KeyFrameVisible = true + timer.Simple(0.3, function() KeyFrameVisible = false end) + end + + + hook.Call("onKeysMenuOpened", nil, ent, Frame) + + Frame:Center() + Frame:SetSkin(GAMEMODE.Config.DarkRPSkin) +end + +function DarkRP.openKeysMenu(um) + CAMI.PlayerHasAccess(LocalPlayer(), "DarkRP_SetDoorOwner", function(setDoorOwnerAccess) + CAMI.PlayerHasAccess(LocalPlayer(), "DarkRP_ChangeDoorSettings", fp{openMenu, setDoorOwnerAccess}) + end) +end +usermessage.Hook("KeysMenu", DarkRP.openKeysMenu) diff --git a/gamemodes/darkrp/entities/weapons/keys/shared.lua b/gamemodes/darkrp/entities/weapons/keys/shared.lua new file mode 100644 index 0000000..08d774f --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/keys/shared.lua @@ -0,0 +1,163 @@ +AddCSLuaFile() + +if SERVER then + AddCSLuaFile("cl_menu.lua") +end + +if CLIENT then + SWEP.Slot = 1 + SWEP.SlotPos = 1 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false + + include("cl_menu.lua") +end + +SWEP.PrintName = "Keys" +SWEP.Author = "DarkRP Developers" +SWEP.Instructions = "Left click to lock\nRight click to unlock\nReload for door settings or animation menu" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.IsDarkRPKeys = true + +SWEP.WorldModel = "" + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "rpg" + +SWEP.UseHands = true + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = "DarkRP (Utility)" +SWEP.Sound = "doors/door_latch3.wav" + +SWEP.Primary.Delay = 0.3 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" + +SWEP.Secondary.Delay = 0.3 +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + +function SWEP:Initialize() + self:SetHoldType("normal") +end + +function SWEP:Deploy() + if CLIENT or not IsValid(self:GetOwner()) then return true end + self:GetOwner():DrawWorldModel(false) + return true +end + +function SWEP:Holster() + return true +end + +function SWEP:PreDrawViewModel() + return true +end + +local function lookingAtLockable(ply, ent, hitpos) + local eyepos = ply:EyePos() + return IsValid(ent) + and ent:isKeysOwnable() + and ( + ent:isDoor() and eyepos:DistToSqr(hitpos) < 2000 + or + ent:IsVehicle() and eyepos:DistToSqr(hitpos) < 4000 + ) +end + +local function lockUnlockAnimation(ply, snd) + ply:EmitSound("npc/metropolice/gear" .. math.random(1, 6) .. ".wav") + timer.Simple(0.9, function() if IsValid(ply) then ply:EmitSound(snd) end end) + + umsg.Start("anim_keys") + umsg.Entity(ply) + umsg.String("usekeys") + umsg.End() + + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_GMOD_GESTURE_ITEM_PLACE, true) +end + +local function doKnock(ply, sound) + ply:EmitSound(sound, 100, math.random(90, 110)) + + umsg.Start("anim_keys") + umsg.Entity(ply) + umsg.String("knocking") + umsg.End() + + ply:AnimRestartGesture(GESTURE_SLOT_ATTACK_AND_RELOAD, ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST, true) +end + +function SWEP:PrimaryAttack() + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + local trace = Owner:GetEyeTrace() + + local ent = trace.Entity + + if not lookingAtLockable(Owner, ent, trace.HitPos) then return end + + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + if CLIENT then return end + + if Owner:canKeysLock(ent) then + ent:keysLock() -- Lock the door immediately so it won't annoy people + lockUnlockAnimation(Owner, self.Sound) + elseif ent:IsVehicle() then + DarkRP.notify(Owner, 1, 3, DarkRP.getPhrase("do_not_own_ent")) + else + doKnock(Owner, "physics/wood/wood_crate_impact_hard2.wav") + end +end + +function SWEP:SecondaryAttack() + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + local trace = Owner:GetEyeTrace() + + local ent = trace.Entity + + if not lookingAtLockable(Owner, ent, trace.HitPos) then return end + + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + + if CLIENT then return end + + if Owner:canKeysUnlock(ent) then + ent:keysUnLock() -- Unlock the door immediately so it won't annoy people + lockUnlockAnimation(Owner, self.Sound) + elseif ent:IsVehicle() then + DarkRP.notify(Owner, 1, 3, DarkRP.getPhrase("do_not_own_ent")) + else + doKnock(Owner, "physics/wood/wood_crate_impact_hard3.wav") + end +end + +function SWEP:Reload() + local trace = self:GetOwner():GetEyeTrace() + + local ent = trace.Entity + + if not IsValid(ent) or ((not ent:isDoor() and not ent:IsVehicle()) or self:GetOwner():EyePos():DistToSqr(trace.HitPos) > 40000) then + if CLIENT and not DarkRP.disabledDefaults["modules"]["animations"] then RunConsoleCommand("_DarkRP_AnimationMenu") end + return + end + if SERVER then + umsg.Start("KeysMenu", self:GetOwner()) + umsg.End() + end +end diff --git a/gamemodes/darkrp/entities/weapons/lockpick/shared.lua b/gamemodes/darkrp/entities/weapons/lockpick/shared.lua new file mode 100644 index 0000000..dc0d89b --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/lockpick/shared.lua @@ -0,0 +1,309 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Slot = 5 + SWEP.SlotPos = 1 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +-- Variables that are used on both client and server + +SWEP.PrintName = "Lock Pick" +SWEP.Author = "DarkRP Developers" +SWEP.Instructions = "Left or right click to pick a lock" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.IsDarkRPLockpick = true + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.ViewModel = Model("models/weapons/c_crowbar.mdl") +SWEP.WorldModel = Model("models/weapons/w_crowbar.mdl") + +SWEP.UseHands = true + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = "DarkRP (Utility)" + +SWEP.Sound = Sound("physics/wood/wood_box_impact_hard3.wav") + +SWEP.Primary.ClipSize = -1 -- Size of a clip +SWEP.Primary.DefaultClip = 0 -- Default number of bullets in a clip +SWEP.Primary.Automatic = false -- Automatic/Semi Auto +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 -- Size of a clip +SWEP.Secondary.DefaultClip = -1 -- Default number of bullets in a clip +SWEP.Secondary.Automatic = false -- Automatic/Semi Auto +SWEP.Secondary.Ammo = "" + +function SWEP:SetupDataTables() + self:NetworkVar("Bool", 0, "IsLockpicking") + self:NetworkVar("Float", 0, "LockpickStartTime") + self:NetworkVar("Float", 1, "LockpickEndTime") + self:NetworkVar("Float", 2, "NextSoundTime") + self:NetworkVar("Int", 0, "TotalLockpicks") + self:NetworkVar("Entity", 0, "LockpickEnt") +end + +function SWEP:Initialize() + self:SetHoldType("normal") +end + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + 0.5) + if self:GetIsLockpicking() then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + Owner:LagCompensation(true) + local trace = Owner:GetEyeTrace() + Owner:LagCompensation(false) + local ent = trace.Entity + + if not IsValid(ent) or ent.DarkRPCanLockpick == false then return end + local canLockpick = hook.Call("canLockpick", nil, Owner, ent, trace) + + if canLockpick == false then return end + if canLockpick ~= true and ( + trace.HitPos:DistToSqr(Owner:GetShootPos()) > 10000 or + (not GAMEMODE.Config.canforcedooropen and ent:getKeysNonOwnable()) or + (not ent:isDoor() and not ent:IsVehicle() and not string.find(string.lower(ent:GetClass()), "vehicle") and (not GAMEMODE.Config.lockpickfading or not ent.isFadingDoor)) + ) then + return + end + + self:SetHoldType("pistol") + + self:SetIsLockpicking(true) + self:SetLockpickEnt(ent) + self:SetLockpickStartTime(CurTime()) + local endDelta = hook.Call("lockpickTime", nil, Owner, ent) or util.SharedRandom("DarkRP_Lockpick" .. self:EntIndex() .. "_" .. self:GetTotalLockpicks(), 10, 30) + self:SetLockpickEndTime(CurTime() + endDelta) + self:SetTotalLockpicks(self:GetTotalLockpicks() + 1) + + + if IsFirstTimePredicted() then + hook.Call("lockpickStarted", nil, Owner, ent, trace) + end + + if CLIENT then + self.Dots = "" + self.NextDotsTime = SysTime() + 0.5 + return + end + + local onFail = function(ply) if ply == Owner then hook.Call("onLockpickCompleted", nil, ply, false, ent) end end + + -- Lockpick fails when dying or disconnecting + hook.Add("PlayerDeath", self, fc{onFail, fn.Flip(fn.Const)}) + hook.Add("PlayerDisconnected", self, fc{onFail, fn.Flip(fn.Const)}) + -- Remove hooks when finished + hook.Add("onLockpickCompleted", self, fc{fp{hook.Remove, "PlayerDisconnected", self}, fp{hook.Remove, "PlayerDeath", self}}) +end + +function SWEP:Holster() + if self:GetIsLockpicking() and self:GetLockpickEndTime() ~= 0 then + self:Fail() + end + return true +end + +function SWEP:Succeed() + self:SetHoldType("normal") + + local ent = self:GetLockpickEnt() + self:SetIsLockpicking(false) + self:SetLockpickEnt(nil) + + if not IsValid(ent) then return end + + local override = hook.Call("onLockpickCompleted", nil, self:GetOwner(), true, ent) + + if override then return end + + if ent.isFadingDoor and ent.fadeActivate and not ent.fadeActive then + ent:fadeActivate() + if IsFirstTimePredicted() then timer.Simple(5, function() if IsValid(ent) and ent.fadeActive then ent:fadeDeactivate() end end) end + elseif ent.Fire then + ent:keysUnLock() + ent:Fire("open", "", .6) + ent:Fire("setanimation", "open", .6) + end +end + +function SWEP:Fail() + self:SetIsLockpicking(false) + self:SetHoldType("normal") + + hook.Call("onLockpickCompleted", nil, self:GetOwner(), false, self:GetLockpickEnt()) + self:SetLockpickEnt(nil) +end + +local colorBackground = Color(10, 10, 10, 120) +local dots = { + [0] = ".", + [1] = "..", + [2] = "...", + [3] = "" +} +function SWEP:Think() + if not self:GetIsLockpicking() or self:GetLockpickEndTime() == 0 then return end + + if CurTime() >= self:GetNextSoundTime() then + self:SetNextSoundTime(CurTime() + 1) + local snd = {1,3,4} + self:EmitSound("weapons/357/357_reload" .. tostring(snd[math.Round(util.SharedRandom("DarkRP_LockpickSnd" .. CurTime(), 1, #snd))]) .. ".wav", 50, 100) + end + if CLIENT and (not self.NextDotsTime or SysTime() >= self.NextDotsTime) then + self.NextDotsTime = SysTime() + 0.5 + self.Dots = self.Dots or "" + local len = string.len(self.Dots) + + self.Dots = dots[len] + end + + local trace = self:GetOwner():GetEyeTrace() + if not IsValid(trace.Entity) or trace.Entity ~= self:GetLockpickEnt() or trace.HitPos:DistToSqr(self:GetOwner():GetShootPos()) > 10000 then + self:Fail() + elseif self:GetLockpickEndTime() <= CurTime() then + self:Succeed() + end +end + +function SWEP:DrawHUD() + if not self:GetIsLockpicking() or self:GetLockpickEndTime() == 0 then return end + + self.Dots = self.Dots or "" + local w = ScrW() + local h = ScrH() + local x, y, width, height = w / 2 - w / 10, h / 2 - 60, w / 5, h / 15 + draw.RoundedBox(8, x, y, width, height, colorBackground) + + local time = self:GetLockpickEndTime() - self:GetLockpickStartTime() + local curtime = CurTime() - self:GetLockpickStartTime() + local status = math.Clamp(curtime / time, 0, 1) + local BarWidth = status * (width - 16) + local cornerRadius = math.Min(8, BarWidth / 3 * 2 - BarWidth / 3 * 2 % 2) + draw.RoundedBox(cornerRadius, x + 8, y + 8, BarWidth, height - 16, Color(255 - (status * 255), 0 + (status * 255), 0, 255)) + + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("picking_lock") .. self.Dots, "Trebuchet24", w / 2, y + height / 2, color_white, 1, 1) +end + +function SWEP:SecondaryAttack() + self:PrimaryAttack() +end + + +DarkRP.hookStub{ + name = "canLockpick", + description = "Whether an entity can be lockpicked.", + parameters = { + { + name = "ply", + description = "The player attempting to lockpick an entity.", + type = "Player" + }, + { + name = "ent", + description = "The entity being lockpicked.", + type = "Entity" + }, + { + name = "trace", + description = "The trace result.", + type = "table" + } + }, + returns = { + { + name = "allowed", + description = "Whether the entity can be lockpicked", + type = "boolean" + } + }, + realm = "Shared" +} + +DarkRP.hookStub{ + name = "lockpickStarted", + description = "Called when a player is about to pick a lock.", + parameters = { + { + name = "ply", + description = "The player that is about to pick a lock.", + type = "Player" + }, + { + name = "ent", + description = "The entity being lockpicked.", + type = "Entity" + }, + { + name = "trace", + description = "The trace result.", + type = "table" + } + }, + returns = {}, + realm = "Shared" +} + +DarkRP.hookStub{ + name = "onLockpickCompleted", + description = "Result of a player attempting to lockpick an entity.", + parameters = { + { + name = "ply", + description = "The player attempting to lockpick the entity.", + type = "Player" + }, + { + name = "success", + description = "Whether the player succeeded in lockpicking the entity.", + type = "boolean" + }, + { + name = "ent", + description = "The entity that was lockpicked.", + type = "Entity" + }, + }, + returns = { + { + name = "override", + description = "Return true to override default behaviour, which is opening the (fading) door.", + type = "boolean" + } + }, + realm = "Shared" +} + +DarkRP.hookStub{ + name = "lockpickTime", + description = "The length of time, in seconds, it takes to lockpick an entity.", + parameters = { + { + name = "ply", + description = "The player attempting to lockpick an entity.", + type = "Player" + }, + { + name = "ent", + description = "The entity being lockpicked.", + type = "Entity" + }, + }, + returns = { + { + name = "time", + description = "Seconds in which it takes a player to lockpick an entity", + type = "number" + } + }, + realm = "Shared" +} diff --git a/gamemodes/darkrp/entities/weapons/ls_sniper/cl_init.lua b/gamemodes/darkrp/entities/weapons/ls_sniper/cl_init.lua new file mode 100644 index 0000000..895a273 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/ls_sniper/cl_init.lua @@ -0,0 +1,17 @@ +include("shared.lua") +local deltas = {-44, -34, -24, -14, 44, 34, 24, 14} +function SWEP:DrawHUD() + if self:GetScopeLevel() < 2 then return end + + --Width hairs + draw.RoundedBox(1, ScrW() / 2 - 54, ScrH() / 2, 50, 1, color_black) + draw.RoundedBox(1, ScrW() / 2 + 4, ScrH() / 2, 50, 1, color_black) + + draw.RoundedBox(1, ScrW() / 2, ScrH() / 2 - 54, 1, 50, color_black) + draw.RoundedBox(1, ScrW() / 2, ScrH() / 2 + 4, 1, 50, color_black) + + for _, v in ipairs(deltas) do + draw.RoundedBox(1, ScrW() / 2 + v, ScrH() / 2 - 5, 1, 11, color_black) + draw.RoundedBox(1, ScrW() / 2 - 5, ScrH() / 2 + v, 11, 1, color_black) + end +end diff --git a/gamemodes/darkrp/entities/weapons/ls_sniper/shared.lua b/gamemodes/darkrp/entities/weapons/ls_sniper/shared.lua new file mode 100644 index 0000000..4ba44db --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/ls_sniper/shared.lua @@ -0,0 +1,84 @@ +AddCSLuaFile() + +if SERVER then + AddCSLuaFile("cl_init.lua") +end + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 0 + SWEP.SlotPos = 0 + SWEP.IconLetter = "n" + + killicon.AddFont("ls_sniper", "CSKillIcons", SWEP.IconLetter, Color(200, 200, 200, 255)) +end + +DEFINE_BASECLASS("weapon_cs_base2") + +SWEP.PrintName = "Silenced Sniper" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_snip_g3sg1.mdl" +SWEP.WorldModel = "models/weapons/w_snip_g3sg1.mdl" + +SWEP.Weight = 3 + +SWEP.HoldType = "ar2" +SWEP.LoweredHoldType = "passive" + +SWEP.Primary.Sound = Sound("Weapon_M4A1.Silenced") +SWEP.Primary.Damage = 100 +SWEP.Primary.Recoil = 0.03 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.0001 - .05 +SWEP.Primary.ClipSize = 25 +SWEP.Primary.Delay = 0.7 +SWEP.Primary.DefaultClip = 75 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "smg1" +SWEP.IronSightsPos = Vector(0, 0, 0) -- this is just to make it disappear so it doesn't show up whilst scoped + +function SWEP:SetupDataTables() + BaseClass.SetupDataTables(self) + -- Int 0 = BurstBulletNum + -- Int 1 = TotalUsedMagCount + self:NetworkVar("Int", 2, "ScopeLevel") +end + +function SWEP:Deploy() + self:GetOwner():SetFOV(0, 0) + + self:SetScopeLevel(0) + + return BaseClass.Deploy(self) +end + +function SWEP:Holster() + self:GetOwner():SetFOV(0, 0) + + self:SetScopeLevel(0) + + return BaseClass.Holster(self) +end + +local zoomFOV = {0, 0, 25, 5} +function SWEP:SecondaryAttack() + if not self.IronSightsPos then return end + + self:SetNextSecondaryFire(CurTime() + 0.1) + + self:SetScopeLevel((self:GetScopeLevel() + 1) % 4) + self:SetIronsights(self:GetScopeLevel() > 0) + + self:GetOwner():SetFOV(zoomFOV[self:GetScopeLevel() + 1], 0) +end + +function SWEP:Reload() + self:GetOwner():SetFOV(0, 0) + + self:SetScopeLevel(0) + + return BaseClass.Reload(self) +end diff --git a/gamemodes/darkrp/entities/weapons/med_kit/shared.lua b/gamemodes/darkrp/entities/weapons/med_kit/shared.lua new file mode 100644 index 0000000..29faab4 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/med_kit/shared.lua @@ -0,0 +1,81 @@ +if SERVER then + AddCSLuaFile("shared.lua") +end + +SWEP.PrintName = "Medic Kit" +SWEP.Author = "DarkRP Developers" +SWEP.Slot = 4 +SWEP.SlotPos = 0 +SWEP.Description = "Heals the wounded." +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "Left click to heal someone\nRight click to heal yourself" +SWEP.IsDarkRPMedKit = true + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = "DarkRP (Utility)" + +SWEP.ViewModel = "models/weapons/c_medkit.mdl" +SWEP.WorldModel = "models/weapons/w_medkit.mdl" +SWEP.UseHands = true + +SWEP.Primary.Recoil = 0 +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 1 +SWEP.Primary.Automatic = true +SWEP.Primary.Delay = 0.1 +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.Recoil = 0 +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 1 +SWEP.Secondary.Automatic = true +SWEP.Secondary.Delay = 0.3 +SWEP.Secondary.Ammo = "none" + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + local found + local lastDot = -1 -- the opposite of what you're looking at + Owner:LagCompensation(true) + local aimVec = Owner:GetAimVector() + local shootPos = Owner:GetShootPos() + + for _, v in ipairs(player.GetAll()) do + local maxhealth = v:GetMaxHealth() or 100 + local targetShootPos = v:GetShootPos() + if v == Owner or targetShootPos:DistToSqr(shootPos) > 7225 or v:Health() >= maxhealth or not v:Alive() then continue end + + local direction = targetShootPos - shootPos + direction:Normalize() + local dot = direction:Dot(aimVec) + + -- Looking more in the direction of this player + if dot > lastDot then + lastDot = dot + found = v + end + end + Owner:LagCompensation(false) + + if found then + found:SetHealth(found:Health() + 1) + self:EmitSound("hl1/fvox/boop.wav", 150, math.max(found:Health() / found:GetMaxHealth() * 100, 25), 1, CHAN_AUTO) + end +end + +function SWEP:SecondaryAttack() + self:SetNextSecondaryFire(CurTime() + self.Secondary.Delay) + local ply = self:GetOwner() + local maxhealth = ply:GetMaxHealth() or 100 + if ply:Health() < maxhealth then + ply:SetHealth(ply:Health() + 1) + self:EmitSound("hl1/fvox/boop.wav", 150, math.max(ply:Health() / ply:GetMaxHealth() * 100, 25), 1, CHAN_AUTO) + end +end diff --git a/gamemodes/darkrp/entities/weapons/pocket/cl_menu.lua b/gamemodes/darkrp/entities/weapons/pocket/cl_menu.lua new file mode 100644 index 0000000..b287584 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/pocket/cl_menu.lua @@ -0,0 +1,128 @@ +local meta = FindMetaTable("Player") +local pocket = {} +local frame +local reload + +--[[--------------------------------------------------------------------------- +Stubs +---------------------------------------------------------------------------]] +DarkRP.stub{ + name = "openPocketMenu", + description = "Open the DarkRP pocket menu.", + realm = "Client", + parameters = { + }, + returns = { + }, + metatable = DarkRP +} + +--[[--------------------------------------------------------------------------- +Interface functions +---------------------------------------------------------------------------]] +function meta:getPocketItems() + if self ~= LocalPlayer() then return nil end + + return pocket +end + +function DarkRP.openPocketMenu() + if IsValid(frame) and frame:IsVisible() then return end + local wep = LocalPlayer():GetActiveWeapon() + if not wep:IsValid() or wep:GetClass() ~= "pocket" then return end + + if not pocket then + pocket = {} + + return + end + + if table.IsEmpty(pocket) then return end + frame = vgui.Create("DFrame") + + local count = GAMEMODE.Config.pocketitems or GM.Config.pocketitems + frame:SetSize(345, 32 + 64 * math.ceil(count / 5) + 3 * math.ceil(count / 5)) + frame:SetTitle(DarkRP.getPhrase("drop_item")) + frame.btnMaxim:SetVisible(false) + frame.btnMinim:SetVisible(false) + frame:SetDraggable(false) + frame:MakePopup() + frame:Center() + + local Scroll = vgui.Create("DScrollPanel", frame) + Scroll:Dock(FILL) + + local sbar = Scroll:GetVBar() + sbar:SetWide(3) + frame.List = vgui.Create("DIconLayout", Scroll) + frame.List:Dock(FILL) + frame.List:SetSpaceY(3) + frame.List:SetSpaceX(3) + reload() + frame:SetSkin(GAMEMODE.Config.DarkRPSkin) +end +net.Receive("DarkRP_PocketMenu", DarkRP.openPocketMenu) + +--[[--------------------------------------------------------------------------- +UI +---------------------------------------------------------------------------]] +function reload() + if not IsValid(frame) or not frame:IsVisible() then return end + if not pocket or next(pocket) == nil then frame:Close() return end + + local itemCount = table.Count(pocket) + + frame.List:Clear() + local items = {} + + for k, v in pairs(pocket) do + local ListItem = frame.List:Add("DPanel") + ListItem:SetSize(64, 64) + + local icon = vgui.Create("SpawnIcon", ListItem) + icon:SetModel(v.model) + icon:SetSize(64, 64) + icon:SetTooltip() + icon.DoClick = function(self) + icon:SetTooltip() + + net.Start("DarkRP_spawnPocket") + net.WriteFloat(k) + net.SendToServer() + pocket[k] = nil + + itemCount = itemCount - 1 + + if itemCount == 0 then + frame:Close() + return + end + + fn.Map(self.Remove, items) + items = {} + + local wep = LocalPlayer():GetActiveWeapon() + + wep:SetHoldType("pistol") + timer.Simple(0.2, function() + if wep:IsValid() then + wep:SetHoldType("normal") + end + end) + end + + table.insert(items, icon) + end + if itemCount < GAMEMODE.Config.pocketitems then + for _ = 1, GAMEMODE.Config.pocketitems - itemCount do + local ListItem = frame.List:Add("DPanel") + ListItem:SetSize(64, 64) + end + end +end + +local function retrievePocket() + pocket = net.ReadTable() + reload() +end +net.Receive("DarkRP_Pocket", retrievePocket) diff --git a/gamemodes/darkrp/entities/weapons/pocket/shared.lua b/gamemodes/darkrp/entities/weapons/pocket/shared.lua new file mode 100644 index 0000000..240b214 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/pocket/shared.lua @@ -0,0 +1,146 @@ +AddCSLuaFile() + +if SERVER then + AddCSLuaFile("cl_menu.lua") + include("sv_init.lua") +end + +if CLIENT then + include("cl_menu.lua") +end + +SWEP.PrintName = "Pocket" +SWEP.Slot = 1 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.DrawCrosshair = true + +SWEP.Base = "weapon_cs_base2" + +SWEP.Author = "DarkRP Developers" +SWEP.Instructions = "Left click to pick up\nRight click to drop\nReload to open the menu" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.IsDarkRPPocket = true + +SWEP.IconLetter = "" + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "rpg" +SWEP.WorldModel = "" + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = "DarkRP (Utility)" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + +function SWEP:Initialize() + self:SetHoldType("normal") +end + +function SWEP:Deploy() + return true +end + +function SWEP:DrawWorldModel() end + +function SWEP:PreDrawViewModel(vm) + return true +end + +function SWEP:Holster() + if not SERVER then return true end + + local Owner = self:GetOwner() + Owner:DrawViewModel(true) + Owner:DrawWorldModel(true) + + return true +end + +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + 0.2) + + if not SERVER then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + local ent = Owner:GetEyeTrace().Entity + local canPickup, message = hook.Call("canPocket", GAMEMODE, Owner, ent) + + if not canPickup then + if message then DarkRP.notify(Owner, 1, 4, message) end + return + end + + Owner:addPocketItem(ent) +end + +function SWEP:SecondaryAttack() + if not SERVER then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + local maxK = 0 + + for k in pairs(Owner:getPocketItems()) do + if k < maxK then continue end + maxK = k + end + + if maxK == 0 then + DarkRP.notify(Owner, 1, 4, DarkRP.getPhrase("pocket_no_items")) + return + end + + if SERVER then + local canPickup, message = hook.Call("canDropPocketItem", nil, Owner, maxK, Owner.darkRPPocket[maxK]) + if canPickup == false then + if message then DarkRP.notify(Owner, 1, 4, message) end + return + end + end + + Owner:dropPocketItem(maxK) +end + +function SWEP:Reload() + if CLIENT then + DarkRP.openPocketMenu() + end + + if SERVER and game.SinglePlayer() then + net.Start("DarkRP_PocketMenu") + net.Send(self:GetOwner()) + end +end + +local meta = FindMetaTable("Player") +DarkRP.stub{ + name = "getPocketItems", + description = "Get a player's pocket items.", + parameters = { + }, + returns = { + { + name = "items", + description = "A table containing crucial information about the items in the pocket.", + type = "table" + } + }, + metatable = meta, + realm = "Shared" +} diff --git a/gamemodes/darkrp/entities/weapons/pocket/sv_init.lua b/gamemodes/darkrp/entities/weapons/pocket/sv_init.lua new file mode 100644 index 0000000..77df5de --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/pocket/sv_init.lua @@ -0,0 +1,375 @@ +local meta = FindMetaTable("Player") + +--[[--------------------------------------------------------------------------- +Stubs +---------------------------------------------------------------------------]] +DarkRP.stub{ + name = "dropPocketItem", + description = "Make the player drop an item from the pocket.", + parameters = { + { + name = "ent", + description = "The entity to drop.", + type = "Entity", + optional = false + } + }, + returns = { + }, + metatable = meta +} + +DarkRP.stub{ + name = "addPocketItem", + description = "Add an item to the pocket of the player.", + parameters = { + { + name = "ent", + description = "The entity to add.", + type = "Entity", + optional = false + } + }, + returns = { + }, + metatable = meta +} + +DarkRP.stub{ + name = "removePocketItem", + description = "Remove an item from the pocket of the player.", + parameters = { + { + name = "item", + description = "The index of the entity to remove from pocket.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = meta +} + +DarkRP.hookStub{ + name = "canPocket", + description = "Whether a player can pocket a certain item.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "item", + description = "The item to be pocketed.", + type = "Entity" + } + }, + returns = { + { + name = "answer", + description = "Whether the entity can be pocketed.", + type = "boolean" + }, + { + name = "message", + description = "The message to send to the player when the answer is false.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "onPocketItemAdded", + description = "Called when an entity is added to the pocket.", + parameters = { + { + name = "ply", + description = "The pocket holder.", + type = "Player" + }, + { + name = "ent", + description = "The entity.", + type = "Entity" + }, + { + name = "serialized", + description = "The serialized version of the pocketed entity.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "canDropPocketItem", + description = "Whether someone is allowed to drop something from their pocket.", + parameters = { + { + name = "ply", + description = "The pocket holder.", + type = "Player" + }, + { + name = "item", + description = "The pocket item's index in the pocket.", + type = "table" + }, + { + name = "serialized", + description = "The pocket item.", + type = "table" + } + }, + returns = { + { + name = "answer", + description = "Whether the item can be dropped.", + type = "boolean" + }, + { + name = "message", + description = "The message to send to the player when the answer is false.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "onPocketItemRemoved", + description = "Called when an item is removed from the pocket.", + parameters = { + { + name = "ply", + description = "The pocket holder.", + type = "Player" + }, + { + name = "item", + description = "The index of the pocket item.", + type = "number" + } + }, + returns = { + } +} + +--[[--------------------------------------------------------------------------- +Functions +---------------------------------------------------------------------------]] +-- workaround: GetNetworkVars doesn't give entities because the /duplicator/ doesn't want to save entities +local function getDTVars(ent) + if not ent.GetNetworkVars then return nil end + local name, value = debug.getupvalue(ent.GetNetworkVars, 1) + if name ~= "datatable" then + ErrorNoHalt("Warning: Datatable cannot be stored properly in pocket. Tell a developer!") + end + + local res = {} + + for k,v in pairs(value) do + res[k] = v.GetFunc(ent, v.index) + end + + return res +end + +local function serialize(ent) + local serialized = duplicator.CopyEntTable(ent) + serialized.DT = getDTVars(ent) + + -- this function is also called in duplicator.CopyEntTable, but some + -- entities change the DT vars of a copied entity (e.g. Lexic's moneypot) + -- That is undone with the getDTVars function call. + -- Re-call OnEntityCopyTableFinish assuming its implementation is pure. + if ent.OnEntityCopyTableFinish then + ent:OnEntityCopyTableFinish(serialized) + end + + return serialized +end + +local function deserialize(ply, item) + local ent = ents.Create(item.Class) + duplicator.DoGeneric(ent, item) + ent:Spawn() + ent:Activate() + + duplicator.DoGenericPhysics(ent, ply, item) + table.Merge(ent:GetTable(), item) + + if ent:IsWeapon() and ent.Weapon ~= nil and not ent.Weapon:IsValid() then ent.Weapon = ent end + if ent.Entity ~= nil and not ent.Entity:IsValid() then ent.Entity = ent end + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + ent:SetPos(tr.HitPos) + + DarkRP.placeEntity(ent, tr, ply) + + local phys = ent:GetPhysicsObject() + timer.Simple(0, function() if phys:IsValid() then phys:Wake() end end) + + if ent.OnDuplicated then + ent:OnDuplicated(item) + end + + if ent.PostEntityPaste then + ent:PostEntityPaste(ply, ent, {ent}) + end + + return ent +end + +local function dropAllPocketItems(ply) + for k in pairs(ply.darkRPPocket or {}) do + ply:dropPocketItem(k) + end +end + +util.AddNetworkString("DarkRP_Pocket") +local function sendPocketItems(ply) + net.Start("DarkRP_Pocket") + net.WriteTable(ply:getPocketItems()) + net.Send(ply) +end + +util.AddNetworkString("DarkRP_PocketMenu") + +--[[--------------------------------------------------------------------------- +Interface functions +---------------------------------------------------------------------------]] +function meta:addPocketItem(ent) + if not IsValid(ent) then DarkRP.error("Entity not valid", 2) end + if ent.USED then return end + + -- This item cannot be used until it has been removed + ent.USED = true + + local serialized = serialize(ent) + + hook.Call("onPocketItemAdded", nil, self, ent, serialized) + + ent.IsPocketing = true + ent:Remove() + + self.darkRPPocket = self.darkRPPocket or {} + + local id = table.insert(self.darkRPPocket, serialized) + sendPocketItems(self) + return id +end + +function meta:removePocketItem(item) + if not self.darkRPPocket or not self.darkRPPocket[item] then DarkRP.error("Player does not contain " .. item .. " in their pocket.", 2) end + + hook.Call("onPocketItemRemoved", nil, self, item) + + self.darkRPPocket[item] = nil + sendPocketItems(self) +end + +function meta:dropPocketItem(item) + if not self.darkRPPocket or not self.darkRPPocket[item] then DarkRP.error("Player does not contain " .. item .. " in their pocket.", 2) end + + local id = self.darkRPPocket[item] + local ent = deserialize(self, id) + + -- reset USED status + ent.USED = nil + + hook.Call("onPocketItemDropped", nil, self, ent, item, id) + + self:removePocketItem(item) + + return ent +end + +-- serverside implementation +function meta:getPocketItems() + self.darkRPPocket = self.darkRPPocket or {} + + local res = {} + for k, v in pairs(self.darkRPPocket) do + res[k] = { + model = v.Model, + class = v.Class + } + end + + return res +end + +--[[--------------------------------------------------------------------------- +Commands +---------------------------------------------------------------------------]] +util.AddNetworkString("DarkRP_spawnPocket") +net.Receive("DarkRP_spawnPocket", function(len, ply) + local item = net.ReadFloat() + if not ply.darkRPPocket or not ply.darkRPPocket[item] then return end + local canPickup, message = hook.Call("canDropPocketItem", nil, ply, item, ply.darkRPPocket[item]) + if canPickup == false then + if message then DarkRP.notify(ply, 1, 4, message) end + sendPocketItems(ply) + return + end + ply:dropPocketItem(item) +end) + +--[[--------------------------------------------------------------------------- +Hooks +---------------------------------------------------------------------------]] +function GAMEMODE:canPocket(ply, item) + if not IsValid(item) then return false end + local class = item:GetClass() + + if item.Removed then return false, DarkRP.getPhrase("cannot_pocket_x") end + if not item:CPPICanPickup(ply) then return false, DarkRP.getPhrase("cannot_pocket_x") end + if item.jailWall then return false, DarkRP.getPhrase("cannot_pocket_x") end + if GAMEMODE.Config.PocketBlacklist[class] then return false, DarkRP.getPhrase("cannot_pocket_x") end + if string.find(class, "func_") then return false, DarkRP.getPhrase("cannot_pocket_x") end + if item:IsRagdoll() then return false, DarkRP.getPhrase("cannot_pocket_x") end + if item:IsNPC() then return false, DarkRP.getPhrase("cannot_pocket_x") end + if not duplicator.IsAllowed(class) then return false, DarkRP.getPhrase("cannot_pocket_x") end + -- Entities being held by the gravgun have different properties than + -- entities not being held. One such property is mass, which is set to 1. + -- The simple solution is to disallow pocketing entities that are being + -- held. + if item.DarkRPBeingGravGunHeldBy ~= nil then return false, DarkRP.getPhrase("cannot_pocket_gravgunned") end + + local trace = ply:GetEyeTrace() + if ply:EyePos():DistToSqr(trace.HitPos) > 22500 then return false end + + local ent = trace.Entity + local phys = ent:GetPhysicsObject() + if not phys:IsValid() then return false end + + local mass = ent.RPOriginalMass and ent.RPOriginalMass or phys:GetMass() + if mass > 100 then return false, DarkRP.getPhrase("object_too_heavy") end + + local job = ply:Team() + local max = RPExtraTeams[job].maxpocket or GAMEMODE.Config.pocketitems + if table.Count(ply.darkRPPocket or {}) >= max then return false, DarkRP.getPhrase("pocket_full") end + + return true +end + + +-- Drop pocket items on death +hook.Add("PlayerDeath", "DropPocketItems", function(ply) + if not GAMEMODE.Config.droppocketdeath or not ply.darkRPPocket then return end + dropAllPocketItems(ply) +end) + +hook.Add("playerArrested", "DropPocketItems", function(ply) + if not GAMEMODE.Config.droppocketarrest then return end + dropAllPocketItems(ply) +end) diff --git a/gamemodes/darkrp/entities/weapons/stick_base/shared.lua b/gamemodes/darkrp/entities/weapons/stick_base/shared.lua new file mode 100644 index 0000000..610e22f --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/stick_base/shared.lua @@ -0,0 +1,169 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +DEFINE_BASECLASS("weapon_cs_base2") + +SWEP.Author = "DarkRP Developers" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.IconLetter = "" + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "stunstick" + +SWEP.UseHands = false + +SWEP.AdminOnly = true + +SWEP.StickColor = color_white + +SWEP.ViewModel = Model("models/weapons/v_stunbaton.mdl") +SWEP.WorldModel = Model("models/weapons/w_stunbaton.mdl") + +SWEP.Sound = Sound("weapons/stunstick/stunstick_swing1.wav") + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + +function SWEP:SetupDataTables() + BaseClass.SetupDataTables(self) + -- Bool 0 = IronsightsPredicted + -- Bool 1 = Reloading + self:NetworkVar("Bool", 2, "SeqIdling") + -- Float 0 = IronsightsTime + -- Float 1 = LastPrimaryAttack + -- Float 2 = ReloadEndTime + -- Float 3 = BurstTime + self:NetworkVar("Float", 4, "SeqIdleTime") + self:NetworkVar("Float", 5, "HoldTypeChangeTime") +end + +local stunstickMaterials +function SWEP:Initialize() + self:SetHoldType("normal") + + self.stickRange = 90 + + if SERVER then return end + + stunstickMaterials = stunstickMaterials or {} + + local materialName = "darkrp/" .. self:GetClass() + if stunstickMaterials[materialName] then return end + + CreateMaterial(materialName, "VertexLitGeneric", { + ["$basetexture"] = "models/debug/debugwhite", + ["$surfaceprop"] = "metal", + ["$envmap"] = "env_cubemap", + ["$envmaptint"] = "[ .5 .5 .5 ]", + ["$selfillum"] = 0, + ["$model"] = 1 + }):SetVector("$color2", self.StickColor:ToVector()) + + stunstickMaterials[materialName] = true +end + +function SWEP:Deploy() + BaseClass.Deploy(self) + if SERVER then + self:SetMaterial("!darkrp/" .. self:GetClass()) + end + + local vm = self:GetOwner():GetViewModel() + if not IsValid(vm) then return true end + + vm:SendViewModelMatchingSequence(vm:LookupSequence("idle01")) + + return true +end + +function SWEP:PreDrawViewModel(vm) + for i = 9, 15 do + vm:SetSubMaterial(i, "!darkrp/" .. self:GetClass()) + end +end + +function SWEP:ViewModelDrawn(vm) + if not IsValid(vm) then return end + vm:SetSubMaterial() -- clear sub-materials +end + +function SWEP:ResetStick() + if not IsValid(self:GetOwner()) then return end + if SERVER then + self:SetMaterial() -- clear material + end + self:SetSeqIdling(false) + self:SetSeqIdleTime(0) + self:SetHoldTypeChangeTime(0) +end + +function SWEP:Holster() + BaseClass.Holster(self) + self:ResetStick() + return true +end + +function SWEP:Think() + if self:GetSeqIdling() then + self:SetSeqIdling(false) + + if not IsValid(self:GetOwner()) then return end + self:GetOwner():SetAnimation(PLAYER_ATTACK1) + self:EmitSound(self.Sound) + + local vm = self:GetOwner():GetViewModel() + if not IsValid(vm) then return end + vm:SendViewModelMatchingSequence(vm:LookupSequence("attackch")) + vm:SetPlaybackRate(1 + 1 / 3) + local duration = vm:SequenceDuration() / vm:GetPlaybackRate() + local time = CurTime() + duration + self:SetSeqIdleTime(time) + self:SetNextPrimaryFire(time) + end + if self:GetSeqIdleTime() ~= 0 and CurTime() >= self:GetSeqIdleTime() then + self:SetSeqIdleTime(0) + + if not IsValid(self:GetOwner()) then return end + local vm = self:GetOwner():GetViewModel() + if not IsValid(vm) then return end + vm:SendViewModelMatchingSequence(vm:LookupSequence("idle01")) + end + if self:GetHoldTypeChangeTime() ~= 0 and CurTime() >= self:GetHoldTypeChangeTime() then + self:SetHoldTypeChangeTime(0) + self:SetHoldType("normal") + end +end + +function SWEP:PrimaryAttack() + self:SetHoldType("melee") + self:SetHoldTypeChangeTime(CurTime() + 0.3) + + self:SetNextPrimaryFire(CurTime() + 0.51) -- Actual delay is set later. + + local vm = self:GetOwner():GetViewModel() + if IsValid(vm) then + vm:SendViewModelMatchingSequence(vm:LookupSequence("idle01")) + self:SetSeqIdling(true) + end +end + +function SWEP:SecondaryAttack() + -- Do nothing +end + +function SWEP:Reload() + -- Do nothing +end diff --git a/gamemodes/darkrp/entities/weapons/stunstick/shared.lua b/gamemodes/darkrp/entities/weapons/stunstick/shared.lua new file mode 100644 index 0000000..3c2a952 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/stunstick/shared.lua @@ -0,0 +1,196 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Slot = 0 + SWEP.SlotPos = 5 + SWEP.RenderGroup = RENDERGROUP_BOTH + + killicon.AddAlias("stunstick", "weapon_stunstick") + + CreateMaterial("darkrp/stunstick_beam", "UnlitGeneric", { + ["$basetexture"] = "sprites/lgtning", + ["$additive"] = 1 + }) +end + +DEFINE_BASECLASS("stick_base") + +SWEP.Instructions = "Left click to discipline\nRight click to kill\nHold reload to threaten" +SWEP.IsDarkRPStunstick = true + +SWEP.PrintName = "Stun Stick" +SWEP.Spawnable = true +SWEP.Category = "DarkRP (Utility)" + +SWEP.StickColor = Color(0, 0, 255) + +function SWEP:Initialize() + BaseClass.Initialize(self) + + self.Hit = { + Sound("weapons/stunstick/stunstick_impact1.wav"), + Sound("weapons/stunstick/stunstick_impact2.wav") + } + + self.FleshHit = { + Sound("weapons/stunstick/stunstick_fleshhit1.wav"), + Sound("weapons/stunstick/stunstick_fleshhit2.wav") + } +end + +function SWEP:SetupDataTables() + BaseClass.SetupDataTables(self) + -- Float 0 = IronsightsTime + -- Float 1 = LastPrimaryAttack + -- Float 2 = ReloadEndTime + -- Float 3 = BurstTime + -- Float 4 = SeqIdleTime + -- Float 5 = HoldTypeChangeTime + self:NetworkVar("Float", 6, "LastReload") +end + +function SWEP:Think() + BaseClass.Think(self) + if self.WaitingForAttackEffect and self:GetSeqIdleTime() ~= 0 and CurTime() >= self:GetSeqIdleTime() - 0.35 then + self.WaitingForAttackEffect = false + + local Owner = self:GetOwner() + + local effectData = EffectData() + effectData:SetOrigin(Owner:GetShootPos() + (Owner:EyeAngles():Forward() * 45)) + effectData:SetNormal(Owner:EyeAngles():Forward()) + util.Effect("StunstickImpact", effectData) + end +end + +function SWEP:DoFlash(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + + ply:ScreenFade(SCREENFADE.IN, color_white, 1.2, 0) +end + +local stunstickMaterial = Material("effects/stunstick") +local stunstickBeam = Material("!darkrp/stunstick_beam") +local colorSprite = Color(180, 180, 180) +function SWEP:PostDrawViewModel(vm) + if self:GetSeqIdleTime() ~= 0 or self:GetLastReload() >= CurTime() - 0.1 then + local attachment = vm:GetAttachment(1) + local pos = attachment.Pos + cam.Start3D(EyePos(), EyeAngles()) + render.SetMaterial(stunstickMaterial) + render.DrawSprite(pos, 12, 12, colorSprite) + for i = 1, 3 do + local randVec = VectorRand() * 3 + local offset = (attachment.Ang:Forward() * randVec.x) + (attachment.Ang:Right() * randVec.y) + (attachment.Ang:Up() * randVec.z) + render.SetMaterial(stunstickBeam) + render.DrawBeam(pos, pos + offset, 3.25 - i, 1, 1.25, colorSprite) + pos = pos + offset + end + cam.End3D() + end +end + +local light_glow02_add = Material("sprites/light_glow02_add") +function SWEP:DrawWorldModelTranslucent() + if CurTime() <= self:GetLastReload() + 0.1 then + local bone = self:GetOwner():LookupBone("ValveBiped.Bip01_R_Hand") + if not bone then self:DrawModel() return end + local bonePos, boneAng = self:GetOwner():GetBonePosition(bone) + if bonePos then + local pos = bonePos + (boneAng:Up() * -16) + (boneAng:Right() * 3) + (boneAng:Forward() * 6.5) + render.SetMaterial(light_glow02_add) + render.DrawSprite(pos, 32, 32, color_white) + end + end + self:DrawModel() +end + +local entMeta = FindMetaTable("Entity") +function SWEP:DoAttack(dmg) + if CLIENT then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + Owner:LagCompensation(true) + local trace = util.QuickTrace(Owner:EyePos(), Owner:GetAimVector() * 90, {Owner}) + Owner:LagCompensation(false) + + local ent = trace.Entity + if IsValid(ent) and ent.onStunStickUsed then + ent:onStunStickUsed(Owner) + return + elseif IsValid(ent) and ent:GetClass() == "func_breakable_surf" then + ent:Fire("Shatter") + Owner:EmitSound(self.Hit[math.random(#self.Hit)]) + return + end + + self.WaitingForAttackEffect = true + + ent = Owner:getEyeSightHitEntity( + self.stickRange, + 15, + fn.FAnd{ + fp{fn.Neq, Owner}, + fc{IsValid, entMeta.GetPhysicsObject}, + entMeta.IsSolid + } + ) + + if not IsValid(ent) then return end + if ent:IsPlayer() and not ent:Alive() then return end + + if not ent:isDoor() then + ent:SetVelocity((ent:GetPos() - Owner:GetPos()) * 7) + end + + if dmg > 0 then + ent:TakeDamage(dmg, Owner, self) + end + + if ent:IsPlayer() or ent:IsNPC() or ent:IsVehicle() then + self:DoFlash(ent) + Owner:EmitSound(self.FleshHit[math.random(#self.FleshHit)]) + else + Owner:EmitSound(self.Hit[math.random(#self.Hit)]) + if FPP and FPP.plyCanTouchEnt(Owner, ent, "EntityDamage") then + if ent.SeizeReward and not ent.beenSeized and not ent.burningup and Owner:isCP() and ent.Getowning_ent and Owner ~= ent:Getowning_ent() then + local amount = isfunction(ent.SeizeReward) and ent:SeizeReward(Owner, dmg) or ent.SeizeReward + + Owner:addMoney(amount) + DarkRP.notify(Owner, 1, 4, DarkRP.getPhrase("you_received_x", DarkRP.formatMoney(amount), DarkRP.getPhrase("bonus_destroying_entity"))) + ent.beenSeized = true + end + local health = math.max(ent:Health(), ent:GetMaxHealth()) + health = health == 0 and 1000 or health + + local dmgToTake = GAMEMODE.Config.stunstickdamage <= 1 and GAMEMODE.Config.stunstickdamage * health or GAMEMODE.Config.stunstickdamage + -- Ceil because health is an integer value + dmgToTake = math.max(0, math.ceil(dmgToTake - dmg)) + ent:TakeDamage(dmgToTake, Owner, self) -- for illegal entities + end + end +end + +function SWEP:PrimaryAttack() + BaseClass.PrimaryAttack(self) + self:SetNextSecondaryFire(self:GetNextPrimaryFire()) + self:DoAttack(0) +end + +function SWEP:SecondaryAttack() + BaseClass.PrimaryAttack(self) + self:SetNextSecondaryFire(self:GetNextPrimaryFire()) + self:DoAttack(10) +end + +function SWEP:Reload() + self:SetHoldType("melee") + self:SetHoldTypeChangeTime(CurTime() + 0.1) + + if self:GetLastReload() + 0.1 > CurTime() then self:SetLastReload(CurTime()) return end + self:SetLastReload(CurTime()) + self:EmitSound("weapons/stunstick/spark" .. math.random(1, 3) .. ".wav") +end diff --git a/gamemodes/darkrp/entities/weapons/unarrest_stick/shared.lua b/gamemodes/darkrp/entities/weapons/unarrest_stick/shared.lua new file mode 100644 index 0000000..bee38ad --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/unarrest_stick/shared.lua @@ -0,0 +1,107 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Slot = 1 + SWEP.SlotPos = 3 +end + +DEFINE_BASECLASS("stick_base") + +SWEP.Instructions = "Left click to unarrest\nRight click to switch batons" +SWEP.IsDarkRPUnarrestStick = true + +SWEP.PrintName = "Unarrest Baton" +SWEP.Spawnable = true +SWEP.Category = "DarkRP (Utility)" + +SWEP.StickColor = Color(0, 255, 0) + +DarkRP.hookStub{ + name = "canUnarrest", + description = "Whether someone can unarrest another player.", + parameters = { + { + name = "unarrester", + description = "The player trying to unarrest someone.", + type = "Player" + }, + { + name = "unarrestee", + description = "The player being unarrested.", + type = "Player" + } + }, + returns = { + { + name = "canUnarrest", + description = "A yes or no as to whether the player can unarrest the other player.", + type = "boolean" + }, + { + name = "message", + description = "The message that is shown when they can't unarrest the player.", + type = "string" + } + }, + realm = "Server" +} + +-- Default for canUnarrest hook +local hookCanUnarrest = {canUnarrest = fp{fn.Id, true}} + +function SWEP:Deploy() + self.Switched = true + return BaseClass.Deploy(self) +end + +function SWEP:PrimaryAttack() + BaseClass.PrimaryAttack(self) + + if CLIENT then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + Owner:LagCompensation(true) + local trace = util.QuickTrace(Owner:EyePos(), Owner:GetAimVector() * 90, {Owner}) + Owner:LagCompensation(false) + + local ent = trace.Entity + if IsValid(ent) and ent.onUnArrestStickUsed then + ent:onUnArrestStickUsed(Owner) + return + end + + ent = Owner:getEyeSightHitEntity(nil, nil, function(p) return p ~= Owner and p:IsPlayer() and p:Alive() and p:IsSolid() end) + if not ent then return end + + local stickRange = self.stickRange * self.stickRange + if not IsValid(ent) or not ent:IsPlayer() or (Owner:EyePos():DistToSqr(ent:GetPos()) > stickRange) or not ent:getDarkRPVar("Arrested") then + return + end + + local canUnarrest, message = hook.Call("canUnarrest", hookCanUnarrest, Owner, ent) + if not canUnarrest then + if message then DarkRP.notify(Owner, 1, 5, message) end + return + end + + ent:unArrest(Owner) + DarkRP.notify(ent, 0, 4, DarkRP.getPhrase("youre_unarrested_by", Owner:Nick())) + + if Owner.SteamName then + DarkRP.log(Owner:Nick() .. " (" .. Owner:SteamID() .. ") unarrested " .. ent:Nick(), Color(0, 255, 255)) + end +end + +function SWEP:startDarkRPCommand(usrcmd) + if game.SinglePlayer() and CLIENT then return end + if usrcmd:KeyDown(IN_ATTACK2) then + if not self.Switched and self:GetOwner():HasWeapon("arrest_stick") then + usrcmd:SelectWeapon(self:GetOwner():GetWeapon("arrest_stick")) + end + else + self.Switched = false + end +end diff --git a/gamemodes/darkrp/entities/weapons/weapon_ak472/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_ak472/shared.lua new file mode 100644 index 0000000..3f1c673 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_ak472/shared.lua @@ -0,0 +1,49 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 3 + SWEP.SlotPos = 0 + SWEP.IconLetter = "b" + + killicon.AddFont("weapon_ak472", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "AK47" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.UseHands = true +SWEP.ViewModel = "models/weapons/cstrike/c_rif_ak47.mdl" +SWEP.WorldModel = "models/weapons/w_rif_ak47.mdl" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.HoldType = "ar2" +SWEP.LoweredHoldType = "passive" + +SWEP.Primary.Sound = Sound("Weapon_AK47.Single") +SWEP.Primary.Recoil = 1.5 +SWEP.Primary.Damage = 40 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.002 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.Delay = 0.08 +SWEP.Primary.DefaultClip = 30 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "smg1" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.IronSightsPos = Vector(-6.6, -15, 2.6) +SWEP.IronSightsAng = Vector(2.6, 0.02, 0) + +SWEP.MultiMode = true diff --git a/gamemodes/darkrp/entities/weapons/weapon_cs_base2/sh_commands.lua b/gamemodes/darkrp/entities/weapons/weapon_cs_base2/sh_commands.lua new file mode 100644 index 0000000..35acee9 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_cs_base2/sh_commands.lua @@ -0,0 +1,19 @@ +AddCSLuaFile() + +DarkRP.declareChatCommand{ + command = "drop", + description = "Drop the weapon you're holding.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "dropweapon", + description = "Drop the weapon you're holding.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "weapondrop", + description = "Drop the weapon you're holding.", + delay = 1.5 +} diff --git a/gamemodes/darkrp/entities/weapons/weapon_cs_base2/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_cs_base2/shared.lua new file mode 100644 index 0000000..e14faf1 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_cs_base2/shared.lua @@ -0,0 +1,516 @@ +AddCSLuaFile() + +if SERVER then + include("sv_commands.lua") + include("sh_commands.lua") + SWEP.Weight = 5 + SWEP.AutoSwitchTo = false + SWEP.AutoSwitchFrom = false +end + +if CLIENT then + SWEP.DrawAmmo = true + SWEP.DrawCrosshair = false + SWEP.ViewModelFOV = 82 + SWEP.ViewModelFlip = false + SWEP.CSMuzzleFlashes = true + + -- This is the font that's used to draw the death icons + surface.CreateFont("CSKillIcons", { + size = ScreenScale(30), + weight = 500, + antialias = true, + shadow = true, + font = "csd" + }) + surface.CreateFont("CSSelectIcons", { + size = ScreenScale(60), + weight = 500, + antialias = true, + shadow = true, + font = "csd" + }) +end + +SWEP.Base = "weapon_base" + +SWEP.Author = "DarkRP Developers" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.Instructions = "" + +SWEP.Spawnable = false +SWEP.AdminOnly = false +SWEP.UseHands = true + +SWEP.HoldType = "normal" +SWEP.LoweredHoldType = "normal" + +SWEP.Primary.Sound = Sound("Weapon_AK47.Single") +SWEP.Primary.Recoil = 1.5 +SWEP.Primary.Damage = 40 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.02 +SWEP.Primary.Delay = 0.15 + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.MultiMode = false + +SWEP.DarkRPBased = true + +function SWEP:SetIronsights(b) + if (b ~= self:GetIronsights()) then + self:SetIronsightsPredicted(b) + self:SetIronsightsTime(CurTime()) + if GAMEMODE.Config.ironshoot then + self:SetHoldType(b and self.HoldType or self.LoweredHoldType) + end + if CLIENT then + self:CalcViewModel() + end + end +end + +function SWEP:GetIronsights() + return self:GetIronsightsPredicted() +end + +--- Dummy functions that will be replaced when SetupDataTables runs. These are +--- here for when that does not happen (due to e.g. stacking base classes) +function SWEP:GetIronsightsTime() return -1 end +function SWEP:SetIronsightsTime() end +function SWEP:GetIronsightsPredicted() return false end +function SWEP:SetIronsightsPredicted() end + +function SWEP:SetupDataTables() + self:NetworkVar("Bool", 0, "IronsightsPredicted") + self:NetworkVar("Float", 0, "IronsightsTime") + self:NetworkVar("Bool", 1, "Reloading") + self:NetworkVar("Float", 1, "LastPrimaryAttack") + self:NetworkVar("Float", 2, "ReloadEndTime") + self:NetworkVar("Float", 3, "BurstTime") + self:NetworkVar("Int", 0, "BurstBulletNum") + self:NetworkVar("Int", 1, "TotalUsedMagCount") + self:NetworkVar("String", 0, "FireMode") + self:NetworkVar("Entity", 0, "LastOwner") +end + +function SWEP:Initialize() + if CLIENT and IsValid(self:GetOwner()) then + local vm = self:GetOwner():GetViewModel() + self:ResetDarkRPBones(vm) + end + + self:SetHoldType(GAMEMODE.Config.ironshoot and self.LoweredHoldType or self.HoldType) + if SERVER then + self:SetNPCMinBurst(30) + self:SetNPCMaxBurst(30) + self:SetNPCFireRate(0.01) + end + + self:SetFireMode(self.Primary.Automatic and "auto" or "semi") +end + +function SWEP:Deploy() + self:SetHoldType(GAMEMODE.Config.ironshoot and self.LoweredHoldType or self.HoldType) + self:SetIronsights(false) + self:SetReloading(false) + self:SetReloadEndTime(0) + self:SetBurstTime(0) + self:SetBurstBulletNum(0) + + return true +end + +function SWEP:Holster() + self:SetIronsights(false) + self:SetReloading(false) + self:SetReloadEndTime(0) + self:SetBurstTime(0) + self:SetBurstBulletNum(0) + + if CLIENT then self.hasShot = false end + + if not IsValid(self:GetOwner()) then return true end + if CLIENT then + local vm = self:GetOwner():GetViewModel() + self:ResetDarkRPBones(vm) + end + + return true +end + +function SWEP:OnRemove() + if CLIENT and IsValid(self:GetOwner()) then + local vm = self:GetOwner():GetViewModel() + self:ResetDarkRPBones(vm) + end +end + +function SWEP:OwnerChanged() + if IsValid(self:GetOwner()) then self:SetLastOwner(self:GetOwner()) end +end + +function SWEP:PrimaryAttack() + self.Primary.Automatic = self:GetFireMode() == "auto" + + if self:GetBurstBulletNum() > 0 and CurTime() < self:GetBurstTime() then return end + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + if self.MultiMode and Owner:KeyDown(IN_USE) then + if self:GetFireMode() == "semi" then + self:SetFireMode("burst") + self.Primary.Automatic = false + Owner:PrintMessage(HUD_PRINTCENTER, DarkRP.getPhrase("switched_burst")) + elseif self:GetFireMode() == "burst" then + self:SetFireMode("auto") + self.Primary.Automatic = true + Owner:PrintMessage(HUD_PRINTCENTER, DarkRP.getPhrase("switched_fully_auto")) + elseif self:GetFireMode() == "auto" then + self:SetFireMode("semi") + self.Primary.Automatic = false + Owner:PrintMessage(HUD_PRINTCENTER, DarkRP.getPhrase("switched_semi_auto")) + end + self:SetNextPrimaryFire(CurTime() + 0.5) + self:SetNextSecondaryFire(CurTime() + 0.5) + return + end + + if self:GetFireMode() ~= "burst" then + self:SetNextPrimaryFire(CurTime() + self.Primary.Delay) + end + + self:SetNextSecondaryFire(CurTime() + self.Primary.Delay) + + if self:Clip1() <= 0 then + self:EmitSound("weapons/clipempty_rifle.wav") + self:SetNextPrimaryFire(CurTime() + 2) + return + end + + if not self:CanPrimaryAttack() then self:SetIronsights(false) return end + if not self:GetIronsights() and GAMEMODE.Config.ironshoot then return end + -- Play shoot sound + self:EmitSound(self.Primary.Sound) + + -- Shoot the bullet + self:CSShootBullet(self.Primary.Damage, self.Primary.Recoil + 3, self.Primary.NumShots, self.Primary.Cone + .05) + + if self:GetFireMode() == "burst" then + self:SetBurstBulletNum(self:GetBurstBulletNum() + 1) + if self:GetBurstBulletNum() == 3 then + self:SetBurstTime(0) + self:SetBurstBulletNum(0) + else + self:SetBurstTime(CurTime() + 0.1) + end + end + + -- Remove 1 bullet from our clip + self:TakePrimaryAmmo(1) + + self:SetLastPrimaryAttack(CurTime()) + + if Owner:IsNPC() then return end + + -- Punch the player's view + Owner:ViewPunch(Angle(util.SharedRandom("DarkRP_CSBase" .. self:EntIndex() .. "Mag" .. self:GetTotalUsedMagCount() .. "p" .. self:Clip1(), -1.2, -1.1) * self.Primary.Recoil, util.SharedRandom("DarkRP_CSBase" .. self:EntIndex() .. "Mag" .. self:GetTotalUsedMagCount() .. "y" .. self:Clip1(), -1.1, 1.1) * self.Primary.Recoil, 0)) +end + +function SWEP:CSShootBullet(dmg, recoil, numbul, cone) + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + numbul = numbul or 1 + cone = cone or 0.01 + + local bullet = {} + bullet.Num = numbul or 1 + bullet.Src = Owner:GetShootPos() -- Source + bullet.Dir = (Owner:GetAimVector():Angle() + Owner:GetViewPunchAngles()):Forward() -- Dir of bullet + bullet.Spread = Vector(cone, cone, 0) -- Aim Cone + bullet.Tracer = 4 -- Show a tracer on every x bullets + bullet.Force = 5 -- Amount of force to give to phys objects + bullet.Damage = dmg + + Owner:FireBullets(bullet) + self:SendWeaponAnim(ACT_VM_PRIMARYATTACK) -- View model animation + Owner:MuzzleFlash() -- Crappy muzzle light + Owner:SetAnimation(PLAYER_ATTACK1) -- 3rd Person Animation + + if Owner:IsNPC() then return end + + -- Part of workaround, different viewmodel position if shots have been fired + if CLIENT then self.hasShot = true end +end + +local host_timescale = GetConVar("host_timescale") +local IRONSIGHT_TIME = 0.25 +function SWEP:GetViewModelPosition(pos, ang) + if (not self.IronSightsPos) then return pos, ang end + + pos = pos + ang:Forward() * -5 + + if (self.bIron == nil) then return pos, ang end + + local bIron = self.bIron + local time = self.fCurrentTime + (SysTime() - self.fCurrentSysTime) * game.GetTimeScale() * host_timescale:GetFloat() + + if bIron then + self.SwayScale = 0.3 + self.BobScale = 0.1 + else + self.SwayScale = 1.0 + self.BobScale = 1.0 + end + + if GAMEMODE.Config.ironshoot then + ang:RotateAroundAxis(ang:Right(), -15) + end + + local fIronTime = self.fIronTime + if (not bIron) and fIronTime < time - IRONSIGHT_TIME then + return pos, ang + end + + local mul = 1.0 + + if fIronTime > time - IRONSIGHT_TIME then + mul = math.Clamp((time - fIronTime) / IRONSIGHT_TIME, 0, 1) + + if not bIron then mul = 1 - mul end + end + + local offset = self.IronSightsPos + + if self.IronSightsAng then + ang = ang * 1 + ang:RotateAroundAxis(ang:Right(), self.IronSightsAng.x * mul) + ang:RotateAroundAxis(ang:Up(), self.IronSightsAng.y * mul) + ang:RotateAroundAxis(ang:Forward(), self.IronSightsAng.z * mul) + end + + if GAMEMODE.Config.ironshoot then + ang:RotateAroundAxis(ang:Right(), mul * 15) + else + ang:RotateAroundAxis(ang:Right(), mul) + end + + pos = pos + offset.x * ang:Right() * mul + pos = pos + offset.y * ang:Forward() * mul + pos = pos + offset.z * ang:Up() * mul + + if not self.hasShot then + if self.IronSightsAngAfterShootingAdjustment then + ang:RotateAroundAxis(ang:Right(), self.IronSightsAngAfterShootingAdjustment.x * mul) + ang:RotateAroundAxis(ang:Up(), self.IronSightsAngAfterShootingAdjustment.y * mul) + ang:RotateAroundAxis(ang:Forward(), self.IronSightsAngAfterShootingAdjustment.z * mul) + end + + if self.IronSightsPosAfterShootingAdjustment then + offset = self.IronSightsPosAfterShootingAdjustment + local right = ang:Right() + local up = ang:Up() + local forward = ang:Forward() + + pos = pos + offset.x * right * mul + pos = pos + offset.y * forward * mul + pos = pos + offset.z * up * mul + end + end + + return pos, ang +end + +function SWEP:SecondaryAttack() + if not self.IronSightsPos then return end + + if self:GetReloading() then return end + + self:SetIronsights(not self:GetIronsights()) + + self:SetNextSecondaryFire(CurTime() + 0.3) +end + +--[[--------------------------------------------------------- +Reload does nothing +---------------------------------------------------------]] +function SWEP:Reload() + if not self:DefaultReload(ACT_VM_RELOAD) then return end + self:SetReloading(true) + self:SetIronsights(false) + self:SetBurstTime(0) + self:SetBurstBulletNum(0) + self:GetOwner():SetAnimation(PLAYER_RELOAD) + self:SetReloadEndTime(CurTime() + 2) + self:SetTotalUsedMagCount(self:GetTotalUsedMagCount() + 1) +end + +function SWEP:OnRestore() + self:SetNextSecondaryFire(0) + self:SetIronsights(false) +end + +function SWEP:Equip(NewOwner) + if self.PrimaryClipLeft and self.SecondaryClipLeft and self.PrimaryAmmoLeft and self.SecondaryAmmoLeft then + NewOwner:SetAmmo(self.PrimaryAmmoLeft, self:GetPrimaryAmmoType()) + NewOwner:SetAmmo(self.SecondaryAmmoLeft, self:GetSecondaryAmmoType()) + + self:SetClip1(self.PrimaryClipLeft) + self:SetClip2(self.SecondaryClipLeft) + end +end + +function SWEP:OnDrop() + self.PrimaryClipLeft = self:Clip1() + self.SecondaryClipLeft = self:Clip2() + + if not IsValid(self:GetLastOwner()) then return end + self.PrimaryAmmoLeft = self:GetLastOwner():GetAmmoCount(self:GetPrimaryAmmoType()) + self.SecondaryAmmoLeft = self:GetLastOwner():GetAmmoCount(self:GetSecondaryAmmoType()) + self:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE_DEBRIS) +end + +function SWEP:CalcViewModel() + if (not CLIENT) or (not IsFirstTimePredicted()) then return end + self.bIron = self:GetIronsights() + self.fIronTime = self:GetIronsightsTime() + self.fCurrentTime = CurTime() + self.fCurrentSysTime = SysTime() +end + +-- Note that if you override Think in your SWEP, you should call +-- BaseClass.Think(self) so as not to break ironsights +function SWEP:Think() + self:CalcViewModel() + if self.Primary.ClipSize ~= -1 and not self:GetReloading() and not self:GetIronsights() and self:GetLastPrimaryAttack() + 1 < CurTime() and self:GetHoldType() == self.HoldType and GAMEMODE.Config.ironshoot then + self:SetHoldType(self.LoweredHoldType) + end + if self:GetReloadEndTime() ~= 0 and CurTime() >= self:GetReloadEndTime() then + self:SetReloadEndTime(0) + self:SetReloading(false) + if GAMEMODE.Config.ironshoot then + self:SetHoldType(self.LoweredHoldType) + end + if CLIENT then self.hasShot = false end + end + if self:GetBurstTime() ~= 0 and CurTime() >= self:GetBurstTime() then + self:PrimaryAttack() + end +end + +function SWEP:DrawWeaponSelection(x, y, wide, tall, alpha) + if self.IconLetter and string.find(self.IconLetter, "^[0-9a-wA-Z]$") then + draw.DrawNonParsedSimpleText(self.IconLetter, "CSSelectIcons", x + wide / 2, y + tall * 0.2, Color(255, 210, 0, 255), TEXT_ALIGN_CENTER) + + -- try to fool them into thinking they're playing a Tony Hawks game + draw.DrawNonParsedSimpleText(self.IconLetter, "CSSelectIcons", x + wide / 2 + math.Rand(-4, 4), y + tall * 0.2 + math.Rand(-14, 14), Color(255, 210, 0, math.Rand(10, 120)), TEXT_ALIGN_CENTER) + draw.DrawNonParsedSimpleText(self.IconLetter, "CSSelectIcons", x + wide / 2 + math.Rand(-4, 4), y + tall * 0.2 + math.Rand(-9, 9), Color(255, 210, 0, math.Rand(10, 120)), TEXT_ALIGN_CENTER) + else + -- Set us up the texture + surface.SetDrawColor(255, 255, 255, alpha) + surface.SetTexture(self.WepSelectIcon) + + -- Lets get a sin wave to make it bounce + local fsin = 0 + + if self.BounceWeaponIcon then + fsin = math.sin(CurTime() * 10) * 5 + end + + -- Borders + y = y + 10 + x = x + 10 + wide = wide - 20 + + -- Draw that motherfucker + surface.DrawTexturedRect(x + fsin, y - fsin, wide - fsin * 2, (wide / 2) + fsin) + + -- Draw weapon info box + self:PrintWeaponInfo(x + wide + 20, y + tall * 0.95, alpha) + end +end + +if CLIENT then + function SWEP:ViewModelDrawn(vm) + if self.DarkRPViewModelBoneManipulations and not self:GetReloading() then + self:UpdateDarkRPBones(vm, self.DarkRPViewModelBoneManipulations) + else + self:ResetDarkRPBones(vm) + end + end + + function SWEP:UpdateDarkRPBones(vm, manipulations) + if not IsValid(vm) or not vm:GetBoneCount() then return end + + -- Fill in missing bone names. Things fuck up when we workaround the scale bug and bones are missing. + local bones = {} + for i = 0, vm:GetBoneCount() - 1 do + local bonename = vm:GetBoneName(i) + if manipulations[bonename] then + bones[bonename] = manipulations[bonename] + else + bones[bonename] = { + scale = Vector(1,1,1), + pos = Vector(0,0,0), + angle = Angle(0,0,0) + } + end + end + + for k, v in pairs(bones) do + local bone = vm:LookupBone(k) + if not bone then continue end + + -- Bone scaling seems to be buggy. Workaround. + local scale = Vector(v.scale.x, v.scale.y, v.scale.z) + local ms = Vector(1,1,1) + local cur = vm:GetBoneParent(bone) + while cur >= 0 do + local pscale = bones[vm:GetBoneName(cur)].scale + ms = ms * pscale + cur = vm:GetBoneParent(cur) + end + scale = scale * ms + + if vm:GetManipulateBoneScale(bone) ~= scale then + vm:ManipulateBoneScale(bone, scale) + end + if vm:GetManipulateBonePosition(bone) ~= v.pos then + vm:ManipulateBonePosition(bone, v.pos) + end + if vm:GetManipulateBoneAngles(bone) ~= v.angle then + vm:ManipulateBoneAngles(bone, v.angle) + end + end + end + + function SWEP:ResetDarkRPBones(vm) + if not IsValid(vm) or not vm:GetBoneCount() then return end + for i = 0, vm:GetBoneCount() - 1 do + vm:ManipulateBoneScale(i, Vector(1, 1, 1)) + vm:ManipulateBoneAngles(i, Angle(0, 0, 0)) + vm:ManipulateBonePosition(i, Vector(0, 0, 0)) + end + end +end + +hook.Add("SetupMove", "DarkRP_WeaponSpeed", function(ply, mv) + local wep = ply:GetActiveWeapon() + if not wep:IsValid() or not wep.DarkRPBased or not wep.GetIronsights or not wep:GetIronsights() then return end + + mv:SetMaxClientSpeed(mv:GetMaxClientSpeed() / 3) +end) diff --git a/gamemodes/darkrp/entities/weapons/weapon_cs_base2/sv_commands.lua b/gamemodes/darkrp/entities/weapons/weapon_cs_base2/sv_commands.lua new file mode 100644 index 0000000..77b6336 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_cs_base2/sv_commands.lua @@ -0,0 +1,158 @@ +local meta = FindMetaTable("Player") +function meta:dropDRPWeapon(weapon) + if not weapon:IsValid() or weapon:GetOwner() ~= self or weapon.IsBeingDarkRPDropped then return end + + if GAMEMODE.Config.restrictdrop then + local found = false + for k,v in pairs(CustomShipments) do + if v.entity == weapon:GetClass() then + found = true + break + end + end + + if not found then return end + end + + -- Mark the weapon as being dropped. This, along with the check above will + -- prevent the same weapon from being dropped twice. + weapon.IsBeingDarkRPDropped = true + + local primAmmo = self:GetAmmoCount(weapon:GetPrimaryAmmoType()) + self:DropWeapon(weapon) -- Drop it so the model isn't the viewmodel + weapon:SetOwner(self) + + local ent = ents.Create("spawned_weapon") + + local model = (weapon:GetModel() == "models/weapons/v_physcannon.mdl" and "models/weapons/w_physics.mdl") or weapon:GetModel() + model = util.IsValidModel(model) and model or "models/weapons/w_rif_ak47.mdl" + + ent:SetModel(model) + ent:SetSkin(weapon:GetSkin() or 0) + ent:SetWeaponClass(weapon:GetClass()) + ent.nodupe = true + ent.clip1 = weapon:Clip1() + ent.clip2 = weapon:Clip2() + ent.ammoadd = primAmmo + + self:RemoveAmmo(primAmmo, weapon:GetPrimaryAmmoType()) + self:RemoveAmmo(self:GetAmmoCount(weapon:GetSecondaryAmmoType()), weapon:GetSecondaryAmmoType()) + + local trace = {} + trace.start = self:GetShootPos() + trace.endpos = trace.start + self:GetAimVector() * 50 + trace.filter = {self, weapon, ent} + + local tr = util.TraceLine(trace) + + ent:SetPos(tr.HitPos) + ent:Spawn() + + DarkRP.placeEntity(ent, tr, self) + + hook.Call("onDarkRPWeaponDropped", nil, self, ent, weapon) + + weapon:Remove() +end + +local function DropWeapon(ply) + local curTime = CurTime() + if (ply.DelayDropWeapon or 0) >= curTime then return "" end --Fix Dupe Weapon + + local ent = ply:GetActiveWeapon() + if not ent:IsValid() or ent:GetModel() == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return "" + end + + local canDrop = hook.Call("canDropWeapon", GAMEMODE, ply, ent) + if not canDrop then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cannot_drop_weapon")) + return "" + end + + ply:DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_DROP) + + ply.DelayDropWeapon = curTime + 1.1 + + timer.Simple(1, function() + if IsValid(ply) and IsValid(ent) and ply:Alive() and ent:GetModel() ~= "" and not IsValid(ply:GetObserverTarget()) then + ply:dropDRPWeapon(ent) + end + end) + return "" +end + +DarkRP.defineChatCommand("drop", DropWeapon) +DarkRP.defineChatCommand("dropweapon", DropWeapon) +DarkRP.defineChatCommand("weapondrop", DropWeapon) + +DarkRP.stub{ + name = "dropDRPWeapon", + description = "Drop the weapon with animations.", + parameters = { + { + name = "weapon", + description = "The weapon to drop", + type = "Entity", + optional = false + } + }, + returns = { + }, + metatable = meta +} + +DarkRP.hookStub{ + name = "onDarkRPWeaponDropped", + description = "When a player drops a weapon. Use this hook (in combination with PlayerPickupDarkRPWeapon) to store extra information about a weapon. This hook cannot prevent weapon dropping. If you want to prevent weapon dropping, use canDropWeapon instead.", + parameters = { + { + name = "ply", + description = "The player who dropped the weapon.", + type = "Player" + }, + { + name = "spawned_weapon", + description = "The spawned_weapon created from the weapon that is dropped.", + type = "Entity" + }, + { + name = "original_weapon", + description = "The original weapon from which the spawned_weapon is made.", + type = "Weapon" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "PlayerPickupDarkRPWeapon", + description = "When a player picks up a spawned_weapon.", + parameters = { + { + name = "ply", + description = "The player who dropped the weapon.", + type = "Player" + }, + { + name = "spawned_weapon", + description = "The spawned_weapon created from the weapon that is dropped.", + type = "Entity" + }, + { + name = "real_weapon", + description = "The actual weapon that will be used by the player.", + type = "Weapon" + } + }, + returns = { + { + name = "ShouldntContinue", + description = "Whether weapon should be picked up or not.", + type = "boolean" + } + } +} diff --git a/gamemodes/darkrp/entities/weapons/weapon_deagle2/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_deagle2/shared.lua new file mode 100644 index 0000000..81552ed --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_deagle2/shared.lua @@ -0,0 +1,46 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 1 + SWEP.SlotPos = 1 + SWEP.IconLetter = "f" + + killicon.AddFont("weapon_deagle2", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "Deagle" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_pist_deagle.mdl" +SWEP.WorldModel = "models/weapons/w_pist_deagle.mdl" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.HoldType = "pistol" +SWEP.LoweredHoldType = "normal" + +SWEP.Primary.Sound = Sound("Weapon_Deagle.Single") +SWEP.Primary.Recoil = 5.1 +SWEP.Primary.Damage = 25 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.01 +SWEP.Primary.ClipSize = 7 +SWEP.Primary.Delay = 0.3 +SWEP.Primary.DefaultClip = 7 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "pistol" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.IronSightsPos = Vector(-6.35, -7.5, 2.02) +SWEP.IronSightsAng = Vector(0.51, 0, 0) diff --git a/gamemodes/darkrp/entities/weapons/weapon_fiveseven2/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_fiveseven2/shared.lua new file mode 100644 index 0000000..49c964a --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_fiveseven2/shared.lua @@ -0,0 +1,45 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 1 + SWEP.SlotPos = 1 + SWEP.IconLetter = "u" + + killicon.AddFont("weapon_fiveseven2", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "FiveSeven" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_pist_fiveseven.mdl" +SWEP.WorldModel = "models/weapons/w_pist_fiveseven.mdl" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false +SWEP.HoldType = "pistol" +SWEP.LoweredHoldType = "normal" + +SWEP.Primary.Sound = Sound("Weapon_FiveSeven.Single") +SWEP.Primary.Recoil = .5 +SWEP.Primary.Damage = 10 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.03 +SWEP.Primary.ClipSize = 21 +SWEP.Primary.Delay = 0.05 +SWEP.Primary.DefaultClip = 21 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "pistol" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.IronSightsPos = Vector(-5.92, -6.2, 3) +SWEP.IronSightsAng = Vector(-0.5, 0.07, 0) diff --git a/gamemodes/darkrp/entities/weapons/weapon_glock2/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_glock2/shared.lua new file mode 100644 index 0000000..c25c118 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_glock2/shared.lua @@ -0,0 +1,48 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Instructions = "Shoot with it" + SWEP.Slot = 1 + SWEP.SlotPos = 0 + SWEP.IconLetter = "c" + + killicon.AddFont("weapon_glock2", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "Glock" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_pist_glock18.mdl" +SWEP.WorldModel = "models/weapons/w_pist_glock18.mdl" +SWEP.HoldType = "pistol" +SWEP.LoweredHoldType = "normal" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.Primary.Sound = Sound("Weapon_Glock.Single") +SWEP.Primary.Recoil = 2 +SWEP.Primary.Unrecoil = 6 +SWEP.Primary.Damage = 10 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.05 +SWEP.Primary.ClipSize = 20 +SWEP.Primary.Delay = 0.06 +SWEP.Primary.DefaultClip = 20 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "pistol" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +--Start of Firemode configuration +SWEP.IronSightsPos = Vector(-5.77, -6.6, 2.7) +SWEP.IronSightsAng = Vector(0.9, 0, 0) diff --git a/gamemodes/darkrp/entities/weapons/weapon_keypadchecker/cl_init.lua b/gamemodes/darkrp/entities/weapons/weapon_keypadchecker/cl_init.lua new file mode 100644 index 0000000..0c0cabf --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_keypadchecker/cl_init.lua @@ -0,0 +1,59 @@ +include("shared.lua") + +local DrawData = {} +local KeypadCheckerHalos + +net.Receive("DarkRP_keypadData", function(len) + DrawData = net.ReadTable() + hook.Add("PreDrawHalos", "KeypadCheckerHalos", KeypadCheckerHalos) +end) + +local lineMat = Material("cable/chain") +local textCol = Color(0, 0, 0, 120) +local haloCol = Color(0, 255, 0, 255) + +function SWEP:DrawHUD() + local screenCenter = ScrH() / 2 + draw.WordBox(2, 10, screenCenter, DarkRP.getPhrase("keypad_checker_shoot_keypad"), "UiBold", textCol, color_white) + draw.WordBox(2, 10, screenCenter + 20, DarkRP.getPhrase("keypad_checker_shoot_entity"), "UiBold", textCol, color_white) + draw.WordBox(2, 10, screenCenter + 40, DarkRP.getPhrase("keypad_checker_click_to_clear"), "UiBold", textCol, color_white) + + local eyePos = EyePos() + local eyeAngles = EyeAngles() + + local entMessages = {} + for k,v in ipairs(DrawData or {}) do + if not IsValid(v.ent) or not IsValid(v.original) then continue end + entMessages[v.ent] = (entMessages[v.ent] or 0) + 1 + local obbCenter = v.ent:OBBCenter() + local pos = v.ent:LocalToWorld(obbCenter):ToScreen() + + local name = v.name and ": " .. v.name:gsub("onDown", DarkRP.getPhrase("keypad_on")):gsub("onUp", DarkRP.getPhrase("keypad_off")) or "" + + draw.WordBox(2, pos.x, pos.y + entMessages[v.ent] * 16, (v.delay and v.delay .. " " .. DarkRP.getPhrase("seconds") .. " " or "") .. v.type .. name, "UiBold", textCol, color_white) + + cam.Start3D(eyePos, eyeAngles) + render.SetMaterial(lineMat) + render.DrawBeam(v.original:GetPos(), v.ent:GetPos(), 2, 0.01, 20, haloCol) + cam.End3D() + end +end + +KeypadCheckerHalos = function() + local drawEnts = {} + local i = 1 + for k,v in ipairs(DrawData) do + if not IsValid(v.ent) then continue end + + drawEnts[i] = v.ent + i = i + 1 + end + + if table.IsEmpty(drawEnts) then return end + halo.Add(drawEnts, haloCol, 5, 5, 5, nil, true) +end + +function SWEP:SecondaryAttack() + DrawData = {} + hook.Remove("PreDrawHalos", "KeypadCheckerHalos") +end diff --git a/gamemodes/darkrp/entities/weapons/weapon_keypadchecker/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_keypadchecker/shared.lua new file mode 100644 index 0000000..14f30d2 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_keypadchecker/shared.lua @@ -0,0 +1,187 @@ +AddCSLuaFile() + +if SERVER then + AddCSLuaFile("cl_init.lua") + + util.AddNetworkString("DarkRP_keypadData") +end + +SWEP.Base = "weapon_base" + +SWEP.PrintName = "Admin Keypad Checker" +SWEP.Author = "DarkRP Developers" +SWEP.Instructions = "Left click on a keypad or fading door to check it\nRight click to clear" +SWEP.Slot = 5 +SWEP.SlotPos = 1 +SWEP.DrawAmmo = false +SWEP.ViewModelFlip = false +SWEP.Primary.ClipSize = 0 +SWEP.Primary.Ammo = "" +SWEP.Secondary.Ammo = "" + +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = "DarkRP (Utility)" + +SWEP.HoldType = "normal" +SWEP.ViewModel = Model("models/weapons/c_pistol.mdl") +SWEP.WorldModel = "models/weapons/w_toolgun.mdl" +SWEP.IconLetter = "" + +SWEP.ViewModel = "models/weapons/c_pistol.mdl" +SWEP.UseHands = true + +local table_insert = table.insert +local tonumber = tonumber + +--[[ + Gets which entities are controlled by which keyboard keys +]] +local function getTargets(keypad, keyPass, keyDenied, delayPass, delayDenied) + local targets = {} + local Owner = keypad:CPPIGetOwner() + + for _, v in ipairs(numpad.OnDownItems or {}) do + if v.key == keyPass and v.ply == Owner then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_entering_right_pass"), name = v.name, ent = v.ent, original = keypad}) + end + if v.key == keyDenied and v.ply == Owner then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_entering_wrong_pass"), name = v.name, ent = v.ent, original = keypad}) + end + end + + for _, v in ipairs(numpad.OnUpItems or {}) do + if v.key == keyPass and v.ply == Owner then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_after_right_pass"), name = v.name, delay = math.Round(delayPass, 2), ent = v.ent, original = keypad}) + end + if v.key == keyDenied and v.ply == Owner then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_after_wrong_pass"), name = v.name, delay = math.Round(delayDenied, 2), ent = v.ent, original = keypad}) + end + end + + return targets +end + +--[[--------------------------------------------------------------------------- +Get the entities that are affected by the keypad +---------------------------------------------------------------------------]] +local function get_sent_keypad_Info(keypad) + local keyPass = keypad:GetNWInt("keypad_keygroup1") + local keyDenied = keypad:GetNWInt("keypad_keygroup2") + local delayPass = keypad:GetNWInt("keypad_length1") + local delayDenied = keypad:GetNWInt("keypad_length2") + + return getTargets(keypad, keyPass, keyDenied, delayPass, delayDenied) +end + +--[[--------------------------------------------------------------------------- +Overload for a different keypad addon +---------------------------------------------------------------------------]] +local function get_keypad_Info(keypad) + local keyPass = tonumber(keypad.KeypadData.KeyGranted) or 0 + local keyDenied = tonumber(keypad.KeypadData.KeyDenied) or 0 + local delayPass = tonumber(keypad.KeypadData.LengthGranted) or 0 + local delayDenied = tonumber(keypad.KeypadData.LengthDenied) or 0 + + return getTargets(keypad, keyPass, keyDenied, delayPass, delayDenied) +end + + +--[[--------------------------------------------------------------------------- +Get the keypads that trigger this entity +---------------------------------------------------------------------------]] +local function getEntityKeypad(ent) + local targets = {} + local doorKeys = {} -- The numpad keys that activate this entity + local entOwner = ent:CPPIGetOwner() + + for _, v in ipairs(numpad.OnDownItems or {}) do + if v.ent == ent then + table_insert(doorKeys, v.key) + end + end + + for _, v in ipairs(numpad.OnUpItems or {}) do + if v.ent == ent then + table_insert(doorKeys, v.key) + end + end + + for _, v in ipairs(ents.FindByClass("sent_keypad")) do + local vOwner = v:CPPIGetOwner() + + if vOwner == entOwner and table.HasValue(doorKeys, v:GetNWInt("keypad_keygroup1")) then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_right_pass_entered"), ent = v, original = ent}) + end + if vOwner == entOwner and table.HasValue(doorKeys, v:GetNWInt("keypad_keygroup2")) then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_wrong_pass_entered"), ent = v, original = ent}) + end + end + + for _, v in ipairs(ents.FindByClass("keypad")) do + local vOwner = v:CPPIGetOwner() + + if vOwner == entOwner and table.HasValue(doorKeys, tonumber(v.KeypadData.KeyGranted) or 0) then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_right_pass_entered"), ent = v, original = ent}) + end + if vOwner == entOwner and table.HasValue(doorKeys, tonumber(v.KeypadData.KeyDenied) or 0) then + table_insert(targets, {type = DarkRP.getPhrase("keypad_checker_wrong_pass_entered"), ent = v, original = ent}) + end + end + + return targets +end + +--[[--------------------------------------------------------------------------- +Send the info to the client +---------------------------------------------------------------------------]] +function SWEP:PrimaryAttack() + self:SetNextPrimaryFire(CurTime() + 0.3) + if not SERVER then return end + + local Owner = self:GetOwner() + local trace = Owner:GetEyeTrace() + if not IsValid(trace.Entity) then return end + local ent, class = trace.Entity, string.lower(trace.Entity:GetClass() or "") + local data + + if class == "sent_keypad" then + data = get_sent_keypad_Info(ent) + DarkRP.notify(Owner, 1, 4, DarkRP.getPhrase("keypad_checker_controls_x_entities", #data / 2)) + elseif class == "keypad" then + data = get_keypad_Info(ent) + DarkRP.notify(Owner, 1, 4, DarkRP.getPhrase("keypad_checker_controls_x_entities", #data / 2)) + else + data = getEntityKeypad(ent) + DarkRP.notify(Owner, 1, 4, DarkRP.getPhrase("keypad_checker_controlled_by_x_keypads", #data)) + end + + net.Start("DarkRP_keypadData") + net.WriteTable(data) + net.Send(Owner) +end + +function SWEP:SecondaryAttack() +end + +if not SERVER then return end + +--[[--------------------------------------------------------------------------- +Registering numpad data +---------------------------------------------------------------------------]] +local oldNumpadUp = numpad.OnUp +local oldNumpadDown = numpad.OnDown + +function numpad.OnUp(ply, key, name, ent, ...) + numpad.OnUpItems = numpad.OnUpItems or {} + table_insert(numpad.OnUpItems, {ply = ply, key = key, name = name, ent = ent, arg = {...}}) + + return oldNumpadUp(ply, key, name, ent, ...) +end + +function numpad.OnDown(ply, key, name, ent, ...) + numpad.OnDownItems = numpad.OnDownItems or {} + table_insert(numpad.OnDownItems, {ply = ply, key = key, name = name, ent = ent, arg = {...}}) + + return oldNumpadDown(ply, key, name, ent, ...) +end diff --git a/gamemodes/darkrp/entities/weapons/weapon_m42/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_m42/shared.lua new file mode 100644 index 0000000..7ce518c --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_m42/shared.lua @@ -0,0 +1,131 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Contact = "" + SWEP.Purpose = "" + SWEP.Instructions = "Hold use and left-click to change firemodes." + SWEP.Slot = 2 + SWEP.SlotPos = 0 + SWEP.IconLetter = "w" + + killicon.AddFont("weapon_m42", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "M4" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_rif_m4a1.mdl" +SWEP.WorldModel = "models/weapons/w_rif_m4a1.mdl" +SWEP.HoldType = "ar2" +SWEP.LoweredHoldType = "passive" +SWEP.DarkRPViewModelBoneManipulations = { + ["ValveBiped.Bip01_Spine4"] = { + scale = Vector(1, 1, 1), + pos = Vector(2, 0, 0), + angle = Angle(0, 0, 0) + }, + ["ValveBiped.Bip01_L_Hand"] = { + scale = Vector(0.7, 0.7, 0.5), + pos = Vector(-0.6, -0.6, 0), + angle = Angle(17, -21, 0) + }, + ["ValveBiped.Bip01_L_Finger0"] = { + scale = Vector(1, 1, 1.5), + pos = Vector(0, 0, 0), + angle = Angle(0, -2, 0) + }, + ["ValveBiped.Bip01_L_Finger1"] = { + scale = Vector(1, 1, 1.5), + pos = Vector(-0.3, -0.8, 0), + angle = Angle(0, -10, 0) + }, + ["ValveBiped.Bip01_L_Finger11"] = { + scale = Vector(1, 1, 1), + pos = Vector(0, 0, 0), + angle = Angle(0, -15, 0) + }, + ["ValveBiped.Bip01_L_Finger12"] = { + scale = Vector(1, 1, 1), + pos = Vector(0, 0, 0), + angle = Angle(0, -14, 0) + }, + ["ValveBiped.Bip01_L_Finger2"] = { + scale = Vector(1, 1, 1.5), + pos = Vector(-0.6, -1, -0), + angle = Angle(0, 7, 0) + }, + ["ValveBiped.Bip01_L_Finger21"] = { + scale = Vector(1, 1, 1), + pos = Vector(0, 0, 0), + angle = Angle(0, -15, 0) + }, + ["ValveBiped.Bip01_L_Finger22"] = { + scale = Vector(0.8, 0.8, 1), + pos = Vector(0, -0.3, 0), + angle = Angle(0, -36, 0) + }, + ["ValveBiped.Bip01_L_Finger3"] = { + scale = Vector(1, 1, 1.5), + pos = Vector(-0.36, -1.2, -0.2), + angle = Angle(-6, -2, 0) + }, + ["ValveBiped.Bip01_L_Finger31"] = { + scale = Vector(1, 1, 1), + pos = Vector(0, -0.1, 0), + angle = Angle(0, -4, 0) + }, + ["ValveBiped.Bip01_L_Finger32"] = { + scale = Vector(1, 1, 1), + pos = Vector(0, -0.2, 0), + angle = Angle(0, -12, 0) + }, + ["ValveBiped.Bip01_L_Finger4"] = { + scale = Vector(1, 1, 1.5), + pos = Vector(-0.3, -1.2, 0.3), + angle = Angle(12, -6.2, -4) + }, + ["ValveBiped.Bip01_L_Finger41"] = { + scale = Vector(1, 1, 1), + pos = Vector(0, 0, 0), + angle = Angle(0, 38, 0) + }, + ["ValveBiped.Bip01_L_Finger42"] = { + scale = Vector(1, 1, 1), + pos = Vector(0, 0, 0), + angle = Angle(0, 30, 0) + } +} + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.Primary.Sound = Sound("Weapon_M4A1.Single") +SWEP.Primary.Recoil = 1.25 +SWEP.Primary.Unrecoil = 8 +SWEP.Primary.Damage = 15 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.03 +SWEP.Primary.ClipSize = 30 +SWEP.Primary.Delay = 0.07 +SWEP.Primary.DefaultClip = 30 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "smg1" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +-- Start of Firemode configuration +SWEP.IronSightsPos = Vector(-8.09, -4.5, 0.56) +SWEP.IronSightsAng = Vector(2.75, -3.97, -3.8) +SWEP.IronSightsPosAfterShootingAdjustment = Vector(0.5, 0, 0) +SWEP.IronSightsAngAfterShootingAdjustment = Vector(0, 1.65, 0) + +SWEP.MultiMode = true diff --git a/gamemodes/darkrp/entities/weapons/weapon_mac102/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_mac102/shared.lua new file mode 100644 index 0000000..5640f40 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_mac102/shared.lua @@ -0,0 +1,46 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 2 + SWEP.SlotPos = 0 + SWEP.IconLetter = "l" + + killicon.AddFont("weapon_mac102", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "Mac10" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_smg_mac10.mdl" +SWEP.WorldModel = "models/weapons/w_smg_mac10.mdl" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.HoldType = "pistol" +SWEP.LoweredHoldType = "normal" + +SWEP.Primary.Sound = Sound("Weapon_mac10.Single") +SWEP.Primary.Recoil = .8 +SWEP.Primary.Damage = 30 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.02 +SWEP.Primary.ClipSize = 25 +SWEP.Primary.Delay = 0.09 +SWEP.Primary.DefaultClip = 25 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "smg1" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.IronSightsPos = Vector(-9.08, -8, 2.6) +SWEP.IronSightsAng = Vector(1.8, -7.06, -6.1) diff --git a/gamemodes/darkrp/entities/weapons/weapon_mp52/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_mp52/shared.lua new file mode 100644 index 0000000..20763f0 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_mp52/shared.lua @@ -0,0 +1,46 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 2 + SWEP.SlotPos = 0 + SWEP.IconLetter = "x" + + killicon.AddFont("weapon_mp52", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "MP5" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_smg_mp5.mdl" +SWEP.WorldModel = "models/weapons/w_smg_mp5.mdl" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.HoldType = "smg" +SWEP.LoweredHoldType = "passive" + +SWEP.Primary.Sound = Sound("Weapon_MP5Navy.Single") +SWEP.Primary.Recoil = 0.5 +SWEP.Primary.Damage = 15 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.005 +SWEP.Primary.ClipSize = 32 +SWEP.Primary.Delay = 0.08 +SWEP.Primary.DefaultClip = 32 +SWEP.Primary.Automatic = true +SWEP.Primary.Ammo = "smg1" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.IronSightsPos = Vector(-5.3, -7, 2.1) +SWEP.IronSightsAng = Vector(0.9, 0.1, 0) diff --git a/gamemodes/darkrp/entities/weapons/weapon_p2282/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_p2282/shared.lua new file mode 100644 index 0000000..61f50c4 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_p2282/shared.lua @@ -0,0 +1,46 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 1 + SWEP.SlotPos = 1 + SWEP.IconLetter = "y" + + killicon.AddFont("weapon_p2282", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +SWEP.Base = "weapon_cs_base2" + +SWEP.PrintName = "P228" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.HoldType = "pistol" +SWEP.LoweredHoldType = "normal" + +SWEP.ViewModel = "models/weapons/cstrike/c_pist_p228.mdl" +SWEP.WorldModel = "models/weapons/w_pist_p228.mdl" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.Primary.Sound = Sound("Weapon_p228.Single") +SWEP.Primary.Recoil = 0.8 +SWEP.Primary.Damage = 10 +SWEP.Primary.NumShots = 1 +SWEP.Primary.Cone = 0.04 +SWEP.Primary.ClipSize = 12 +SWEP.Primary.Delay = 0.1 +SWEP.Primary.DefaultClip = 12 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "pistol" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.IronSightsPos = Vector(-5.985, -6.7, 2.87) +SWEP.IronSightsAng = Vector(-0.3, -0.03, 0) diff --git a/gamemodes/darkrp/entities/weapons/weapon_pumpshotgun2/shared.lua b/gamemodes/darkrp/entities/weapons/weapon_pumpshotgun2/shared.lua new file mode 100644 index 0000000..83bd013 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weapon_pumpshotgun2/shared.lua @@ -0,0 +1,136 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Author = "DarkRP Developers" + SWEP.Slot = 2 + SWEP.SlotPos = 0 + SWEP.IconLetter = "k" + + killicon.AddFont("weapon_pumpshotgun2", "CSKillIcons", SWEP.IconLetter, Color(255, 80, 0, 255)) +end + +DEFINE_BASECLASS("weapon_cs_base2") + +SWEP.PrintName = "Pump Shotgun" +SWEP.Spawnable = true +SWEP.AdminOnly = false +SWEP.Category = "DarkRP (Weapon)" + +SWEP.ViewModel = "models/weapons/cstrike/c_shot_m3super90.mdl" +SWEP.WorldModel = "models/weapons/w_shot_m3super90.mdl" + +SWEP.Weight = 5 +SWEP.AutoSwitchTo = false +SWEP.AutoSwitchFrom = false + +SWEP.HoldType = "shotgun" +SWEP.LoweredHoldType = "normal" + +SWEP.Primary.Sound = Sound("Weapon_M3.Single") +SWEP.Primary.Recoil = 1.5 +SWEP.Primary.Damage = 20 +SWEP.Primary.NumShots = 8 +SWEP.Primary.Cone = 0.08 +SWEP.Primary.ClipSize = 8 +SWEP.Primary.Delay = 0.95 +SWEP.Primary.DefaultClip = 8 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "buckshot" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + +SWEP.IronSightsPos = Vector(-7.64, -8, 3.56) +SWEP.IronSightsAng = Vector(-0.1, 0.02, 0) + +function SWEP:SetupDataTables() + BaseClass.SetupDataTables(self) + -- Float 0 = IronsightsTime + -- Float 1 = LastPrimaryAttack + -- Float 2 = ReloadEndTime + -- Float 3 = BurstTime + self:NetworkVar("Float", 4, "QueuedAttackTime") + -- Bool 0 = IronsightsPredicted + -- Bool 1 = Reloading + self:NetworkVar("Bool", 2, "AttackQueued") +end + +function SWEP:Deploy() + self:SetAttackQueued(false) + + return BaseClass.Deploy(self) +end + +function SWEP:Holster() + self:SetAttackQueued(false) + + return BaseClass.Holster(self) +end + +function SWEP:PrimaryAttack() + if self:GetAttackQueued() then return end + + if self:GetReloading() then + self:SetAttackQueued(true) -- this way it doesn't interupt the reload animation + return + end + + BaseClass.PrimaryAttack(self) +end + +function SWEP:Reload() + -- Already reloading + if self:GetReloading() then return end + + -- Start reloading if we can + if self:Clip1() < self.Primary.ClipSize and self:GetOwner():GetAmmoCount(self.Primary.Ammo) > 0 then + self:SetReloading(true) + self:SetReloadEndTime(CurTime() + 0.3) + self:SendWeaponAnim(ACT_VM_RELOAD) + self:SetIronsights(false) + self:SetHoldType(self.HoldType) + self:GetOwner():SetAnimation(PLAYER_RELOAD) + self:SetHoldType("normal") + end +end + +function SWEP:Think() + self:CalcViewModel() + if self:GetReloadEndTime() ~= 0 and CurTime() >= self:GetReloadEndTime() then + -- Finished reload - + if self:Clip1() >= self.Primary.ClipSize or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 then + self:SetReloading(false) + self:SetReloadEndTime(0) + self:SetAttackQueued(false) + return + end + + if self:GetAttackQueued() then + self:SendWeaponAnim(ACT_SHOTGUN_RELOAD_FINISH) + self:SetReloading(false) + self:SetReloadEndTime(0) + self:SetAttackQueued(false) + self:SetQueuedAttackTime(CurTime() + 0.8) + return + end + + -- Next cycle + self:SetReloadEndTime(CurTime() + 0.3) + self:SendWeaponAnim(ACT_VM_RELOAD) + + -- Add ammo + self:GetOwner():RemoveAmmo(1, self.Primary.Ammo, false) + self:SetClip1(self:Clip1() + 1) + + -- Finish filling, final pump + if self:Clip1() >= self.Primary.ClipSize or self:GetOwner():GetAmmoCount(self.Primary.Ammo) <= 0 then + self:SendWeaponAnim(ACT_SHOTGUN_RELOAD_FINISH) + end + end + if self:GetQueuedAttackTime() ~= 0 and CurTime() >= self:GetQueuedAttackTime() then + self:SetQueuedAttackTime(0) + self:PrimaryAttack() + end +end diff --git a/gamemodes/darkrp/entities/weapons/weaponchecker/shared.lua b/gamemodes/darkrp/entities/weapons/weaponchecker/shared.lua new file mode 100644 index 0000000..8314cb0 --- /dev/null +++ b/gamemodes/darkrp/entities/weapons/weaponchecker/shared.lua @@ -0,0 +1,409 @@ +AddCSLuaFile() + +if CLIENT then + SWEP.Slot = 1 + SWEP.SlotPos = 9 + SWEP.DrawAmmo = false + SWEP.DrawCrosshair = false +end + +SWEP.Author = "DarkRP Developers" +SWEP.Instructions = "Left click to weapon check\nRight click to confiscate weapons\nReload to give back the weapons" +SWEP.Contact = "" +SWEP.Purpose = "" +SWEP.IsDarkRPWeaponChecker = true + +SWEP.ViewModelFOV = 62 +SWEP.ViewModelFlip = false +SWEP.AnimPrefix = "rpg" + +SWEP.PrintName = "Weapon Checker" +SWEP.Spawnable = true +SWEP.AdminOnly = true +SWEP.Category = "DarkRP (Utility)" +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = 0 +SWEP.Primary.Automatic = false +SWEP.Primary.Ammo = "" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = 0 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "" + +SWEP.MinCheckTime = 5 +SWEP.MaxCheckTime = 10 + +DarkRP.hookStub{ + name = "playerWeaponsChecked", + description = "Called when a player with a weapon checker has checked another player's weapons. Note: Only called when the player looks at the weapons without confiscating. Please see playerWeaponsConfiscated for when weapons are actually confiscated.", + parameters = { + { + name = "checker", + description = "The player holding the weapon checker.", + type = "Player" + }, + { + name = "target", + description = "The player whose weapons have been checked.", + type = "Player" + }, + { + name = "weapons", + description = "The weapons that have been checked.", + type = "table" + }, + }, + returns = {}, + realm = "Shared" +} + +DarkRP.hookStub{ + name = "playerWeaponsReturned", + description = "Called when a player with a weapon checker has returned another player's weapons.", + parameters = { + { + name = "checker", + description = "The player holding the weapon checker.", + type = "Player" + }, + { + name = "target", + description = "The player whose weapons have been returned.", + type = "Player" + }, + { + name = "weapons", + description = "The weapons that have been returned.", + type = "table" + }, + }, + returns = {}, + realm = "Server" +} + +DarkRP.hookStub{ + name = "playerWeaponsConfiscated", + description = "Called when a player with a weapon checker has confiscated another player's weapons.", + parameters = { + { + name = "checker", + description = "The player holding the weapon checker.", + type = "Player" + }, + { + name = "target", + description = "The player whose weapons have been confiscated.", + type = "Player" + }, + { + name = "weapons", + description = "The weapons that have been confiscated.", + type = "table" + }, + }, + returns = {}, + realm = "Server" +} + +function SWEP:SetupDataTables() + self:NetworkVar("Bool", 0, "IsWeaponChecking") + self:NetworkVar("Float", 0, "StartCheckTime") + self:NetworkVar("Float", 1, "EndCheckTime") + self:NetworkVar("Float", 2, "NextSoundTime") + self:NetworkVar("Int", 0, "TotalWeaponChecks") +end + +function SWEP:Initialize() + self:SetHoldType("normal") +end + +function SWEP:Deploy() + return true +end + +function SWEP:DrawWorldModel() +end + +function SWEP:PreDrawViewModel(vm) + return true +end + +function SWEP:GetStrippableWeapons(ent, callback) + CAMI.PlayerHasAccess(ent, "DarkRP_GetAdminWeapons", function(access) + for _, v in ipairs(ent:GetWeapons()) do + local class = v:GetClass() + + if GAMEMODE.Config.weaponCheckerHideDefault and (table.HasValue(GAMEMODE.Config.DefaultWeapons, class) or + access and table.HasValue(GAMEMODE.Config.AdminWeapons, class) or + ent:getJobTable() and ent:getJobTable().weapons and table.HasValue(ent:getJobTable().weapons, class)) then + continue + end + + if (GAMEMODE.Config.weaponCheckerHideNoLicense and GAMEMODE.NoLicense[class]) or GAMEMODE.Config.noStripWeapons[class] then continue end + + callback(v) + end + end) +end + +function SWEP:PrimaryAttack() + if self:GetIsWeaponChecking() then return end + self:SetNextPrimaryFire(CurTime() + 0.3) + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + Owner:LagCompensation(true) + local trace = Owner:GetEyeTrace() + Owner:LagCompensation(false) + + local ent = trace.Entity + if not IsValid(ent) or not ent:IsPlayer() or ent:GetPos():DistToSqr(Owner:GetPos()) > 10000 then + return + end + + self:EmitSound("npc/combine_soldier/gear5.wav", 50, 100) + self:SetNextSoundTime(CurTime() + 0.3) + + if not IsFirstTimePredicted() then return end + + local weps = {} + self:GetStrippableWeapons(ent, function(wep) + table.insert(weps, wep) + end) + + hook.Call("playerWeaponsChecked", nil, Owner, ent, weps) + + if not CLIENT then return end + + self:PrintWeapons(ent, DarkRP.getPhrase("persons_weapons", ent:Nick())) +end + +function SWEP:SecondaryAttack() + if self:GetIsWeaponChecking() then return end + self:SetNextSecondaryFire(CurTime() + 0.3) + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + Owner:LagCompensation(true) + local trace = Owner:GetEyeTrace() + Owner:LagCompensation(false) + + local ent = trace.Entity + if not IsValid(ent) or not ent:IsPlayer() or ent:GetPos():DistToSqr(Owner:GetPos()) > 10000 then + return + end + + self:SetIsWeaponChecking(true) + self:SetStartCheckTime(CurTime()) + self:SetEndCheckTime(CurTime() + util.SharedRandom("DarkRP_WeaponChecker" .. self:EntIndex() .. "_" .. self:GetTotalWeaponChecks(), self.MinCheckTime, self.MaxCheckTime)) + self:SetTotalWeaponChecks(self:GetTotalWeaponChecks() + 1) + + self:SetNextSoundTime(CurTime() + 0.5) + + if CLIENT then + self.Dots = "" + self.NextDotsTime = CurTime() + 0.5 + end +end + +function SWEP:Reload() + if CLIENT or CurTime() < (self.NextReloadTime or 0) then return end + self.NextReloadTime = CurTime() + 1 + + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + local trace = Owner:GetEyeTrace() + + local ent = trace.Entity + if not IsValid(ent) or not ent:IsPlayer() or ent:GetPos():DistToSqr(Owner:GetPos()) > 10000 then + return + end + + if not ent.ConfiscatedWeapons then + DarkRP.notify(Owner, 1, 4, DarkRP.getPhrase("no_weapons_confiscated", ent:Nick())) + return + else + ent:RemoveAllAmmo() + for _, v in pairs(ent.ConfiscatedWeapons) do + local wep = ent:Give(v.class, true) + + -- :Give returns NULL when the player already has the weapon + wep = IsValid(wep) and wep or ent:GetWeapon(v.class) + if not IsValid(wep) then continue end + + ent:GiveAmmo(v.primaryAmmoCount, v.primaryAmmoType, true) + ent:GiveAmmo(v.secondaryAmmoCount, v.secondaryAmmoType, true) + + wep:SetClip1(v.clip1) + wep:SetClip2(v.clip2) + + end + DarkRP.notify(Owner, 2, 4, DarkRP.getPhrase("returned_persons_weapons", ent:Nick())) + + hook.Call("playerWeaponsReturned", nil, Owner, ent, ent.ConfiscatedWeapons) + ent.ConfiscatedWeapons = nil + end +end + +function SWEP:Holster() + self:SetIsWeaponChecking(false) + self:SetNextSoundTime(0) + return true +end + +function SWEP:Succeed() + if not IsValid(self:GetOwner()) then return end + self:SetIsWeaponChecking(false) + + local trace = self:GetOwner():GetEyeTrace() + local ent = trace.Entity + if not IsValid(ent) or not ent:IsPlayer() then return end + + if CLIENT then + if not IsFirstTimePredicted() then return end + self:PrintWeapons(ent, DarkRP.getPhrase("confiscated_these_weapons")) + return + end + + local stripped = {} + + self:GetStrippableWeapons(ent, function(wep) + local class = wep:GetClass() + ent:StripWeapon(class) + stripped[class] = { + class = class, + primaryAmmoCount = ent:GetAmmoCount(wep:GetPrimaryAmmoType()), + primaryAmmoType = wep:GetPrimaryAmmoType(), + secondaryAmmoCount = ent:GetAmmoCount(wep:GetSecondaryAmmoType()), + secondaryAmmoType = wep:GetSecondaryAmmoType(), + clip1 = wep:Clip1(), + clip2 = wep:Clip2() + } + end) + + if not ent.ConfiscatedWeapons then + if next(stripped) ~= nil then ent.ConfiscatedWeapons = stripped end + else + -- Merge stripped weapons into confiscated weapons + for k,v in pairs(stripped) do + if ent.ConfiscatedWeapons[k] then continue end + + ent.ConfiscatedWeapons[k] = v + end + end + + hook.Call("playerWeaponsConfiscated", nil, self:GetOwner(), ent, ent.ConfiscatedWeapons) + + if next(stripped) ~= nil then + self:EmitSound("npc/combine_soldier/gear5.wav", 50, 100) + self:SetNextSoundTime(CurTime() + 0.3) + else + self:EmitSound("ambient/energy/zap1.wav", 50, 100) + self:SetNextSoundTime(0) + end +end + +function SWEP:PrintWeapons(ent, weaponsFoundPhrase) + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + local result = {} + local weps = {} + self:GetStrippableWeapons(ent, function(wep) + table.insert(weps, wep) + end) + + for _, wep in ipairs(weps) do + table.insert(result, wep:GetPrintName() and language.GetPhrase(wep:GetPrintName()) or wep:GetClass()) + end + + result = table.concat(result, ", ") + + if result == "" then + Owner:ChatPrint(DarkRP.getPhrase("no_illegal_weapons", ent:Nick())) + return + end + + Owner:ChatPrint(weaponsFoundPhrase) + if string.len(result) >= 126 then + local amount = math.ceil(string.len(result) / 126) + for i = 1, amount, 1 do + Owner:ChatPrint(string.sub(result, (i-1) * 126, i * 126 - 1)) + end + else + Owner:ChatPrint(result) + end +end + +function SWEP:Fail() + self:SetIsWeaponChecking(false) + self:SetHoldType("normal") + self:SetNextSoundTime(0) +end + +function SWEP:Think() + local Owner = self:GetOwner() + + if not IsValid(Owner) then return end + + if self:GetIsWeaponChecking() and self:GetEndCheckTime() ~= 0 then + Owner:LagCompensation(true) + local trace = Owner:GetEyeTrace() + Owner:LagCompensation(false) + if not IsValid(trace.Entity) or trace.HitPos:DistToSqr(Owner:GetShootPos()) > 10000 or not trace.Entity:IsPlayer() then + self:Fail() + end + if self:GetEndCheckTime() <= CurTime() then + self:Succeed() + end + end + if self:GetNextSoundTime() ~= 0 and CurTime() >= self:GetNextSoundTime() then + if self:GetIsWeaponChecking() then + self:SetNextSoundTime(CurTime() + 0.5) + self:EmitSound("npc/combine_soldier/gear5.wav", 100, 100) + else + self:SetNextSoundTime(0) + self:EmitSound("npc/combine_soldier/gear5.wav", 50, 100) + end + end + if CLIENT and self.NextDotsTime and CurTime() >= self.NextDotsTime then + self.NextDotsTime = CurTime() + 0.5 + self.Dots = self.Dots or "" + local len = string.len(self.Dots) + local dots = { + [0] = ".", + [1] = "..", + [2] = "...", + [3] = "" + } + self.Dots = dots[len] + end +end + +local colorBackground = Color(10, 10, 10, 120) + +function SWEP:DrawHUD() + if self:GetIsWeaponChecking() and self:GetEndCheckTime() ~= 0 then + self.Dots = self.Dots or "" + local w = ScrW() + local h = ScrH() + local x, y, width, height = w / 2 - w / 10, h / 2, w / 5, h / 15 + local time = self:GetEndCheckTime() - self:GetStartCheckTime() + local curtime = CurTime() - self:GetStartCheckTime() + local status = math.Clamp(curtime / time, 0, 1) + local BarWidth = status * (width - 16) + local cornerRadius = math.Min(8, BarWidth / 3 * 2 - BarWidth / 3 * 2 % 2) + + draw.RoundedBox(8, x, y, width, height, colorBackground) + draw.RoundedBox(cornerRadius, x + 8, y + 8, BarWidth, height - 16, Color(0, 0 + (status * 255), 255 - (status * 255), 255)) + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("checking_weapons") .. self.Dots, "Trebuchet24", w / 2, y + height / 2, color_white, 1, 1) + end +end diff --git a/gamemodes/darkrp/gamemode/cl_init.lua b/gamemodes/darkrp/gamemode/cl_init.lua new file mode 100644 index 0000000..60fb519 --- /dev/null +++ b/gamemodes/darkrp/gamemode/cl_init.lua @@ -0,0 +1,57 @@ +hook.Run("DarkRPStartedLoading") + +GM.Version = "2.7.0" +GM.Name = "DarkRP" +GM.Author = "By FPtje Falco et al." + +DeriveGamemode("sandbox") +DEFINE_BASECLASS("gamemode_sandbox") +GM.Sandbox = BaseClass + + +local function LoadModules() + local root = GM.FolderName .. "/gamemode/modules/" + local _, folders = file.Find(root .. "*", "LUA") + + for _, folder in SortedPairs(folders, true) do + if DarkRP.disabledDefaults["modules"][folder] then continue end + + for _, File in SortedPairs(file.Find(root .. folder .. "/sh_*.lua", "LUA"), true) do + if File == "sh_interface.lua" then continue end + include(root .. folder .. "/" .. File) + end + + for _, File in SortedPairs(file.Find(root .. folder .. "/cl_*.lua", "LUA"), true) do + if File == "cl_interface.lua" then continue end + include(root .. folder .. "/" .. File) + end + end +end + +GM.Config = {} -- config table +GM.NoLicense = GM.NoLicense or {} + +include("config/config.lua") +include("libraries/sh_cami.lua") +include("libraries/simplerr.lua") +include("libraries/fn.lua") +include("libraries/tablecheck.lua") +include("libraries/interfaceloader.lua") +include("libraries/disjointset.lua") +include("config/licenseweapons.lua") + +include("libraries/modificationloader.lua") + +hook.Call("DarkRPPreLoadModules", GM) + +LoadModules() + +DarkRP.DARKRP_LOADING = true +include("config/jobrelated.lua") +include("config/addentities.lua") +include("config/ammotypes.lua") +DarkRP.DARKRP_LOADING = nil + +DarkRP.finish() + +hook.Call("DarkRPFinishedLoading", GM) diff --git a/gamemodes/darkrp/gamemode/config/DO NOT TOUCH THESE FILES.txt b/gamemodes/darkrp/gamemode/config/DO NOT TOUCH THESE FILES.txt new file mode 100644 index 0000000..b4e0a0a --- /dev/null +++ b/gamemodes/darkrp/gamemode/config/DO NOT TOUCH THESE FILES.txt @@ -0,0 +1,4 @@ +You should not edit DarkRP files. Not even the configuration files. + +Use the DarkRPMod addon instead +https://github.com/FPtje/DarkRPModification diff --git a/gamemodes/darkrp/gamemode/config/_MySQL.lua b/gamemodes/darkrp/gamemode/config/_MySQL.lua new file mode 100644 index 0000000..6e84a0e --- /dev/null +++ b/gamemodes/darkrp/gamemode/config/_MySQL.lua @@ -0,0 +1,9 @@ +RP_MySQLConfig = {} +RP_MySQLConfig.EnableMySQL = false +RP_MySQLConfig.Host = "127.0.0.1" +RP_MySQLConfig.Username = "user" +RP_MySQLConfig.Password = "password" +RP_MySQLConfig.Database_name = "DarkRP" +RP_MySQLConfig.Database_port = 3306 +RP_MySQLConfig.Preferred_module = "mysqloo" +RP_MySQLConfig.MultiStatements = false diff --git a/gamemodes/darkrp/gamemode/config/addentities.lua b/gamemodes/darkrp/gamemode/config/addentities.lua new file mode 100644 index 0000000..e7a2a59 --- /dev/null +++ b/gamemodes/darkrp/gamemode/config/addentities.lua @@ -0,0 +1,166 @@ +DarkRP.createShipment("Глоки отмычки", { + model = "models/weapons/w_pist_glock18.mdl", + entity = "qwb_glock18", + price = 10000, + amount = 10, + separate = false, + pricesep = 160, + noship = false, + allowed = {TEAM_GUN}, + category = "Пистолеты", +}) + +DarkRP.createShipment("Юсп доброграды", { + model = "models/weapons/w_pist_usp.mdl", + entity = "qwb_usp", + price = 8000, + amount = 10, + separate = false, + pricesep = 160, + noship = false, + allowed = {TEAM_GUN}, + category = "Пистолеты", +}) + +DarkRP.createShipment("Юмп сенвуя", { + model = "models/weapons/w_smg_ump45.mdl", + entity = "qwb_ump45", + price = 15000, + amount = 10, + separate = false, + pricesep = nil, + noship = false, + allowed = {TEAM_GUN}, + category = "ПП", +}) + +DarkRP.createShipment("Дробовик жоский", { + model = "models/weapons/w_shot_m3super90.mdl", + entity = "qwb_m3super", + price = 19000, + amount = 10, + separate = false, + pricesep = nil, + noship = false, + allowed = {TEAM_GUN}, + category = "Дробовики", +}) + +DarkRP.createShipment("Авп симпла", { + model = "models/weapons/w_snip_g3sg1.mdl", + entity = "qwb_awp", + price = 27000, + amount = 10, + separate = false, + pricesep = nil, + noship = false, + allowed = {TEAM_GUN}, + category = "Снайперки", +}) + +DarkRP.createEntity("Принтер с урбапетличка рпг", { + ent = "nyxteam_printer", + model = "models/props_lab/reciever01a.mdl", + price = 3000, + max = 2, + cmd = "buymoneyprinter" +}) + +DarkRP.createEntity("Медкит", { + ent = "item_healthkit", + model = "models/Items/HealthKit.mdl", + price = 300, + max = 4, + cmd = "buymedkit" +}) + +DarkRP.createEntity("Арморкит", { + ent = "item_battery", + model = "models/Items/battery.mdl", + price = 300, + max = 4, + cmd = "buyarmorkit" +}) + +if not DarkRP.disabledDefaults["modules"]["hungermod"] then + DarkRP.createEntity("Microwave", { + ent = "microwave", + model = "models/props/cs_office/microwave.mdl", + price = 400, + max = 1, + cmd = "buymicrowave", + allowed = TEAM_COOK + }) +end + +DarkRP.createCategory{ + name = "Другое", + categorises = "entities", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 255, +} + +DarkRP.createCategory{ + name = "Другое", + categorises = "shipments", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 255, +} + +DarkRP.createCategory{ + name = "ПП", + categorises = "shipments", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 100, +} + +DarkRP.createCategory{ + name = "Дробовики", + categorises = "shipments", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 101, +} + +DarkRP.createCategory{ + name = "Снайперки", + categorises = "shipments", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 102, +} + +DarkRP.createCategory{ + name = "Пистолеты", + categorises = "shipments", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 100, +} + +DarkRP.createCategory{ + name = "Другое", + categorises = "weapons", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 255, +} + +DarkRP.createCategory{ + name = "Другое", + categorises = "vehicles", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 255, +} diff --git a/gamemodes/darkrp/gamemode/config/ammotypes.lua b/gamemodes/darkrp/gamemode/config/ammotypes.lua new file mode 100644 index 0000000..25c176a --- /dev/null +++ b/gamemodes/darkrp/gamemode/config/ammotypes.lua @@ -0,0 +1,43 @@ +DarkRP.createAmmoType("pistol", { + name = "Пистолетные патроны", + model = "models/Items/BoxSRounds.mdl", + price = 30, + amountGiven = 24 +}) + +DarkRP.createAmmoType("buckshot", { + name = "Дробовичные патроны", + model = "models/Items/BoxBuckshot.mdl", + price = 50, + amountGiven = 8 +}) + +DarkRP.createAmmoType("smg1", { + name = "ПП патроны", + model = "models/Items/BoxMRounds.mdl", + price = 60, + amountGiven = 30 +}) + +DarkRP.createAmmoType("AR2", { + name = "Винтовочные патроны", + model = "models/Items/BoxMRounds.mdl", + price = 80, + amountGiven = 30 +}) + +DarkRP.createAmmoType("357", { + name = "Револьверные патроны", + model = "models/Items/BoxMRounds.mdl", + price = 50, + amountGiven = 15 +}) + +DarkRP.createCategory{ + name = "Другое", + categorises = "ammo", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 255, +} diff --git a/gamemodes/darkrp/gamemode/config/config.lua b/gamemodes/darkrp/gamemode/config/config.lua new file mode 100644 index 0000000..2e03023 --- /dev/null +++ b/gamemodes/darkrp/gamemode/config/config.lua @@ -0,0 +1,542 @@ +--[[------------------------------------------------------------------------- +DarkRP config settings. +----------------------------------------------------------------------------- + +This is the settings file of DarkRP. Every DarkRP setting is listed here. + +Warning: +If this file is missing settings (because of e.g. an update), DarkRP will assume default values for these settings. +Don't worry about updating this file. If a new setting is added you can manually add them to this file. +---------------------------------------------------------------------------]] + + +--[[ +Toggle settings. +Set to true or false. +]] + +-- voice3D - Enable/disable 3DVoice is enabled. +GM.Config.voice3D = true +-- AdminsCopWeapons - Enable/disable admins spawning with cop weapons. +GM.Config.AdminsCopWeapons = false +-- adminBypassJobCustomCheck - Enable/disable whether an admin can force set a job with whenever customCheck returns false. +GM.Config.adminBypassJobRestrictions = true +-- Acts/Taunts - Enable/disable Taunts (e.g. act salute) +GM.Config.allowActs = false +-- allow people getting their own custom jobs. +GM.Config.allowjobswitch = true +-- allowrpnames - Allow Players to Set their RP names using the /rpname command. +GM.Config.allowrpnames = true +-- allowsprays - Enable/disable the use of sprays on the server. +GM.Config.allowsprays = true +-- allowvehicleowning - Enable/disable whether people can own vehicles. +GM.Config.allowvehicleowning = true +-- allowvnocollide - Enable/disable the ability to no-collide a vehicle (for security). +GM.Config.allowvnocollide = false +-- alltalk - Enable for global chat, disable for local chat. +GM.Config.alltalk = false +-- antimultirun - Disallow people joining your server(s) twice on the same account. +GM.Config.antimultirun = true +-- autovehiclelock - Enable/Disable automatic locking of a vehicle when a player exits it. +GM.Config.autovehiclelock = false +-- babygod - people spawn godded (prevent spawn killing). +GM.Config.babygod = true +-- canforcedooropen - whether players can force an unownable door open with lockpick or battering ram or w/e. +GM.Config.canforcedooropen = true +-- chatsounds - sounds are played when some things are said in chat. +GM.Config.chatsounds = true +-- chiefjailpos - Allow the Chief to set the jail positions. +GM.Config.chiefjailpos = false +-- cit_propertytax - Enable/disable property tax that is exclusive only for citizens. +GM.Config.cit_propertytax = false +-- copscanunfreeze - Enable/disable the ability of cops to unfreeze other people's props. +GM.Config.copscanunfreeze = true +-- copscanunweld - Enable/disable the ability of cops to unweld other people's props. +GM.Config.copscanunweld = false +-- cpcanarrestcp - Allow/Disallow CPs to arrest other CPs. +GM.Config.cpcanarrestcp = true +-- currencyLeft - The position of the currency symbol. true for left, false for right. +GM.Config.currencyLeft = true +-- customjobs - Enable/disable the /job command (personalized job names). +GM.Config.customjobs = true +-- customspawns - Enable/disable whether custom spawns should be used. +GM.Config.customspawns = true +-- deathblack - Whether or not a player sees black on death. +GM.Config.deathblack = false +-- showdeaths - Display kill information in the upper right corner of everyone's screen. +GM.Config.showdeaths = true +-- deadtalk - Enable/disable whether people talk and use commands while dead. +GM.Config.deadtalk = true +-- deadvoice - Enable/disable whether people talk through the microphone while dead. +GM.Config.deadvoice = true +-- deathpov - Enable/disable whether people see their death in first person view. +GM.Config.deathpov = false +-- decalcleaner - Enable/Disable clearing every player's decals. +GM.Config.decalcleaner = false +-- disallowClientsideScripts - Clientside scripts can be very useful for customizing the HUD or to aid in building. This option bans those scripts. +GM.Config.disallowClientsideScripts = false +-- doorwarrants - Enable/disable Warrant requirement to enter property. +GM.Config.doorwarrants = true +-- dropmoneyondeath - Enable/disable whether people drop money on death. +GM.Config.dropmoneyondeath = false +-- droppocketarrest - Enable/disable whether people drop the stuff in their pockets when they get arrested. +GM.Config.droppocketarrest = false +-- droppocketdeath - Enable/disable whether people drop the stuff in their pockets when they die. +GM.Config.droppocketdeath = true +-- dropweapondeath - Enable/disable whether people drop their current weapon when they die. +GM.Config.dropweapondeath = false +-- Whether players can drop the weapons they spawn with. +GM.Config.dropspawnedweapons = false +-- dynamicvoice - Enable/disable whether only people in the same room as you can hear your mic. +GM.Config.dynamicvoice = true +-- earthquakes - Enable/disable earthquakes. +GM.Config.earthquakes = false +-- enablebuypistol - Turn /buy on of off. +GM.Config.enablebuypistol = true +-- enforceplayermodel - Whether or not to force players to use their role-defined character models. +GM.Config.enforceplayermodel = true +-- globalshow - Whether or not to display player info above players' heads in-game. +GM.Config.globalshow = false +-- ironshoot - Enable/disable whether people need iron sights to shoot. +GM.Config.ironshoot = true +-- showjob - Whether or not to display a player's job above their head in-game. +GM.Config.showjob = true +-- letters - Enable/disable letter writing / typing. +GM.Config.letters = true +-- license - Enable/disable People need a license to be able to pick up guns. +GM.Config.license = false +-- lockdown - Enable/Disable initiating lockdowns for mayors. +GM.Config.lockdown = true +-- lockpickfading - Enable/disable the lockpicking of fading doors. +GM.Config.lockpickfading = true +-- logging - Enable/disable logging everything that happens. +GM.Config.logging = true +-- lottery - Enable/disable creating lotteries for mayors. +GM.Config.lottery = true +-- showname - Whether or not to display a player's name above their head in-game. +GM.Config.showname = true +-- showhealth - Whether or not to display a player's health above their head in-game. +GM.Config.showhealth = true +-- needwantedforarrest - Enable/disable Cops can only arrest wanted people. +GM.Config.needwantedforarrest = false +-- noguns - Enabling this feature bans Guns and Gun Dealers. +GM.Config.noguns = false +-- norespawn - Enable/Disable that people don't have to respawn when they change job. +GM.Config.norespawn = true +-- keepPickedUp - Enable/Disable keeping picked up weapons when switching jobs. +GM.Config.keepPickedUp = false +-- instantjob - Enable/Disable instantly respawning when norespawn is false +GM.Config.instantjob = false +-- npcarrest - Enable/disable arresting npc's. +GM.Config.npcarrest = false +-- ooc - Whether or not OOC tags are enabled. +GM.Config.ooc = true +-- propertytax - Enable/disable property tax. +GM.Config.propertytax = false +-- proppaying - Whether or not players should pay for spawning props. +GM.Config.proppaying = false +-- propspawning - Enable/disable props spawning. Applies to admins too. +GM.Config.propspawning = true +-- removeclassitems - Enable/disable shipments/microwaves/etc. removal when someone changes team. +GM.Config.removeclassitems = true +-- removeondisconnect - Enable/disable shipments/microwaves/etc. removal when someone disconnects. +GM.Config.removeondisconnect = true +-- respawninjail - Enable/disable whether people can respawn in jail when they die. +GM.Config.respawninjail = true +-- restrictallteams - Enable/disable Players can only be citizen until an admin allows them. +GM.Config.restrictallteams = false +-- restrictbuypistol - Enabling this feature makes /buy available only to Gun Dealers. +GM.Config.restrictbuypistol = false +-- restrictdrop - Enable/disable restricting the weapons players can drop. Setting this to true disallows weapons from shipments from being dropped. +GM.Config.restrictdrop = false +-- revokeLicenseOnJobChange - Whether licenses are revoked when a player changes jobs. +GM.Config.revokeLicenseOnJobChange = true +-- shouldResetLaws - Enable/Disable resetting the laws back to the default law set when the mayor changes. +GM.Config.shouldResetLaws = false +-- strictsuicide - Whether or not players should spawn where they suicided. +GM.Config.strictsuicide = false +-- telefromjail - Enable/disable teleporting from jail. +GM.Config.telefromjail = true +-- teletojail - Enable/disable teleporting to jail. +GM.Config.teletojail = true +-- unlockdoorsonstart - Enable/Disable unlocking all doors on map start. +GM.Config.unlockdoorsonstart = false +-- voiceradius - Enable/disable local voice chat. +GM.Config.voiceradius = true +-- tax - Whether players pay taxes on their wallets. +GM.Config.wallettax = false +-- wantedrespawn - Whether players remain wanted on respawn. +GM.Config.wantedrespawn = false +-- wantedsuicide - Enable/Disable suiciding while you are wanted by the police. +GM.Config.wantedsuicide = false +-- realisticfalldamage - Enable/Disable dynamic fall damage. Setting mp_falldamage to 1 will over-ride this. +GM.Config.realisticfalldamage = true +-- printeroverheat - Whether the default money printer can overheat on its own. +GM.Config.printeroverheat = true +-- weaponCheckerHideDefault - Hide default weapons when checking weapons. +GM.Config.weaponCheckerHideDefault = true +-- weaponCheckerHideNoLicense - Hide weapons that do not require a license. +GM.Config.weaponCheckerHideNoLicense = false + +--[[ +Value settings +]] +-- adminnpcs - Whether or not NPCs should be admin only. 0 = everyone, 1 = admin or higher, 2 = superadmin or higher, 3 = rcon only +GM.Config.adminnpcs = 2 +-- adminsents - Whether or not SENTs should be admin only. 0 = everyone, 1 = admin or higher, 2 = superadmin or higher, 3 = rcon only +GM.Config.adminsents = 2 +-- adminvehicles - Whether or not vehicles should be admin only. 0 = everyone, 1 = admin or higher, 2 = superadmin or higher, 3 = rcon only +GM.Config.adminvehicles = 2 +-- adminweapons - Who can spawn weapons: 0: admins only, 1: supadmins only, 2: no one, 3: everyone +GM.Config.adminweapons = 1 +-- arrestspeed - Sets the max arrest speed. +GM.Config.arrestspeed = 120 +-- babygodtime - How long the babygod lasts. +GM.Config.babygodtime = 5 +-- chatsoundsdelay - How long to wait before letting a player emit a sound from their chat again. +-- Leave this on at least a few seconds to prevent people from spamming sounds. Set to 0 to disable. +GM.Config.chatsoundsdelay = 5 +-- deathfee - the amount of money someone drops when dead. +GM.Config.deathfee = 30 +-- decaltimer - Sets the time to clear clientside decals (in seconds). +GM.Config.decaltimer = 120 +-- demotetime - Number of seconds before a player can rejoin a team after demotion from that team. +GM.Config.demotetime = 120 +-- doorcost - Sets the cost of a door. +GM.Config.doorcost = 30 +-- EntitySpamTime - Antispam time between spawning entities. +GM.Config.EntitySpamTime = 2 +-- entremovedelay - how long to wait before removing a bought entity after disconnect. +GM.Config.entremovedelay = 0 +-- gunlabweapon - The weapon that the gunlab spawns. +GM.Config.gunlabweapon = "weapon_p2282" +-- jailtimer - Sets the jailtimer (in seconds). +GM.Config.jailtimer = 120 +-- lockdowndelay - The amount of time a mayor must wait before starting the next lockdown. +GM.Config.lockdowndelay = 120 +-- maxadvertbillboards - The maximum number of /advert billboards a player can place. +GM.Config.maxadvertbillboards = 3 +-- maxCheques - The maximum number of cheques someone can write +GM.Config.maxCheques = 0 +-- maxdoors - Sets the max amount of doors one can own. +GM.Config.maxdoors = 20 +-- maxdrugs - Sets max drugs. +GM.Config.maxdrugs = 2 +-- maxfoods - Sets the max food cartons per Microwave owner. +GM.Config.maxfoods = 2 +-- maxfooditems - Sets the max amount of food items a player can buy from the F4 menu. +GM.Config.maxfooditems = 20 +-- maxlawboards - The maximum number of law boards the mayor can place. +GM.Config.maxlawboards = 2 +-- maxletters - Sets max letters. +GM.Config.maxletters = 10 +-- maxlotterycost - Maximum payment the mayor can set to join a lottery. +GM.Config.maxlotterycost = 250 +-- maxvehicles - Sets how many vehicles one can buy. +GM.Config.maxvehicles = 5 +-- microwavefoodcost - Sets the sale price of Microwave Food. +GM.Config.microwavefoodcost = 30 +-- gunlabguncost - Sets the initial price of a gun from a gunlab. Note that the +-- gunlab owner can change this price. +GM.Config.gunlabguncost = 200 +-- druglabdrugcost - Sets the initial price of drugs from a drugs lab. Note that +-- the drugs lab owner can change this price. +GM.Config.druglabdrugcost = 100 +-- minlotterycost - Minimum payment the mayor can set to join a lottery. +GM.Config.minlotterycost = 30 +-- Money packets will get removed if they don't get picked up after a while. Set to 0 to disable. +GM.Config.moneyRemoveTime = 600 +-- mprintamount - Value of the money printed by the money printer. +GM.Config.mprintamount = 250 +-- normalsalary - Sets the starting salary for newly joined players. +GM.Config.normalsalary = 45 +-- npckillpay - Sets the money given for each NPC kill. +GM.Config.npckillpay = 10 +-- paydelay - Sets how long it takes before people get salary. +GM.Config.paydelay = 160 +-- pocketitems - Sets the amount of objects the pocket can carry. +GM.Config.pocketitems = 10 +-- pricecap - The maximum price of items (using /price). +GM.Config.pricecap = 500 +-- pricemin - The minimum price of items (using /price). +GM.Config.pricemin = 50 +-- propcost - How much prop spawning should cost (prop paying must be enabled for this to have an effect). +GM.Config.propcost = 10 +-- quakechance - Chance of an earthquake happening. +GM.Config.quakechance = 4000 +-- respawntime - Minimum amount of seconds a player has to wait before respawning. +GM.Config.respawntime = 1 +-- changejobtime - Minimum amount of seconds a player has to wait before changing job. +GM.Config.changejobtime = 10 +-- runspeed - Sets the max running speed. +GM.Config.runspeed = 240 +-- runspeed - Sets the max running speed for CP teams. +GM.Config.runspeedcp = 255 +-- searchtime - Number of seconds for which a search warrant is valid. +GM.Config.searchtime = 30 +-- ShipmentSpamTime - Antispam time between spawning shipments. +GM.Config.ShipmentSpamTime = 3 +-- shipmenttime - The number of seconds it takes for a shipment to spawn. +GM.Config.shipmentspawntime = 10 +-- startinghealth - the health when you spawn. +GM.Config.startinghealth = 100 +-- startingmoney - your wallet when you join for the first time. +GM.Config.startingmoney = 20000 +-- stunstickdamage - amount of damage the stunstick will do to entities. +-- When between 0 and 1, the damage is relative, where 1 takes the entire health of the entity. +-- When above 1, the damage is absolute +GM.Config.stunstickdamage = 1000 +-- vehiclecost - Sets the cost of a vehicle (To own it). +GM.Config.vehiclecost = 40 +-- wallettaxmax - Maximum percentage of tax to be paid. +GM.Config.wallettaxmax = 5 +-- wallettaxmin - Minimum percentage of tax to be paid. +GM.Config.wallettaxmin = 1 +-- wallettaxtime - Time in seconds between taxing players. Requires server restart. +GM.Config.wallettaxtime = 600 +-- wantedtime - Number of seconds for which a player is wanted for. +GM.Config.wantedtime = 120 +-- walkspeed - Sets the max walking speed. +GM.Config.walkspeed = 160 +-- falldamagedamper - The damper on realistic fall damage. Default is 15. Decrease this for more damage. +GM.Config.falldamagedamper = 15 +-- falldamageamount - The base damage taken from falling for static fall damage. Default is 10. +GM.Config.falldamageamount = 10 +-- printeroverheatchance - The likelyhood of a printer overheating. The higher this number, the less likely (minimum 3, default 22). +GM.Config.printeroverheatchance = 22 +-- printerreward - Reward for destroying a money printer. +GM.Config.printerreward = 950 + +--[[--------------------------------------------------------------------------- +Chat distance settings +Distance is in source units (similar to inches) +---------------------------------------------------------------------------]] +GM.Config.talkDistance = 250 +GM.Config.whisperDistance = 90 +GM.Config.yellDistance = 550 +GM.Config.meDistance = 250 +GM.Config.voiceDistance = 550 + +--[[--------------------------------------------------------------------------- +Other settings +---------------------------------------------------------------------------]] + +-- The classname of money packets. Use this to create your own money entity! +-- Note: the money packet must support the "Setamount" method (or the amount DTVar). +GM.Config.MoneyClass = "spawned_money" +-- In case you do wish to keep the default money, but change the model, this option is the way to go: +GM.Config.moneyModel = "models/props/cs_assault/money.mdl" +-- You can set your own, custom sound to be played for all players whenever a lockdown is initiated. +-- Note: Remember to include the folder where the sound file is located. +GM.Config.lockdownsound = "npc/overwatch/cityvoice/f_confirmcivilstatus_1_spkr.wav" + +-- The skin DarkRP uses. Set to "default" to use the GMod default derma theme. +GM.Config.DarkRPSkin = "DarkRP" +GM.Config.currency = "$" +GM.Config.currencyThousandSeparator = "," +GM.Config.chatCommandPrefix = "/" +GM.Config.F1MenuHelpPage = "https://darkrp.miraheze.org/wiki/Main_Page" +GM.Config.F1MenuHelpPageTitle = "DarkRP Wiki" + +-- The sound that plays when you get a DarkRP notification +GM.Config.notificationSound = "buttons/lightswitch2.wav" + +-- Put Steam ID's and ranks in this list, and the players will have that rank when they join. +GM.Config.DefaultPlayerGroups = { + ["STEAM_0:0:00000000"] = "superadmin", + ["STEAM_0:0:11111111"] = "admin", +} + +-- Custom modules in this addon that are disabled. +GM.Config.DisabledCustomModules = { + ["hudreplacement"] = false, + ["extraf4tab"] = false, +} + +-- The list of weapons that players are not allowed to drop. Items set to true are not allowed to be dropped. +GM.Config.DisallowDrop = { + ["arrest_stick"] = true, + ["door_ram"] = true, + ["gmod_camera"] = true, + ["gmod_tool"] = true, + ["keys"] = true, + ["lockpick"] = true, + ["med_kit"] = true, + ["pocket"] = true, + ["stunstick"] = true, + ["unarrest_stick"] = true, + ["weapon_keypadchecker"] = true, + ["weapon_physcannon"] = true, + ["weapon_physgun"] = true, + ["weaponchecker"] = true, +} + +-- The list of weapons people spawn with. +GM.Config.DefaultWeapons = { + "keys", + "weapon_physcannon", + "gmod_camera", + "gmod_tool", + "itemstore_pickup", + "weapon_physgun", +} + +-- Override categories. +-- NOTE: categories are to be set in the "category" field of the custom jobs/shipments/entities/ammo/pistols/vehicles. +-- Use this only to override the categories of _default_ things. +-- This will NOT work for your own custom stuff. +-- Make sure the category is created in the darkrp_customthings/categories.lua, otherwise it won't work! +GM.Config.CategoryOverride = { + -- jobs = { + -- ["Citizen"] = "Citizens", + -- ["Hobo"] = "Citizens", + -- ["Gun Dealer"] = "Citizens", + -- ["Medic"] = "Citizens", + -- ["Civil Protection"] = "Civil Protection", + -- ["Gangster"] = "Gangsters", + -- ["Mob boss"] = "Gangsters", + -- ["Civil Protection Chief"] = "Civil Protection", + -- ["Mayor"] = "Civil Protection", + -- }, + -- entities = { + -- ["Drug lab"] = "Other", + -- ["Money printer"] = "Other", + -- ["Gun lab"] = "Other", + + -- }, + -- shipments = { + -- ["AK47"] = "Rifles", + -- ["MP5"] = "Rifles", + -- ["M4"] = "Rifles", + -- ["Mac 10"] = "Other", + -- ["Pump shotgun"] = "Shotguns", + -- ["Sniper rifle"] = "Snipers", + + -- }, + -- weapons = { + -- ["Desert eagle"] = "Pistols", + -- ["Fiveseven"] = "Pistols", + -- ["Glock"] = "Pistols", + -- ["P228"] = "Pistols", + -- }, + -- vehicles = {}, -- There are no default vehicles. + -- ammo = { + -- ["Pistol ammo"] = "Other", + -- ["Shotgun ammo"] = "Other", + -- ["Rifle ammo"] = "Other", + -- }, +} + +-- The list of weapons admins spawn with, in addition to the default weapons, a job's weapons and GM.Config.AdminsCopWeapons. +GM.Config.AdminWeapons = { + "weapon_keypadchecker", +} + +-- These are the default laws, they're unchangeable in-game. +GM.Config.DefaultLaws = { + "Нелья пиздиться без причины.", + "Нельзя грабить воровать.", + "Маники и накрота запрещены.", +} + +GM.Config.PocketBlacklist = { + ["fadmin_jail"] = true, + ["meteor"] = true, + ["door"] = true, + ["func_"] = true, + ["player"] = true, + ["beam"] = true, + ["worldspawn"] = true, + ["env_"] = true, + ["path_"] = true, + ["prop_physics"] = true, + ["money_printer"] = true, + ["gunlab"] = true, + ["prop_dynamic"] = true, + ["prop_vehicle_prisoner_pod"] = true, + ["keypad_wire"] = true, + ["gmod_button"] = true, + ["gmod_rtcameraprop"] = true, + ["gmod_cameraprop"] = true, + ["gmod_dynamite"] = true, + ["gmod_thruster"] = true, + ["gmod_light"] = true, + ["gmod_lamp"] = true, + ["gmod_emitter"] = true, +} + +-- These weapons are classed as 'legal' in the weapon checker and are not stripped when confiscating weapons. +-- This setting is used IN ADDITION to GM.Config.weaponCheckerHideDefault and GM.Config.weaponCheckerHideNoLicense. +-- You should use the former if you want to class the default weapons (GM.Config.DefaultWeapons and, if admin, GM.Config.AdminWeapons) and a player's job weapons as legal. +-- The latter takes GM.NoLicense weapons as legal (see licenseweapons.lua). +-- The format of this config is similar to GM.Config.DisallowDrop. +GM.Config.noStripWeapons = { + +} + +-- The entities listed here will not be removed when a player changes their job. +-- This only applies when removeclassitems is set to true. +-- Note: entities will only be removed when the player changes to a job that is not allowed to have the entity. +GM.Config.preventClassItemRemoval = { + ["gunlab"] = false, + ["microwave"] = false, + ["spawned_shipment"] = false, +} + +-- Properties set to true are allowed to be used. Values set to false or are missing from this list are blocked. +GM.Config.allowedProperties = { + remover = true, + ignite = false, + extinguish = true, + keepupright = true, + gravity = true, + collision = true, + skin = true, + bodygroups = true, +} + +--[[--------------------------------------------------------------------------- +F4 menu +---------------------------------------------------------------------------]] +-- hide the items that you can't buy and the jobs you can't get (instead of graying them out). +-- this option hides items when you don't have enough money, when the maximum is reached for a job or any other reason. +GM.Config.hideNonBuyable = false + +-- Hide only the items that you have the wrong job for (or for which the customCheck says no). +-- When you set this option to true and hideNonBuyable to false, you WILL see e.g. items that are too expensive for you to buy. +-- but you won't see gundealer shipments when you have the citizen job. +GM.Config.hideTeamUnbuyable = true + +--[[--------------------------------------------------------------------------- +AFK module +---------------------------------------------------------------------------]] +-- The time of inactivity before being demoted. +GM.Config.afkdemotetime = 600 +-- Prevent people from spamming AFK. +GM.Config.AFKDelay = 300 + +--[[--------------------------------------------------------------------------- +Hitmenu module +---------------------------------------------------------------------------]] +-- The minimum price for a hit. +GM.Config.minHitPrice = 200 +-- The maximum price for a hit. +GM.Config.maxHitPrice = 50000 +-- The minimum distance between a hitman and his customer when they make the deal. +GM.Config.minHitDistance = 150 +-- The text that tells the player he can press use on the hitman to request a hit. +GM.Config.hudText = "I am a hitman.\nPress E on me to request a hit!" +-- The text above a hitman when he's got a hit. +GM.Config.hitmanText = "Hit\naccepted!" +-- The cooldown time for a hit target (so they aren't spam killed). +GM.Config.hitTargetCooldown = 120 +-- How long a customer has to wait to be able to buy another hit (from the moment the hit is accepted). +GM.Config.hitCustomerCooldown = 240 + +--[[--------------------------------------------------------------------------- +Hungermod module +---------------------------------------------------------------------------]] +-- hungerspeed - Set the rate at which players will become hungry (2 is the default). +GM.Config.hungerspeed = 2 +-- starverate - How much health that is taken away every second the player is starving (3 is the default). +GM.Config.starverate = 3 diff --git a/gamemodes/darkrp/gamemode/config/jobrelated.lua b/gamemodes/darkrp/gamemode/config/jobrelated.lua new file mode 100644 index 0000000..c8134c3 --- /dev/null +++ b/gamemodes/darkrp/gamemode/config/jobrelated.lua @@ -0,0 +1,270 @@ +-- People often copy jobs. When they do, the GM table does not exist anymore. +-- This line makes the job code work both inside and outside of gamemode files. +-- You should not copy this line into your code. +local GAMEMODE = GAMEMODE or GM +--[[-------------------------------------------------------- +Default teams. Please do not edit this file. Please use the darkrpmod addon instead. +--------------------------------------------------------]] +TEAM_CITIZEN = DarkRP.createJob("Гражданин", { + color = Color(20, 150, 20, 255), + model = { + "models/Humans/Group02/Player/Tale_01.mdl", + "models/Humans/Group02/Player/Tale_03.mdl", + "models/Humans/Group02/Player/Tale_04.mdl", + "models/Humans/Group02/Player/Tale_05.mdl", + "models/Humans/Group02/Player/Tale_06.mdl", + "models/Humans/Group02/Player/Tale_07.mdl", + "models/Humans/Group02/Player/Tale_08.mdl", + "models/Humans/Group02/Player/Tale_09.mdl", + + "models/Humans/Group02/Player/Temale_01.mdl", + "models/Humans/Group02/Player/Temale_02.mdl", + "models/Humans/Group02/Player/Temale_07.mdl" + }, + description = [[Обычный пясун йобнуца.]], + weapons = {}, + command = "citizen", + max = 0, + salary = GAMEMODE.Config.normalsalary, + admin = 0, + vote = false, + hasLicense = false, + candemote = false, + category = "Гражданские", +}) + +TEAM_POLICE = DarkRP.createJob("Мент", { + color = Color(25, 25, 170, 255), + model = {"models/player/police.mdl", "models/player/police_fem.mdl"}, + description = [[Мусорок йобнуца.]], + weapons = {"arrest_stick", "unarrest_stick", "qwb_glock18", "stunstick", "door_ram", "weaponchecker"}, + command = "cp", + max = 4, + salary = GAMEMODE.Config.normalsalary * 1.45, + admin = 0, + vote = true, + hasLicense = true, + ammo = { + ["pistol"] = 60, + }, + category = "Менты", +}) + +TEAM_GANG = DarkRP.createJob("Гангстер", { + color = Color(75, 75, 75, 255), + model = { + "models/player/Group03/Female_01.mdl", + "models/player/Group03/Female_02.mdl", + "models/player/Group03/Female_03.mdl", + "models/player/Group03/Female_04.mdl", + "models/player/Group03/Female_06.mdl", + "models/player/group03/male_01.mdl", + "models/player/Group03/Male_02.mdl", + "models/player/Group03/male_03.mdl", + "models/player/Group03/Male_04.mdl", + "models/player/Group03/Male_05.mdl", + "models/player/Group03/Male_06.mdl", + "models/player/Group03/Male_07.mdl", + "models/player/Group03/Male_08.mdl", + "models/player/Group03/Male_09.mdl"}, + description = [[Бандит чтобы с ментами пиздиться.]], + weapons = {}, + command = "gangster", + max = 3, + salary = GAMEMODE.Config.normalsalary, + admin = 0, + vote = false, + hasLicense = false, + category = "Бандиты", +}) + +TEAM_MOB = DarkRP.createJob("Пахан", { + color = Color(25, 25, 25, 255), + model = "models/player/gman_high.mdl", + description = [[Заправляет гангстерами.]], + weapons = {"lockpick", "unarrest_stick"}, + command = "mobboss", + max = 1, + salary = GAMEMODE.Config.normalsalary * 1.34, + admin = 0, + vote = false, + hasLicense = false, + category = "Бандиты", +}) + +TEAM_GUN = DarkRP.createJob("Продавец оружия", { + color = Color(255, 140, 0, 255), + model = "models/player/monk.mdl", + description = [[Продает пушки доброградовские.]], + weapons = {}, + command = "gundealer", + max = 2, + salary = GAMEMODE.Config.normalsalary, + admin = 0, + vote = false, + hasLicense = false, + category = "Гражданские", +}) + +TEAM_MEDIC = DarkRP.createJob("Медик", { + color = Color(47, 79, 79, 255), + model = "models/player/kleiner.mdl", + description = [[Юзлесс профа тут но похуй пусть будет.]], + weapons = {"med_kit"}, + command = "medic", + max = 3, + salary = GAMEMODE.Config.normalsalary, + admin = 0, + vote = false, + hasLicense = false, + medic = true, + category = "Гражданские", +}) + +TEAM_CHIEF = DarkRP.createJob("Глава ментов", { + color = Color(20, 20, 255, 255), + model = "models/player/combine_soldier_prisonguard.mdl", + description = [[Управляешь мусорками.]], + weapons = {"arrest_stick", "unarrest_stick", "qwb_ump45", "stunstick", "door_ram", "weaponchecker"}, + command = "chief", + max = 1, + salary = GAMEMODE.Config.normalsalary * 1.67, + admin = 0, + vote = false, + hasLicense = true, + chief = true, + NeedToChangeFrom = TEAM_POLICE, + ammo = { + ["pistol"] = 60, + }, + category = "Менты", +}) + +TEAM_MAYOR = DarkRP.createJob("Мэр", { + color = Color(150, 20, 20, 255), + model = "models/player/breen.mdl", + description = [[Создаешь законы и прочую хуйню делаешь]], + weapons = {}, + command = "mayor", + max = 1, + salary = GAMEMODE.Config.normalsalary * 1.89, + admin = 0, + vote = true, + hasLicense = false, + mayor = true, + category = "Менты", +}) + +TEAM_HOBO = DarkRP.createJob("Бомж", { + color = Color(80, 45, 0, 255), + model = "models/player/corpse1.mdl", + description = [[Вам сасет сенвай хотябы ладно]], + weapons = {"weapon_bugbait"}, + command = "hobo", + max = 5, + salary = 0, + admin = 0, + vote = false, + hasLicense = false, + candemote = false, + hobo = true, + category = "Гражданские", +}) + +if not DarkRP.disabledDefaults["modules"]["hungermod"] then + TEAM_COOK = DarkRP.createJob("Cook", { + color = Color(238, 99, 99, 255), + model = "models/player/mossman.mdl", + description = [[As a cook, it is your responsibility to feed the other members of your city. + You can spawn a microwave and sell the food you make: + /buymicrowave]], + weapons = {}, + command = "cook", + max = 2, + salary = 45, + admin = 0, + vote = false, + hasLicense = false, + cook = true + }) +end + +-- Compatibility for when default teams are disabled +TEAM_CITIZEN = TEAM_CITIZEN or -1 +TEAM_POLICE = TEAM_POLICE or -1 +TEAM_GANG = TEAM_GANG or -1 +TEAM_MOB = TEAM_MOB or -1 +TEAM_GUN = TEAM_GUN or -1 +TEAM_MEDIC = TEAM_MEDIC or -1 +TEAM_CHIEF = TEAM_CHIEF or -1 +TEAM_MAYOR = TEAM_MAYOR or -1 +TEAM_HOBO = TEAM_HOBO or -1 +TEAM_COOK = TEAM_COOK or -1 + +-- Door groups +AddDoorGroup("Менты", TEAM_CHIEF, TEAM_POLICE, TEAM_MAYOR) +AddDoorGroup("Оружейник", TEAM_GUN) + + +-- Agendas +DarkRP.createAgenda("Гангстерская повестка", TEAM_MOB, {TEAM_GANG}) +DarkRP.createAgenda("Ментовская повестка", {TEAM_MAYOR, TEAM_CHIEF}, {TEAM_POLICE}) + +-- Group chats +DarkRP.createGroupChat(function(ply) return ply:isCP() end) +DarkRP.createGroupChat(TEAM_MOB, TEAM_GANG) +DarkRP.createGroupChat(function(listener, ply) return not ply or ply:Team() == listener:Team() end) + +-- Initial team when first spawning +GAMEMODE.DefaultTeam = TEAM_CITIZEN + +-- Teams that belong to Менты +GAMEMODE.CivilProtection = { + [TEAM_POLICE] = true, + [TEAM_CHIEF] = true, + [TEAM_MAYOR] = true, +} + +-- Hitman team +-- DarkRP.addHitmanTeam(TEAM_MOB) + +-- Demote groups +DarkRP.createDemoteGroup("Менты", {TEAM_POLICE, TEAM_CHIEF}) +DarkRP.createDemoteGroup("Гангстеры", {TEAM_GANG, TEAM_MOB}) + +-- Default categories +DarkRP.createCategory{ + name = "Гражданские", + categorises = "jobs", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 100, +} + +DarkRP.createCategory{ + name = "Менты", + categorises = "jobs", + startExpanded = true, + color = Color(25, 25, 170, 255), + canSee = fp{fn.Id, true}, + sortOrder = 101, +} + +DarkRP.createCategory{ + name = "Бандиты", + categorises = "jobs", + startExpanded = true, + color = Color(75, 75, 75, 255), + canSee = fp{fn.Id, true}, + sortOrder = 101, +} + +DarkRP.createCategory{ + name = "Другие", + categorises = "jobs", + startExpanded = true, + color = Color(0, 107, 0, 255), + canSee = fp{fn.Id, true}, + sortOrder = 255, +} diff --git a/gamemodes/darkrp/gamemode/config/licenseweapons.lua b/gamemodes/darkrp/gamemode/config/licenseweapons.lua new file mode 100644 index 0000000..08b3ea9 --- /dev/null +++ b/gamemodes/darkrp/gamemode/config/licenseweapons.lua @@ -0,0 +1,5 @@ +GM.NoLicense["weapon_physcannon"] = true +GM.NoLicense["weapon_physgun"] = true +GM.NoLicense["weapon_bugbait"] = true +GM.NoLicense["gmod_tool"] = true +GM.NoLicense["gmod_camera"] = true diff --git a/gamemodes/darkrp/gamemode/init.lua b/gamemodes/darkrp/gamemode/init.lua new file mode 100644 index 0000000..d31e69e --- /dev/null +++ b/gamemodes/darkrp/gamemode/init.lua @@ -0,0 +1,114 @@ +hook.Run("DarkRPStartedLoading") + +GM.Version = "2.7.0" +GM.Name = "DarkRP" +GM.Author = "By FPtje Falco et al." + +DeriveGamemode("sandbox") +DEFINE_BASECLASS("gamemode_sandbox") + +GM.Sandbox = BaseClass + + +AddCSLuaFile("libraries/sh_cami.lua") +AddCSLuaFile("libraries/simplerr.lua") +AddCSLuaFile("libraries/interfaceloader.lua") +AddCSLuaFile("libraries/modificationloader.lua") +AddCSLuaFile("libraries/disjointset.lua") +AddCSLuaFile("libraries/fn.lua") +AddCSLuaFile("libraries/tablecheck.lua") + +AddCSLuaFile("config/config.lua") +AddCSLuaFile("config/addentities.lua") +AddCSLuaFile("config/jobrelated.lua") +AddCSLuaFile("config/ammotypes.lua") +AddCSLuaFile("config/licenseweapons.lua") + +AddCSLuaFile("cl_init.lua") + +GM.Config = GM.Config or {} +GM.NoLicense = GM.NoLicense or {} + +include("libraries/interfaceloader.lua") + +include("config/_MySQL.lua") +include("config/config.lua") +include("config/licenseweapons.lua") + +include("libraries/fn.lua") +include("libraries/tablecheck.lua") +include("libraries/sh_cami.lua") +include("libraries/simplerr.lua") +include("libraries/modificationloader.lua") +include("libraries/mysqlite/mysqlite.lua") +include("libraries/disjointset.lua") + +resource.AddFile("materials/vgui/entities/arrest_stick.vmt") +resource.AddFile("materials/vgui/entities/door_ram.vmt") +resource.AddFile("materials/vgui/entities/keys.vmt") +resource.AddFile("materials/vgui/entities/lockpick.vmt") +resource.AddFile("materials/vgui/entities/ls_sniper.vmt") +resource.AddFile("materials/vgui/entities/med_kit.vmt") +resource.AddFile("materials/vgui/entities/pocket.vmt") +resource.AddFile("materials/vgui/entities/stunstick.vmt") +resource.AddFile("materials/vgui/entities/unarrest_stick.vmt") +resource.AddFile("materials/vgui/entities/weapon_ak472.vmt") +resource.AddFile("materials/vgui/entities/weapon_deagle2.vmt") +resource.AddFile("materials/vgui/entities/weapon_fiveseven2.vmt") +resource.AddFile("materials/vgui/entities/weapon_glock2.vmt") +resource.AddFile("materials/vgui/entities/weapon_keypadchecker.vmt") +resource.AddFile("materials/vgui/entities/weapon_m42.vmt") +resource.AddFile("materials/vgui/entities/weapon_mac102.vmt") +resource.AddFile("materials/vgui/entities/weapon_mp52.vmt") +resource.AddFile("materials/vgui/entities/weapon_p2282.vmt") +resource.AddFile("materials/vgui/entities/weapon_pumpshotgun2.vmt") +resource.AddFile("materials/vgui/entities/weaponchecker.vmt") + + +hook.Call("DarkRPPreLoadModules", GM) + + +--[[--------------------------------------------------------------------------- +Loading modules +---------------------------------------------------------------------------]] +local fol = GM.FolderName .. "/gamemode/modules/" +local files, folders = file.Find(fol .. "*", "LUA") +local SortedPairs = SortedPairs + +for _, v in ipairs(files) do + if DarkRP.disabledDefaults["modules"][v:Left(-5)] then continue end + if string.GetExtensionFromFilename(v) ~= "lua" then continue end + include(fol .. v) +end + +for _, folder in SortedPairs(folders, true) do + if folder == "." or folder == ".." or DarkRP.disabledDefaults["modules"][folder] then continue end + + for _, File in SortedPairs(file.Find(fol .. folder .. "/sh_*.lua", "LUA"), true) do + if File == "sh_interface.lua" then continue end + AddCSLuaFile(fol .. folder .. "/" .. File) + include(fol .. folder .. "/" .. File) + end + + for _, File in SortedPairs(file.Find(fol .. folder .. "/sv_*.lua", "LUA"), true) do + if File == "sv_interface.lua" then continue end + include(fol .. folder .. "/" .. File) + end + + for _, File in SortedPairs(file.Find(fol .. folder .. "/cl_*.lua", "LUA"), true) do + if File == "cl_interface.lua" then continue end + AddCSLuaFile(fol .. folder .. "/" .. File) + end +end + + +DarkRP.DARKRP_LOADING = true +include("config/jobrelated.lua") +include("config/addentities.lua") +include("config/ammotypes.lua") +DarkRP.DARKRP_LOADING = nil + +DarkRP.finish() + +hook.Call("DarkRPFinishedLoading", GM) +MySQLite.initialize() diff --git a/gamemodes/darkrp/gamemode/libraries/disjointset.lua b/gamemodes/darkrp/gamemode/libraries/disjointset.lua new file mode 100644 index 0000000..9c9db8b --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/disjointset.lua @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------- +Disjoint-set forest implementation +Inspired by the book Introduction To Algorithms (third edition) + +by FPtje Atheos + +Running time per operation (Union/FindSet): O(a(n)) where a is the inverse of the Ackermann function. +---------------------------------------------------------------------------*/ + +local ipairs = ipairs +local setmetatable = setmetatable +local string = string +local table = table +local tostring = tostring + +module("disjoint") + +local metatable + +-- Make a singleton set. Parent parameter is optional, must be a disjoint-set as well. +function MakeSet(x, parent) + local set = {} + set.value = x + set.rank = 0 + set.parent = parent or set + + setmetatable(set, metatable) + + return set +end + +local function Link(x, y) + if x == y then return x end + + -- Union by rank + if x.rank > y.rank then + y.parent = x + return x + end + + x.parent = y + + if x.rank == y.rank then + y.rank = y.rank + 1 + end + + return y +end + +-- Apply the union operation between two sets. +function Union(x, y) + return Link(FindSet(x), FindSet(y)) +end + +function FindSet(x) + local parent = x + local listParents + + -- Go up the tree to find the parent + while parent ~= parent.parent do + parent = parent.parent + + listParents = listParents or {} + table.insert(listParents, parent) + end + + -- Path compression, update all parents to refer to the top parent + if listParents then + for _, v in ipairs(listParents) do + v.parent = parent + end + end + + return parent +end + +function Disconnect(x) + x.parent = x + + return x +end + + +metatable = { + __tostring = function(self) + return string.format("Disjoint-Set [value: %s][Rank: %s][Parent: %s]", tostring(self.value), self.rank, tostring(self.parent.value)) + end, + __metatable = true, -- restrict access to metatable + __add = Union +} diff --git a/gamemodes/darkrp/gamemode/libraries/fn.lua b/gamemodes/darkrp/gamemode/libraries/fn.lua new file mode 100644 index 0000000..e35f77e --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/fn.lua @@ -0,0 +1,337 @@ +/*--------------------------------------------------------------------------- +Functional library + +by FPtje Atheos +---------------------------------------------------------------------------*/ + +/*--------------------------------------------------------------------------- +Function currying + Take a function with n parameters. + Currying is the procedure of storing k < n parameters "in the function" + in such a way that the remaining function can be called with n - k parameters + + Example: + DebugPrint = fp{print, "[DEBUG]"} + DebugPrint("TEST") + > [DEBUG] TEST +---------------------------------------------------------------------------*/ +function fp(tbl) + local func = tbl[1] + + return function(...) + local fnArgs = {} + local arg = {...} + local tblN = table.maxn(tbl) + + for i = 2, tblN do fnArgs[i - 1] = tbl[i] end + for i = 1, table.maxn(arg) do fnArgs[tblN + i - 1] = arg[i] end + + return func(unpack(fnArgs, 1, table.maxn(fnArgs))) + end +end + +local unpack = unpack +local table = table +local pairs = pairs +local ipairs = ipairs +local error = error +local math = math +local select = select +local type = type +local _G = _G +local fp = fp + + +module("fn") + +/*--------------------------------------------------------------------------- +Parameter manipulation +---------------------------------------------------------------------------*/ +Id = function(...) return ... end + +Flip = function(f) + if not f then error("not a function") end + return function(b, a, ...) + return f(a, b, ...) + end +end + +-- Definition from http://lua-users.org/wiki/CurriedLua +ReverseArgs = function(...) + + --reverse args by building a function to do it, similar to the unpack() example + local function reverse_h(acc, v, ...) + if select('#', ...) == 0 then + return v, acc() + else + return reverse_h(function () return v, acc() end, ...) + end + end + + -- initial acc is the end of the list + return reverse_h(function () return end, ...) +end + +/*--------------------------------------------------------------------------- +Misc functions +---------------------------------------------------------------------------*/ +-- function composition +do + local function comp_h(a, b, ...) + if b == nil then return a end + b = comp_h(b, ...) + return function(...) + return a(b(...)) + end + end + Compose = function(funcs, ...) + if type(funcs) == "table" then + return comp_h(unpack(funcs)) + else + return comp_h(funcs, ...) + end + end +end + +_G.fc = Compose + +-- Definition from http://lua-users.org/wiki/CurriedLua +Curry = function(func, num_args) + if not num_args then error("Missing argument #2: num_args") end + if not func then error("Function does not exist!", 2) end + -- helper + local function curry_h(argtrace, n) + if n == 0 then + -- reverse argument list and call function + return func(ReverseArgs(argtrace())) + else + -- "push" argument (by building a wrapper function) and decrement n + return function(x) + return curry_h(function() return x, argtrace() end, n - 1) + end + end + end + + -- no sense currying for 1 arg or less + if num_args > 1 then + return curry_h(function() return end, num_args) + else + return func + end +end + +-- Thanks Lexic! +Partial = function(func, ...) + local args = {...} + return function(...) + return func(unpack(table.Add( args, {...}))) + end +end + +Apply = function(f, ...) return f(...) end + +Const = function(a, b) return a end +Until = function(cmp, fn, val) + if cmp(val) then + return val + end + return Until(cmp, fn, fn(val)) +end + +Seq = function(f, x) f(x) return x end + +GetGlobalVar = function(key) return _G[key] end + +/*--------------------------------------------------------------------------- +Mathematical operators and functions +---------------------------------------------------------------------------*/ +Add = function(a, b) return a + b end +Sub = function(a, b) return a - b end +Mul = function(a, b) return a * b end +Div = function(a, b) return a / b end +Mod = function(a, b) return a % b end +Neg = function(a) return -a end + +Eq = function(a, b) return a == b end +Neq = function(a, b) return a ~= b end +Gt = function(a, b) return a > b end +Lt = function(a, b) return a < b end +Gte = function(a, b) return a >= b end +Lte = function(a, b) return a <= b end + +Succ = Compose{Add, 1} +Pred = Compose{Flip(Sub), 1} +Even = Compose{fp{Eq, 0}, fp{Flip(Mod), 2}} +Odd = Compose{Not, Even} + +/*--------------------------------------------------------------------------- +Functional logical operators and conditions +---------------------------------------------------------------------------*/ +FAnd = function(fns) + return function(...) + local val + for _, f in pairs(fns) do + val = {f(...)} + if not val[1] then return unpack(val) end + end + if val then return unpack(val) end + end +end + +FOr = function(fns) + return function(...) + local val + for _, f in pairs(fns) do + val = {f(...)} + if val[1] then return unpack(val) end + end + return false, unpack(val, 2) + end +end + +Not = function(x) return not x end + +If = function(f, Then, Else) + return function(x) + if f(x) then + return Then + else + return Else + end + end +end + +/*--------------------------------------------------------------------------- +List operations +---------------------------------------------------------------------------*/ +Map = function(f, xs) + for k, v in pairs(xs) do + xs[k] = f(v) + end + return xs +end + +Append = function(xs, ys) + return table.Add(xs, ys) +end + +Filter = function(f, xs) + local res = {} + for k,v in pairs(xs) do + if f(v) then res[k] = v end + end + return res +end + +ForEach = function(f, xs) + for k,v in pairs(xs) do + local val = f(k, v) + if val ~= nil then return val end + end +end + +Head = function(xs) + return table.GetFirstValue(xs) +end + +Last = function(xs) + return xs[#xs] or table.GetLastValue(xs) +end + +Tail = function(xs) + table.remove(xs, 1) + return xs +end + +Init = function(xs) + xs[#xs] = nil + return xs +end + +GetValue = function(i, xs) + return xs[i] +end + +Null = function(xs) + for k, v in pairs(xs) do + return false + end + return true +end + +Length = function(xs) + return #xs +end + +Index = function(xs, i) + return xs[i] +end + +Reverse = function(xs) + local res = {} + for i = #xs, 1, -1 do + res[#xs - i + 1] = xs[i] + end + return res +end + +/*--------------------------------------------------------------------------- +Folds +---------------------------------------------------------------------------*/ +Foldr = function(func, val, xs) + for i = #xs, 1, -1 do + val = func(xs[i], val) + end + + return val +end + +Foldl = function(func, val, xs) + for k, v in ipairs(xs) do + val = func(val, v) + end + + return val +end + +And = function(xs) + for k, v in pairs(xs) do + if v ~= true then return false end + end + return true +end + +Or = function(xs) + for k, v in pairs(xs) do + if v == true then return true end + end + return false +end + +Any = function(func, xs) + for k, v in pairs(xs) do + if func(v) == true then return true end + end + return false +end + +All = function(func, xs) + for k, v in pairs(xs) do + if func(v) ~= true then return false end + end + return true +end + +Sum = _G.fp{Foldr, Add, 0} + +Product = _G.fp{Foldr, Mul, 1} + +Concat = _G.fp{Foldr, Append, {}} + +Maximum = _G.fp{Foldl, math.Max, -math.huge} + +Minimum = _G.fp{Foldl, math.Min, math.huge} + +Snd = _G.fp{select, 2} + +Thrd = _G.fp{select, 3} diff --git a/gamemodes/darkrp/gamemode/libraries/interfaceloader.lua b/gamemodes/darkrp/gamemode/libraries/interfaceloader.lua new file mode 100644 index 0000000..7dd1abc --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/interfaceloader.lua @@ -0,0 +1,210 @@ +module("DarkRP", package.seeall) + +MetaName = "DarkRP" + +-- Variables that maintain the existing stubs and hooks +local stubs = {} +local hookStubs = {} + +-- Contains the functions that the hooks call by default +hooks = {} + +-- Delay the calling of methods until the functions are implemented +local delayedCalls = {} + +local returnsLayout, isreturns +local parameterLayout, isparameters +local isdeprecated +local checkStub + +local hookLayout + +local realm -- State variable to manage the realm of the stubs + +--[[--------------------------------------------------------------------------- +Methods that check whether certain fields are valid +---------------------------------------------------------------------------]] +isreturns = function(tbl) + if not istable(tbl) then return false end + for _, v in pairs(tbl) do + if not checkStub(v, returnsLayout) then return false end + end + return true +end + +isparameters = function(tbl) + if not istable(tbl) then return false end + for _, v in pairs(tbl) do + if not checkStub(v, parameterLayout) then return false end + end + return true +end + +isdeprecated = function(val) + return val == nil or isstring(val) +end + +--[[--------------------------------------------------------------------------- +The layouts of stubs +---------------------------------------------------------------------------]] +local stubLayout = { + name = isstring, + description = isstring, + deprecated = isdeprecated, + parameters = isparameters, -- the parameters of a method + returns = isreturns, -- the return values of a method + metatable = istable -- DarkRP, Player, Entity, Vector, ... +} + +hookLayout = { + name = isstring, + description = isstring, + deprecated = isdeprecated, + parameters = isreturns, -- doesn't have the 'optional' field + returns = isreturns, +} + +returnsLayout = { + name = isstring, + description = isstring, + type = isstring +} + +parameterLayout = { + name = isstring, + description = isstring, + type = isstring, + optional = isbool +} + +--[[--------------------------------------------------------------------------- +Check the validity of a stub +---------------------------------------------------------------------------]] +checkStub = function(tbl, stub) + if not istable(tbl) then return false, "table" end + + for name, check in pairs(stub) do + if not check(tbl[name]) then + return false, name + end + end + + return true +end + +--[[--------------------------------------------------------------------------- +When a stub is called, the calling of the method is delayed +---------------------------------------------------------------------------]] +local function notImplemented(name, args, thisFunc) + if stubs[name] and stubs[name].metatable[name] ~= thisFunc then -- when calling the not implemented function after the function was implemented + return stubs[name].metatable[name](unpack(args)) + end + table.insert(delayedCalls, {name = name, args = args}) + + return nil -- no return value because the method is not implemented +end + +--[[--------------------------------------------------------------------------- +Generate a stub +---------------------------------------------------------------------------]] +function stub(tbl) + local isStub, field = checkStub(tbl, stubLayout) + if not isStub then + error("Invalid DarkRP method stub! Field \"" .. field .. "\" is invalid!", 2) + end + + tbl.realm = tbl.realm or realm + stubs[tbl.name] = tbl + + local function retNotImpl(...) + return notImplemented(tbl.name, {...}, retNotImpl) + end + + return retNotImpl +end + +--[[--------------------------------------------------------------------------- +Generate a hook stub +---------------------------------------------------------------------------]] +function hookStub(tbl) + local isStub, field = checkStub(tbl, hookLayout) + if not isStub then + error("Invalid DarkRP hook! Field \"" .. field .. "\" is invalid!", 2) + end + + tbl.realm = tbl.realm or realm + hookStubs[tbl.name] = tbl +end + +--[[--------------------------------------------------------------------------- +Retrieve the stubs +---------------------------------------------------------------------------]] +function getStubs() + return table.Copy(stubs) +end + +--[[--------------------------------------------------------------------------- +Retrieve the hooks +---------------------------------------------------------------------------]] +function getHooks() + return table.Copy(hookStubs) +end + +--[[--------------------------------------------------------------------------- +Call the cached methods +---------------------------------------------------------------------------]] +function finish() + local calls = table.Copy(delayedCalls) -- Loop through a copy, so the notImplemented function doesn't get called again + for _, tbl in ipairs(calls) do + local name = tbl.name + + if not stubs[name] then ErrorNoHalt("Calling non-existing stub \"" .. name .. "\"") continue end + + stubs[name].metatable[name](unpack(tbl.args)) + end + + delayedCalls = {} +end + +--[[--------------------------------------------------------------------------- +Load the interface files +---------------------------------------------------------------------------]] +local function loadInterfaces() + local root = GM.FolderName .. "/gamemode/modules" + + local _, folders = file.Find(root .. "/*", "LUA") + + ENTITY = FindMetaTable("Entity") + PLAYER = FindMetaTable("Player") + VECTOR = FindMetaTable("Vector") + + for _, folder in SortedPairs(folders, true) do + local interfacefile = string.format("%s/%s/%s_interface.lua", root, folder, "%s") + local client = string.format(interfacefile, "cl") + local shared = string.format(interfacefile, "sh") + local server = string.format(interfacefile, "sv") + + if file.Exists(shared, "LUA") then + if SERVER then AddCSLuaFile(shared) end + realm = "Shared" + include(shared) + end + + if SERVER and file.Exists(client, "LUA") then + AddCSLuaFile(client) + end + + if SERVER and file.Exists(server, "LUA") then + realm = "Server" + include(server) + end + + if CLIENT and file.Exists(client, "LUA") then + realm = "Client" + include(client) + end + end + + ENTITY, PLAYER, VECTOR = nil, nil, nil +end +loadInterfaces() diff --git a/gamemodes/darkrp/gamemode/libraries/modificationloader.lua b/gamemodes/darkrp/gamemode/libraries/modificationloader.lua new file mode 100644 index 0000000..c508f43 --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/modificationloader.lua @@ -0,0 +1,152 @@ +-- Modification loader. +-- Dependencies: +-- - fn +-- - simplerr + +--[[--------------------------------------------------------------------------- +Disabled defaults +---------------------------------------------------------------------------]] +DarkRP.disabledDefaults = {} +DarkRP.disabledDefaults["modules"] = { + ["afk"] = true, + ["chatsounds"] = false, + ["events"] = false, + ["fpp"] = false, + ["hitmenu"] = false, + ["hud"] = false, + ["hungermod"] = true, + ["playerscale"] = false, + ["sleep"] = false, +} + +DarkRP.disabledDefaults["agendas"] = {} +DarkRP.disabledDefaults["ammo"] = {} +DarkRP.disabledDefaults["demotegroups"] = {} +DarkRP.disabledDefaults["doorgroups"] = {} +DarkRP.disabledDefaults["entities"] = {} +DarkRP.disabledDefaults["food"] = {} +DarkRP.disabledDefaults["groupchat"] = {} +DarkRP.disabledDefaults["hitmen"] = {} +DarkRP.disabledDefaults["jobs"] = {} +DarkRP.disabledDefaults["shipments"] = {} +DarkRP.disabledDefaults["vehicles"] = {} +DarkRP.disabledDefaults["workarounds"] = {} + +-- The client cannot use simplerr.runLuaFile because of restrictions in GMod. +local doInclude = CLIENT and include or fc{simplerr.wrapError, simplerr.wrapLog, simplerr.runFile} + +if file.Exists("darkrp_config/disabled_defaults.lua", "LUA") then + if SERVER then AddCSLuaFile("darkrp_config/disabled_defaults.lua") end + doInclude("darkrp_config/disabled_defaults.lua") +end + +--[[--------------------------------------------------------------------------- +Config +---------------------------------------------------------------------------]] +local configFiles = { + "darkrp_config/settings.lua", + "darkrp_config/licenseweapons.lua", +} + +for _, File in ipairs(configFiles) do + if not file.Exists(File, "LUA") then continue end + + if SERVER then AddCSLuaFile(File) end + doInclude(File) +end +if SERVER and file.Exists("darkrp_config/mysql.lua", "LUA") then doInclude("darkrp_config/mysql.lua") end + +--[[--------------------------------------------------------------------------- +Modules +---------------------------------------------------------------------------]] +local function loadModules() + local fol = "darkrp_modules/" + + local _, folders = file.Find(fol .. "*", "LUA") + + for _, folder in SortedPairs(folders, true) do + if folder == "." or folder == ".." or GAMEMODE.Config.DisabledCustomModules[folder] then continue end + -- Sound but incomplete way of detecting the error of putting addons in the darkrpmod folder + if file.Exists(fol .. folder .. "/addon.txt", "LUA") or file.Exists(fol .. folder .. "/addon.json", "LUA") then + DarkRP.errorNoHalt("Addon detected in the darkrp_modules folder.", 2, { + "This addon is not supposed to be in the darkrp_modules folder.", + "It is supposed to be in garrysmod/addons/ instead.", + "Whether a mod is to be installed in darkrp_modules or addons is the author's decision.", + "Please read the readme of the addons you're installing next time." + }, + "/lua/darkrp_modules/" .. folder, -1) + continue + end + + for _, File in SortedPairs(file.Find(fol .. folder .. "/sh_*.lua", "LUA"), true) do + if SERVER then + AddCSLuaFile(fol .. folder .. "/" .. File) + end + + if File == "sh_interface.lua" then continue end + doInclude(fol .. folder .. "/" .. File) + end + + if SERVER then + for _, File in SortedPairs(file.Find(fol .. folder .. "/sv_*.lua", "LUA"), true) do + if File == "sv_interface.lua" then continue end + doInclude(fol .. folder .. "/" .. File) + end + end + + for _, File in SortedPairs(file.Find(fol .. folder .. "/cl_*.lua", "LUA"), true) do + if File == "cl_interface.lua" then continue end + + if SERVER then + AddCSLuaFile(fol .. folder .. "/" .. File) + else + doInclude(fol .. folder .. "/" .. File) + end + end + end +end + +local function loadLanguages() + local fol = "darkrp_language/" + + local files, _ = file.Find(fol .. "*", "LUA") + for _, File in ipairs(files) do + if SERVER then AddCSLuaFile(fol .. File) end + doInclude(fol .. File) + end +end + +local customFiles = { + "darkrp_customthings/jobs.lua", + "darkrp_customthings/shipments.lua", + "darkrp_customthings/entities.lua", + "darkrp_customthings/vehicles.lua", + "darkrp_customthings/food.lua", + "darkrp_customthings/ammo.lua", + "darkrp_customthings/groupchats.lua", + "darkrp_customthings/categories.lua", + "darkrp_customthings/agendas.lua", -- has to be run after jobs.lua + "darkrp_customthings/doorgroups.lua", -- has to be run after jobs.lua + "darkrp_customthings/demotegroups.lua", -- has to be run after jobs.lua +} +local function loadCustomDarkRPItems() + for _, File in ipairs(customFiles) do + if not file.Exists(File, "LUA") then continue end + if File == "darkrp_customthings/food.lua" and DarkRP.disabledDefaults["modules"]["hungermod"] then continue end + + if SERVER then AddCSLuaFile(File) end + doInclude(File) + end +end + + +function GM:DarkRPFinishedLoading() + -- GAMEMODE gets set after the last statement in the gamemode files is run. That is not the case in this hook + GAMEMODE = GAMEMODE or GM + + loadLanguages() + loadModules() + loadCustomDarkRPItems() + hook.Call("loadCustomDarkRPItems", self) + hook.Call("postLoadCustomDarkRPItems", self) +end diff --git a/gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua b/gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua new file mode 100644 index 0000000..91b485f --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua @@ -0,0 +1,519 @@ +--[[ + MySQLite - Abstraction mechanism for SQLite and MySQL + + Why use this? + - Easy to use interface for MySQL + - No need to modify code when switching between SQLite and MySQL + - Queued queries: execute a bunch of queries in order an run the callback when all queries are done + + License: LGPL V2.1 (read here: https://www.gnu.org/licenses/lgpl-2.1.html) + + Supported MySQL modules: + - MySQLOO + - tmysql4 + + Note: When both MySQLOO and tmysql4 modules are installed, MySQLOO is used by default. + + /*--------------------------------------------------------------------------- + Documentation + ---------------------------------------------------------------------------*/ + + MySQLite.initialize([config :: table]) :: No value + Initialize MySQLite. Loads the config from either the config parameter OR the MySQLite_config global. + This loads the module (if necessary) and connects to the MySQL database (if set up). + The config must have this layout: + { + EnableMySQL :: Bool - set to true to use MySQL, false for SQLite + Host :: String - database hostname + Username :: String - database username + Password :: String - database password (keep away from clients!) + Database_name :: String - name of the database + Database_port :: Number - connection port (3306 by default) + Preferred_module :: String - Preferred module, case sensitive, must be either "mysqloo" or "tmysql4" + MultiStatements :: Bool - Only available in tmysql4: allow multiple SQL statements per query + } + + ----------------------------- Utility functions ----------------------------- + MySQLite.isMySQL() :: Bool + Returns whether MySQLite is set up to use MySQL. True for MySQL, false for SQLite. + Use this when the query syntax between SQLite and MySQL differs (example: AUTOINCREMENT vs AUTO_INCREMENT) + + MySQLite.SQLStr(str :: String) :: String + Escapes the string and puts it in quotes. + It uses the escaping method of the module that is currently being used. + + MySQLite.tableExists(tbl :: String, callback :: function, errorCallback :: function) + Checks whether table tbl exists. + + callback format: function(res :: Bool) + res is a boolean indicating whether the table exists. + + The errorCallback format is the same as in MySQLite.query. + + ----------------------------- Running queries ----------------------------- + MySQLite.query(sqlText :: String, callback :: function, errorCallback :: function) :: No value + Runs a query. Calls the callback parameter when finished, calls errorCallback when an error occurs. + + callback format: + function(result :: table, lastInsert :: number) + Result is the table with results (nil when there are no results or when the result list is empty) + lastInsert is the row number of the last inserted value (use with AUTOINCREMENT) + + Note: lastInsert is NOT supported when using SQLite. + + errorCallback format: + function(error :: String, query :: String) :: Bool + error is the error given by the database module. + query is the query that triggered the error. + + Return true to suppress the error! + + MySQLite.queryValue(sqlText :: String, callback :: function, errorCallback :: function) :: No value + Runs a query and returns the first value it comes across. + + callback format: + function(result :: any) + where the result is either a string or a number, depending on the requested database field. + + The errorCallback format is the same as in MySQLite.query. + + MySQLite.prepare(sqlText :: String, sqlParams :: Table, callback :: function, errorCallback :: function) + Calls a prepared statement, faster for queries that are ran multiple times. Params do not need to be escaped. + This does not work with SQLite, and will instead call a regular query + + callback format: + function(result :: table, lastInsert :: number, rowsChanged :: number) + Result is the table with results (nil when there are no results or when the result list is empty) + lastInsert is the row number of the last inserted value (use with AUTOINCREMENT) + rowsChanged is the amount of rows that were changed with the query + + Note: lastInsert is NOT supported when using SQLite. + + The errorCallback format is the same as in MySQLite.query. + + Example: + MySQLite.prepare("UPDATE `darkrp_player` SET `rpname` = ? WHERE `rpname` = ?", { "players_old_name", "players_new_name"}, success_callback, error_callback) + + ----------------------------- Transactions ----------------------------- + MySQLite.begin() :: No value + Starts a transaction. Use in combination with MySQLite.queueQuery and MySQLite.commit. + + MySQLite.queueQuery(sqlText :: String, callback :: function, errorCallback :: function) :: No value + Queues a query in the transaction. Note: a transaction must be started with MySQLite.begin() for this to work. + The callback will be called when this specific query has been executed successfully. + The errorCallback function will be called when an error occurs in this specific query. + + See MySQLite.query for the callback and errorCallback format. + + MySQLite.commit(onFinished) + Commits a transaction and calls onFinished when EVERY queued query has finished. + onFinished is NOT called when an error occurs in one of the queued queries. + + onFinished is called without arguments. + + ----------------------------- Hooks ----------------------------- + DatabaseInitialized + Called when a successful connection to the database has been made. +]] + +local debug = debug +local error = error +local ErrorNoHalt = ErrorNoHalt +local hook = hook +local pairs = pairs +local require = require +local sql = sql +local string = string +local table = table +local timer = timer +local tostring = tostring +local mysqlOO +local TMySQL +local _G = _G +local ipairs = ipairs +local type = type +local unpack = unpack + +local multistatements + +local MySQLite_config = MySQLite_config or RP_MySQLConfig or FPP_MySQLConfig +local moduleLoaded + +local function loadMySQLModule() + if moduleLoaded or not MySQLite_config or not MySQLite_config.EnableMySQL then return end + + local moo, tmsql = util.IsBinaryModuleInstalled("mysqloo"), util.IsBinaryModuleInstalled("tmysql4") + + if not moo and not tmsql then + error("Could not find a suitable MySQL module. Please either:\n - Install tmysql. It can be obtained from https://github.com/SuperiorServers/gm_tmysql4\n - Install MySQLOO. It can be obtained from https://github.com/FredyH/MySQLOO\nDue to this error, MySQL is disabled. This means that SQLite is used instead to store data.") + end + moduleLoaded = true + + require( + moo and tmsql and MySQLite_config.Preferred_module or + moo and "mysqloo" or + tmsql and "tmysql4" + ) + + multistatements = CLIENT_MULTI_STATEMENTS + + mysqlOO = mysqloo + TMySQL = tmysql + + if MySQLite_config.Preferred_module == "tmysql4" then + if not tmsql then + ErrorNoHalt("The preferred module for MySQL is selected to be tmysql4. However, tmysql4 does not appear to be installed. Please either:\n - Install tmysql. It can be obtained from https://github.com/SuperiorServers/gm_tmysql4\n - Select MySQLOO as the preferred module for MySQL. MySQLOO appears to be installed.") + return + end + + if not tmysql.Version or tmysql.Version < 4.1 then + MsgC(Color(255, 0, 0), "Using older tmysql version, please consider updating!\n") + MsgC(Color(255, 0, 0), "Newer Version: https://github.com/SuperiorServers/gm_tmysql4\n") + end + + -- Turns tmysql.Connect into tmysql.Initialize if they're using an older version. + TMySQL.Connect = tmysql.Version and tmysql.Version >= 4.1 and TMySQL.Connect or TMySQL.initialize + TMySQL.SetOption = tmysql.Version and tmysql.Version >= 4.1 and TMySQL.SetOption or TMySQL.Option + else + if not moo then + ErrorNoHalt("The preferred module for MySQL is selected to be MySQLOO. However, MySQLOO does not appear to be installed. Please either:\n - Install MySQLOO. It can be obtained from https://github.com/FredyH/MySQLOO\n - Select tmysql4 as the preferred module for MySQL. tmysql4 appears to be installed.") + end + end +end +loadMySQLModule() + +module("MySQLite") + +-- Helper function to return the first value found when iterating over a table. +-- Replaces the now deprecated table.GetFirstValue +local function arbitraryTableValue(tbl) + for _, v in pairs(tbl) do return v end +end + +function initialize(config) + MySQLite_config = config or MySQLite_config + + if not MySQLite_config then + ErrorNoHalt("Warning: No MySQL config!") + end + + loadMySQLModule() + + if MySQLite_config.EnableMySQL then + connectToMySQL(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port) + else + timer.Simple(0, function() + _G.GAMEMODE.DatabaseInitialized = _G.GAMEMODE.DatabaseInitialized or function() end + hook.Call("DatabaseInitialized", _G.GAMEMODE) + end) + end +end + +local CONNECTED_TO_MYSQL = false +local msOOConnect +databaseObject = nil + +local queuedQueries +local cachedQueries + +function isMySQL() + return CONNECTED_TO_MYSQL +end + +function begin() + if not CONNECTED_TO_MYSQL then + sql.Begin() + else + if queuedQueries then + debug.Trace() + error("Transaction ongoing!") + end + queuedQueries = {} + end +end + +function commit(onFinished) + if not CONNECTED_TO_MYSQL then + sql.Commit() + if onFinished then onFinished() end + return + end + + if not queuedQueries then + error("No queued queries! Call begin() first!") + end + + if #queuedQueries == 0 then + queuedQueries = nil + if onFinished then onFinished() end + return + end + + -- Copy the table so other scripts can create their own queue + local queue = table.Copy(queuedQueries) + queuedQueries = nil + + -- Handle queued queries in order + local queuePos = 0 + local call + + -- Recursion invariant: queuePos > 0 and queue[queuePos] <= #queue + call = function(...) + queuePos = queuePos + 1 + + if queue[queuePos].callback then + queue[queuePos].callback(...) + end + + -- Base case, end of the queue + if queuePos + 1 > #queue then + if onFinished then onFinished() end -- All queries have finished + return + end + + -- Recursion + local nextQuery = queue[queuePos + 1] + query(nextQuery.query, call, nextQuery.onError) + end + + query(queue[1].query, call, queue[1].onError) +end + +function queueQuery(sqlText, callback, errorCallback) + if CONNECTED_TO_MYSQL then + table.insert(queuedQueries, {query = sqlText, callback = callback, onError = errorCallback}) + return + end + -- SQLite is instantaneous, simply running the query is equal to queueing it + query(sqlText, callback, errorCallback) +end + +local function msOOQuery(sqlText, callback, errorCallback, queryValue) + local queryObject = databaseObject:query(sqlText) + local data + queryObject.onData = function(Q, D) + data = data or {} + data[#data + 1] = D + end + + queryObject.onError = function(Q, E) + if databaseObject:status() == mysqlOO.DATABASE_NOT_CONNECTED then + table.insert(cachedQueries, {sqlText, callback, queryValue}) + + -- Immediately try reconnecting + msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port) + return + end + + local supp = errorCallback and errorCallback(E, sqlText) + if not supp then error(E .. " (" .. sqlText .. ")") end + end + + queryObject.onSuccess = function() + local res = queryValue and data and data[1] and arbitraryTableValue(data[1]) or not queryValue and data or nil + if callback then callback(res, queryObject:lastInsert()) end + end + queryObject:start() +end + +local preparedStatements = {} +local paramTypes = {} +paramTypes["number"] = function(queryObj, paramIndex, paramValue) return queryObj:setNumber(paramIndex, paramValue) end +paramTypes["string"] = function(queryObj, paramIndex, paramValue) return queryObj:setString(paramIndex, paramValue) end +paramTypes["boolean"] = function(queryObj, paramIndex, paramValue) return queryObj:setBoolean(paramIndex, paramValue) end + +local function msOOPrepare(sqlText, sqlParams, callback, errorCallback) + local queryObject + + if preparedStatements[sqlText] then + queryObject = preparedStatements[sqlText] + else + queryObject = databaseObject:prepare(sqlText) + preparedStatements[sqlText] = queryObject + end + + for i, param in ipairs(sqlParams) do + local paramType = type(param) + + if paramTypes[paramType] then + paramTypes[paramType](queryObject, i, param) + else + queryObject:setString(i, param) + end + end + + queryObject.onError = function(_, E) + local supp = errorCallback and errorCallback(E, sqlText) + if not supp then error(E .. " (" .. sqlText .. ")") end + end + + queryObject.onSuccess = function(_, data) + if callback then callback(data, queryObject:lastInsert(), queryObject:affectedRows()) end + end + + queryObject:start() +end + +local function tmsqlPrepare(sqlText, sqlParams, callback, errorCallback) + local queryObject + + if preparedStatements[sqlText] then + queryObject = preparedStatements[sqlText] + else + queryObject = databaseObject:Prepare(sqlText) + preparedStatements[sqlText] = queryObject + end + + for i, param in ipairs(sqlParams) do + param = SQLStr(param, true) + end + + local varcount = queryObject:GetArgCount() + + sqlParams[varcount + 1] = function(results) + if results[1].error ~= nil then + local supp = errorCallback and errorCallback(E, results[1].error) + if not supp then error(E .. " (" .. results[1].error .. ")") end + end + + if callback then callback(data, results[1].lastid, results[1].affected) end + end + + queryObject:Run(unpack(sqlParams, 1, varcount + 2)) +end + +local function tmsqlQuery(sqlText, callback, errorCallback, queryValue) + local call = function(res) + res = res[1] -- For now only support one result set + if not res.status then + local supp = errorCallback and errorCallback(res.error, sqlText) + if not supp then error(res.error .. " (" .. sqlText .. ")") end + return + end + + if not res.data or #res.data == 0 then res.data = nil end -- compatibility with other backends + if queryValue and callback then return callback(res.data and res.data[1] and arbitraryTableValue(res.data[1]) or nil) end + if callback then callback(res.data, res.lastid) end + end + + databaseObject:Query(sqlText, call) +end + +local function SQLiteQuery(sqlText, callback, errorCallback, queryValue) + sql.m_strError = "" -- reset last error + + local lastError = sql.LastError() + local Result = queryValue and sql.QueryValue(sqlText) or sql.Query(sqlText) + + if sql.LastError() and sql.LastError() ~= lastError then + local err = sql.LastError() + local supp = errorCallback and errorCallback(err, sqlText) + if supp == false then error(err .. " (" .. sqlText .. ")", 2) end + return + end + + if callback then callback(Result) end + return Result +end + +-- SQLite doesn't support preparedStatements, so convert it to a regular query. +local function SQLitePrepare(sqlText, sqlParams, callback, errorCallback) + for _, param in ipairs(sqlParams) do + sqlText = sqlText:gsub("%?", sql.SQLStr(param), 1) + end + + return SQLiteQuery(sqlText, callback, errorCallback, false) +end + +function query(sqlText, callback, errorCallback) + local qFunc = (CONNECTED_TO_MYSQL and ((mysqlOO and msOOQuery) or (TMySQL and tmsqlQuery))) or SQLiteQuery + return qFunc(sqlText, callback, errorCallback, false) +end + +function queryValue(sqlText, callback, errorCallback) + local qFunc = (CONNECTED_TO_MYSQL and ((mysqlOO and msOOQuery) or (TMySQL and tmsqlQuery))) or SQLiteQuery + return qFunc(sqlText, callback, errorCallback, true) +end + +function prepare(sqlText, sqlParams, callback, errorCallback) + local qFunc = (CONNECTED_TO_MYSQL and ((mysqlOO and msOOPrepare) or (TMySQL and tmsqlPrepare))) or SQLitePrepare + return qFunc(sqlText, sqlParams, callback, errorCallback, false) +end + +local function onConnected() + CONNECTED_TO_MYSQL = true + + -- Run the queries that were called before the connection was made + for k, v in pairs(cachedQueries or {}) do + cachedQueries[k] = nil + if v[3] then + queryValue(v[1], v[2]) + else + query(v[1], v[2]) + end + end + cachedQueries = {} + local GM = _G.GAMEMODE or _G.GM + + hook.Call("DatabaseInitialized", GM.DatabaseInitialized and GM or nil) + +end + +msOOConnect = function(host, username, password, database_name, database_port) + databaseObject = mysqlOO.connect(host, username, password, database_name, database_port) + + if timer.Exists("darkrp_check_mysql_status") then timer.Remove("darkrp_check_mysql_status") end + + databaseObject.onConnectionFailed = function(_, msg) + timer.Simple(5, function() + msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port) + end) + error("Connection failed! " .. tostring(msg) .. "\nTrying again in 5 seconds.") + end + + databaseObject.onConnected = onConnected + + databaseObject:connect() +end + +local function tmsqlConnect(host, username, password, database_name, database_port) + local db, err = TMySQL.Connect(host, username, password, database_name, database_port, nil, MySQLite_config.MultiStatements and multistatements or nil) + if err then error("Connection failed! " .. err .. "\n") end + + databaseObject = db + onConnected() + + if (TMySQL.Version and TMySQL.Version >= 4.1) then + hook.Add("Think", "MySQLite:tmysqlPoll", function() + db:Poll() + end) + end +end + +function connectToMySQL(host, username, password, database_name, database_port) + database_port = database_port or 3306 + local func = mysqlOO and msOOConnect or TMySQL and tmsqlConnect or function() end + func(host, username, password, database_name, database_port) +end + +function SQLStr(sqlStr) + local escape = + not CONNECTED_TO_MYSQL and sql.SQLStr or + mysqlOO and function(str) return "\"" .. databaseObject:escape(tostring(str)) .. "\"" end or + TMySQL and function(str) return "\"" .. databaseObject:Escape(tostring(str)) .. "\"" end + + return escape(sqlStr) +end + +function tableExists(tbl, callback, errorCallback) + if not CONNECTED_TO_MYSQL then + local exists = sql.TableExists(tbl) + callback(exists) + + return exists + end + + queryValue(string.format("SHOW TABLES LIKE %s", SQLStr(tbl)), function(v) + callback(v ~= nil) + end, errorCallback) +end diff --git a/gamemodes/darkrp/gamemode/libraries/sh_cami.lua b/gamemodes/darkrp/gamemode/libraries/sh_cami.lua new file mode 100644 index 0000000..cc0993e --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/sh_cami.lua @@ -0,0 +1,362 @@ +--[[ +CAMI - Common Admin Mod Interface. +Copyright 2020 CAMI Contributors + +Makes admin mods intercompatible and provides an abstract privilege interface +for third party addons. + +Follows the specification on this page: +https://github.com/glua/CAMI/blob/master/README.md + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] + +-- Version number in YearMonthDay format. +local version = 20211019 + +if CAMI and CAMI.Version >= version then return end + +CAMI = CAMI or {} +CAMI.Version = version + + +--- @class CAMI_USERGROUP +--- defines the charactaristics of a usergroup +--- @field Name string @The name of the usergroup +--- @field Inherits string @The name of the usergroup this usergroup inherits from +--- @field CAMI_Source string @The source specified by the admin mod which registered this usergroup (if any, converted to a string) + +--- @class CAMI_PRIVILEGE +--- defines the charactaristics of a privilege +--- @field Name string @The name of the privilege +--- @field MinAccess "'user'" | "'admin'" | "'superadmin'" @Default group that should have this privilege +--- @field Description string | nil @Optional text describing the purpose of the privilege +local CAMI_PRIVILEGE = {} +--- Optional function to check if a player has access to this privilege +--- (and optionally execute it on another player) +--- +--- ⚠ **Warning**: This function may not be called by all admin mods +--- @param actor GPlayer @The player +--- @param target GPlayer | nil @Optional - the target +--- @return boolean @If they can or not +--- @return string | nil @Optional reason +function CAMI_PRIVILEGE:HasAccess(actor, target) +end + +--- Contains the registered CAMI_USERGROUP usergroup structures. +--- Indexed by usergroup name. +--- @type CAMI_USERGROUP[] +local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or { + user = { + Name = "user", + Inherits = "user", + CAMI_Source = "Garry's Mod", + }, + admin = { + Name = "admin", + Inherits = "user", + CAMI_Source = "Garry's Mod", + }, + superadmin = { + Name = "superadmin", + Inherits = "admin", + CAMI_Source = "Garry's Mod", + } +} + +--- Contains the registered CAMI_PRIVILEGE privilege structures. +--- Indexed by privilege name. +--- @type CAMI_PRIVILEGE[] +local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {} + +--- Registers a usergroup with CAMI. +--- +--- Use the source parameter to make sure CAMI.RegisterUsergroup function and +--- the CAMI.OnUsergroupRegistered hook don't cause an infinite loop +--- @param usergroup CAMI_USERGROUP @The structure for the usergroup you want to register +--- @param source any @Identifier for your own admin mod. Can be anything. +--- @return CAMI_USERGROUP @The usergroup given as an argument +function CAMI.RegisterUsergroup(usergroup, source) + if source then + usergroup.CAMI_Source = tostring(source) + end + usergroups[usergroup.Name] = usergroup + + hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source) + return usergroup +end + +--- Unregisters a usergroup from CAMI. This will call a hook that will notify +--- all other admin mods of the removal. +--- +--- ⚠ **Warning**: Call only when the usergroup is to be permanently removed. +--- +--- Use the source parameter to make sure CAMI.UnregisterUsergroup function and +--- the CAMI.OnUsergroupUnregistered hook don't cause an infinite loop +--- @param usergroupName string @The name of the usergroup. +--- @param source any @Identifier for your own admin mod. Can be anything. +--- @return boolean @Whether the unregistering succeeded. +function CAMI.UnregisterUsergroup(usergroupName, source) + if not usergroups[usergroupName] then return false end + + local usergroup = usergroups[usergroupName] + usergroups[usergroupName] = nil + + hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source) + + return true +end + +--- Retrieves all registered usergroups. +--- @return CAMI_USERGROUP[] @Usergroups indexed by their names. +function CAMI.GetUsergroups() + return usergroups +end + +--- Receives information about a usergroup. +--- @param usergroupName string +--- @return CAMI_USERGROUP | nil @Returns nil when the usergroup does not exist. +function CAMI.GetUsergroup(usergroupName) + return usergroups[usergroupName] +end + +--- Checks to see if potentialAncestor is an ancestor of usergroupName. +--- All usergroups are ancestors of themselves. +--- +--- Examples: +--- * `user` is an ancestor of `admin` and also `superadmin` +--- * `admin` is an ancestor of `superadmin`, but not `user` +--- @param usergroupName string @The usergroup to query +--- @param potentialAncestor string @The ancestor to query +--- @return boolean @Whether usergroupName inherits potentialAncestor. +function CAMI.UsergroupInherits(usergroupName, potentialAncestor) + repeat + if usergroupName == potentialAncestor then return true end + + usergroupName = usergroups[usergroupName] and + usergroups[usergroupName].Inherits or + usergroupName + until not usergroups[usergroupName] or + usergroups[usergroupName].Inherits == usergroupName + + -- One can only be sure the usergroup inherits from user if the + -- usergroup isn't registered. + return usergroupName == potentialAncestor or potentialAncestor == "user" +end + +--- Find the base group a usergroup inherits from. +--- +--- This function traverses down the inheritence chain, so for example if you have +--- `user` -> `group1` -> `group2` +--- this function will return `user` if you pass it `group2`. +--- +--- ℹ **NOTE**: All usergroups must eventually inherit either user, admin or superadmin. +--- @param usergroupName string @The name of the usergroup +--- @return "'user'" | "'admin'" | "'superadmin'" @The name of the root usergroup +function CAMI.InheritanceRoot(usergroupName) + if not usergroups[usergroupName] then return end + + local inherits = usergroups[usergroupName].Inherits + while inherits ~= usergroups[usergroupName].Inherits do + usergroupName = usergroups[usergroupName].Inherits + end + + return usergroupName +end + +--- Registers an addon privilege with CAMI. +--- +--- ⚠ **Warning**: This should only be used by addons. Admin mods must *NOT* +--- register their privileges using this function. +--- @param privilege CAMI_PRIVILEGE +--- @return CAMI_PRIVILEGE @The privilege given as argument. +function CAMI.RegisterPrivilege(privilege) + privileges[privilege.Name] = privilege + + hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege) + + return privilege +end + +--- Unregisters a privilege from CAMI. +--- This will call a hook that will notify any admin mods of the removal. +--- +--- ⚠ **Warning**: Call only when the privilege is to be permanently removed. +--- @param privilegeName string @The name of the privilege. +--- @return boolean @Whether the unregistering succeeded. +function CAMI.UnregisterPrivilege(privilegeName) + if not privileges[privilegeName] then return false end + + local privilege = privileges[privilegeName] + privileges[privilegeName] = nil + + hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege) + + return true +end + +--- Retrieves all registered privileges. +--- @return CAMI_PRIVILEGE[] @All privileges indexed by their names. +function CAMI.GetPrivileges() + return privileges +end + +--- Receives information about a privilege. +--- @param privilegeName string +--- @return CAMI_PRIVILEGE | nil +function CAMI.GetPrivilege(privilegeName) + return privileges[privilegeName] +end + +-- Default access handler +local defaultAccessHandler = {["CAMI.PlayerHasAccess"] = + function(_, actorPly, privilegeName, callback, targetPly, extraInfoTbl) + -- The server always has access in the fallback + if not IsValid(actorPly) then return callback(true, "Fallback.") end + + local priv = privileges[privilegeName] + + local fallback = extraInfoTbl and ( + not extraInfoTbl.Fallback and actorPly:IsAdmin() or + extraInfoTbl.Fallback == "user" and true or + extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or + extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin()) + + + if not priv then return callback(fallback, "Fallback.") end + + local hasAccess = + priv.MinAccess == "user" or + priv.MinAccess == "admin" and actorPly:IsAdmin() or + priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin() + + if hasAccess and priv.HasAccess then + hasAccess = priv:HasAccess(actorPly, targetPly) + end + + callback(hasAccess, "Fallback.") + end, + ["CAMI.SteamIDHasAccess"] = + function(_, _, _, callback) + callback(false, "No information available.") + end +} + +--- @class CAMI_ACCESS_EXTRA_INFO +--- @field Fallback "'user'" | "'admin'" | "'superadmin'" @Fallback status for if the privilege doesn't exist. Defaults to `admin`. +--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have. +--- @field CommandArguments table @Extra arguments that were given to the privilege command. + +--- Checks if a player has access to a privilege +--- (and optionally can execute it on targetPly) +--- +--- This function is designed to be asynchronous but will be invoked +--- synchronously if no callback is passed. +--- +--- ⚠ **Warning**: If the currently installed admin mod does not support +--- synchronous queries, this function will throw an error! +--- @param actorPly GPlayer @The player to query +--- @param privilegeName string @The privilege to query +--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer, or nil for synchronous +--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban) +--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod +--- @return boolean | nil @Synchronous only - if the player has the privilege +--- @return string | nil @Synchronous only - optional reason from admin mod +function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly, +extraInfoTbl) + local hasAccess, reason = nil, nil + local callback_ = callback or function(hA, r) hasAccess, reason = hA, r end + + hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly, + privilegeName, callback_, targetPly, extraInfoTbl) + + if callback ~= nil then return end + + if hasAccess == nil then + local err = [[The function CAMI.PlayerHasAccess was used to find out + whether Player %s has privilege "%s", but an admin mod did not give an + immediate answer!]] + error(string.format(err, + actorPly:IsPlayer() and actorPly:Nick() or tostring(actorPly), + privilegeName)) + end + + return hasAccess, reason +end + +--- Get all the players on the server with a certain privilege +--- (and optionally who can execute it on targetPly) +--- +--- ℹ **NOTE**: This is an asynchronous function! +--- @param privilegeName string @The privilege to query +--- @param callback fun(players: GPlayer[]) @Callback to receive the answer +--- @param targetPly GPlayer | nil @Optional - target for if the privilege effects another player (eg kick/ban) +--- @param extraInfoTbl CAMI_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod +function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly, +extraInfoTbl) + local allowedPlys = {} + local allPlys = player.GetAll() + local countdown = #allPlys + + local function onResult(ply, hasAccess, _) + countdown = countdown - 1 + + if hasAccess then table.insert(allowedPlys, ply) end + if countdown == 0 then callback(allowedPlys) end + end + + for _, ply in ipairs(allPlys) do + CAMI.PlayerHasAccess(ply, privilegeName, + function(...) onResult(ply, ...) end, + targetPly, extraInfoTbl) + end +end + +--- @class CAMI_STEAM_ACCESS_EXTRA_INFO +--- @field IgnoreImmunity boolean @Ignore any immunity mechanisms an admin mod might have. +--- @field CommandArguments table @Extra arguments that were given to the privilege command. + +--- Checks if a (potentially offline) SteamID has access to a privilege +--- (and optionally if they can execute it on a target SteamID) +--- +--- ℹ **NOTE**: This is an asynchronous function! +--- @param actorSteam string | nil @The SteamID to query +--- @param privilegeName string @The privilege to query +--- @param callback fun(hasAccess: boolean, reason: string|nil) @Callback to receive the answer +--- @param targetSteam string | nil @Optional - target SteamID for if the privilege effects another player (eg kick/ban) +--- @param extraInfoTbl CAMI_STEAM_ACCESS_EXTRA_INFO | nil @Table of extra information for the admin mod +function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback, +targetSteam, extraInfoTbl) + hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam, + privilegeName, callback, targetSteam, extraInfoTbl) +end + +--- Signify that your admin mod has changed the usergroup of a player. This +--- function communicates to other admin mods what it thinks the usergroup +--- of a player should be. +--- +--- Listen to the hook to receive the usergroup changes of other admin mods. +--- @param ply GPlayer @The player for which the usergroup is changed +--- @param old string @The previous usergroup of the player. +--- @param new string @The new usergroup of the player. +--- @param source any @Identifier for your own admin mod. Can be anything. +function CAMI.SignalUserGroupChanged(ply, old, new, source) + hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source) +end + +--- Signify that your admin mod has changed the usergroup of a disconnected +--- player. This communicates to other admin mods what it thinks the usergroup +--- of a player should be. +--- +--- Listen to the hook to receive the usergroup changes of other admin mods. +--- @param steamId string @The steam ID of the player for which the usergroup is changed +--- @param old string @The previous usergroup of the player. +--- @param new string @The new usergroup of the player. +--- @param source any @Identifier for your own admin mod. Can be anything. +function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source) + hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source) +end diff --git a/gamemodes/darkrp/gamemode/libraries/simplerr.lua b/gamemodes/darkrp/gamemode/libraries/simplerr.lua new file mode 100644 index 0000000..653d116 --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/simplerr.lua @@ -0,0 +1,557 @@ +local CompileFile = CompileFile +local CompileString = CompileString +local debug = debug +local error = error +local file = file +local hook = hook +local include = include +local isfunction = isfunction +local isstring = isstring +local math = math +local os = os +local string = string +local table = table +local tonumber = tonumber +local unpack = unpack +local xpcall = xpcall + +-- Template for syntax errors +-- The [ERROR] start of it cannot be removed, because that would make the +-- error mechanism remove all square brackets. Only Garry can make that bullshit up. +local synErrTranslation = [=[[ERROR] Lua is unable to understand file "%s" because its author made a mistake around line number %i. +The best help I can give you is this: + +%s + +Hints: +%s + +------- End of Simplerr error ------- +]=] -- The end is a special string by which simplerr errors are internally recognised + +-- Template for runtime errors +local runErrTranslation = [=[[ERROR] A runtime error has occurred in "%s" on line %i. +The best help I can give you is this: + +%s + +Hints: +%s + +The responsibility for the error above lies with (the authors of) one (or more) of these files: +%s +------- End of Simplerr error ------- +]=] + +-- Structure that contains syntax errors and their translations. Catches only the most common errors. +-- Order is important: the structure with the first match is taken. +local synErrs = { + { + match = "'=' expected near '(.*)'", + text = "Right before the '%s', Lua expected to read an '='-sign, but it didn't.", + format = function(m) return m[1] end, + hints = { + "Did you simply forget the '='-sign?", + "Did you forget a comma?", + "Is this supposed to be a local variable?" + } + }, + { + match = "'.' expected [(]to close '([{[(])' at line ([0-9-]+)[)] near '(.*)'", + text = "There is an opening '%s' bracket at line %i, but this bracket is never closed or not closed in time. It was expected to be closed before the '%s' at line %i.", + format = function(m, l) return m[1], m[2], m[3], l end, + hints = { + "Did you forget a comma?", + "All open brackets ({, (, [) must have a matching closing bracket. Are you sure it's there?", + "Brackets must be opened and closed in the right order. This will work: ({}), but this won't: ({)}." + } + }, + { + match = "'end' expected [(]to close '(.*)' at line ([0-9-]+)[)] near '(.*)'", + text = "An '%s' was started on line %i, but it was never ended or not ended in time. It was expected to be ended before the '%s' at line %i", + format = function(m, l) return m[1], m[2], m[3], l end, + hints = { + "For every if/for/do/while/function there must be an 'end' that closes it." + } + }, + { + match = "unfinished string near '(.*)'", + text = "The string '%s' at line %i is opened, but not closed.", + format = function(m, l) return m[1], l end, + hints = { + "A string is a different word for literal text.", + "Strings must be in single or double quotation marks (e.g. 'example', \"example\")", + "A third option for strings is for them to be in double square brackets.", + "Whatever you use (quotations or square brackets), you must not forget that strings are enclosed within a pair of quotation marks/square brackets." + } + }, + { + match = "unfinished long string near '(.*)'", + text = "Lua expected to see the end of a multiline string somewhere before the '%s' at line %i.", + format = function(m, l) return m[1], l end, + hints = { + "A string is a different word for literal text.", + "Multiline strings are strings that span over multiple lines.", + "Multiline strings must be enclosed by double square brackets.", + "Whatever you use (quotations or square brackets), you must not forget that strings are enclosed within a pair of quotation marks/square brackets.", + "If you used brackets, the source of the mistake may be somewhere above the reported line." + } + }, + { + match = "unfinished long comment near '(.*)'", + text = "Lua expected to see the end of a multiline comment somewhere before the '%s' at line %i.", + format = function(m, l) return m[1], l end, + hints = { + "A comment is text ignored by Lua.", + "Multiline comments are ones that span multiple lines.", + "Multiline comments must be enclosed by either /* and */ or double square brackets.", + "Whatever you use (/**/ or square brackets), you must not forget that once you start a comment, you must end it.", + "The source of the mistake may be somewhere above the reported line." + } + }, + -- Generic error messages + { + match = "function arguments expected near '(.*)'", + text = "A function is being called right before '%s', but its arguments are not given.", + format = function(m) return m[1] end, + hints = { + "Did you write 'something:otherthing'? Try changing it to 'something:otherthing()'" + } + }, + { + match = "unexpected symbol near '(.*)'", + text = "Right before the '%s', Lua encountered something it could not make sense of.", + format = function(m) return m[1] end, + hints = {"Did you forget something here? (Perhaps a closing bracket)", "Is it a typo?"} + }, + { + match = "'(.*)' expected near '(.*)'", + text = "Right before the '%s', Lua expected to read a '%s', but it didn't.", + format = function(m) return m[2], m[1] end, + hints = {"Did you forget a keyword?", "Did you forget a comma?"} + }, + { + match = "malformed number near '(.*)'", + text = "Lua attempted to read '%s' as a number, but failed to do so.", + format = function(m) return m[1] end, + hints = { + "Numbers starting with '0x' are hexidecimal.", + "Lua can get confused when doing '..\"some text\"'. Try inserting a space between the number and the '..'." + } + }, +} + +-- Similar structure for runtime errors. Catches only the most common errors. +-- Order is important: the structure with the first match is taken +local runErrs = { + { + match = "table index is nil", + text = "A table is being indexed by something that does not exist (table index is nil).", -- Requires improvement + format = function() end, + hints = { + "The thing between square brackets does not exist (is nil)." + } + }, + { + match = "table index is NaN", + text = "A table is being indexed by something that is not really a number (table index is NaN).", + format = function() end, + hints = { + "Did you divide zero by zero thinking it would be funny?" + } + }, + { + match = "attempt to index global '(.*)' [(]a nil value[)]", + text = "'%s' is being indexed like it is a table, but in reality it does not exist (is nil).", + format = function(m) return m[1] end, + hints = { + "You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here does not exist." + } + }, + { + match = "attempt to index global '(.*)' [(]a (.*) value[)]", + text = "'%s' is being indexed like it is a table, but in reality it is a %s value.", + format = function(m) return m[1], m[2] end, + hints = { + "You either have 'something.somethingElse' or 'something:somethingElse(more)'. The 'something' here is not a table." + } + }, + { + match = "attempt to index a nil value", + text = "Something is being indexed like it is a table, but in reality does not exist (is nil).", + format = function() end, + hints = { + "You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here does not exist." + } + }, + { + match = "attempt to index a (.*) value", + text = "Something is being indexed like it is a table, but in reality it is a %s value.", + format = function(m) return m[1] end, + hints = { + "You either have 'something.somethingElse', 'something[somethingElse]' or 'something:somethingElse(more)'. The 'something' here is not a table." + } + }, + { + match = "attempt to call global '(.*)' [(]a nil value[)]", + text = "'%s' is being called like it is a function, but in reality does not exist (is nil).", + format = function(m) return m[1] end, + hints = { + "You are doing something(). The 'something' here does not exist." + } + }, + { + match = "attempt to call a nil value", + text = "Something is being called like it is a function, but in reality it does not exist (is nil).", + format = function() end, + hints = { + "You are doing something(). The 'something' here does not exist." + } + }, + { + match = "attempt to call global '(.*)' [(]a (.*) value[)]", + text = "'%s' is being called like it is a function, but in reality it is a %s.", + format = function(m) return m[1], m[2] end, + hints = { + "You are doing something(). The 'something' here is not a function." + } + }, + { + match = "attempt to call a (.*) value", + text = "Something is being called like it is a function, but in reality it is a %s.", + format = function(m) return m[1] end, + hints = { + "You are doing something(). The 'something' here is not a function." + } + }, + { + match = "attempt to call field '(.*)' [(]a nil value[)]", + text = "'%s' is being called like it is a function, but in reality it does not exist (is nil).", + format = function(m) return m[1] end, + hints = { + "You are doing either stuff.something() or stuff:something(). The 'something' here does not exist." + } + }, + { + match = "attempt to call field '(.*)' [(]a (.*) value[)]", + text = "'%s' is being called like it is a function, but in reality it is a %s.", + format = function(m) return m[1], m[2] end, + hints = { + "You are doing either stuff.something() or stuff:something(). The 'something' here is not a function." + } + }, + { + match = "attempt to concatenate global '(.*)' [(]a nil value[)]", + text = "'%s' is being concatenated to something else, but '%s' does not exist (is nil).", + format = function(m) return m[1], m[1] end, + hints = { + "Concatenation looks like this: something .. otherThing. Either something or otherThing does not exist." + } + }, + { + match = "attempt to concatenate global '(.*)' [(]a (.*) value[)]", + text = "'%s' is being concatenated to something else, but %s values cannot be concatenated.", + format = function(m) return m[1], m[2] end, + hints = { + "Concatenation looks like this: something .. otherThing. Either something or otherThing is neither string nor number." + } + }, + { + match = "attempt to concatenate a nil value", + text = "Two (or more) things are being concatenated and one of them does not exist (is nil).", + format = function() end, + hints = { + "Concatenation looks like this: something .. otherThing. Either something or otherThing does not exist." + } + }, + { + match = "attempt to concatenate a (.*) value", + text = "Two (or more) things are being concatenated and one of them is neither string nor number, but a %s.", + format = function(m) return m[1] end, + hints = { + "Concatenation looks like this: something .. otherThing. Either something or otherThing is neither string nor number." + } + }, + { + match = "stack overflow", + text = "The stack of function calls has overflowed", + format = function() end, + hints = { + "Most likely infinite recursion.", + "Do you have a function calling itself?" + } + }, + { + match = "attempt to compare two (.*) values", + text = "A comparison is being made between two %s values. They cannot be compared.", + format = function(m) return m[1] end, + hints = { + "This error usually occurs when two incompatible things are being compared.", + "'comparison' in this context means one of <, >, <=, >= (smaller than, greater than, etc.)" + } + }, + { + match = "attempt to compare (.*) with (.*)", + text = "A comparison is being made between a %s and a %s. This is not possible.", + format = function(m) return m[1], m[2] end, + hints = { + "This error usually occurs when two incompatible things are being compared.", + "'Comparison' in this context means one of <, >, <=, >= (smaller than, greater than, etc.)" + } + }, + { + match = "attempt to perform arithmetic on a (.*) value", + text = "Arithmetic operations are being performed on a %s. This is not possible.", + format = function(m) return m[1] end, + hints = { + "'Arithmetic' in this context means adding, multiplying, dividing, etc." + } + }, + { + match = "attempt to get length of global '(.*)' [(]a nil value[)]", + text = "The length of '%s' is requested as if it is a table, but in reality it does not exist (is nil).", + format = function(m) return m[1] end, + hints = { + "You are doing #something. The 'something' here is does not exist." + } + }, + { + match = "attempt to get length of global '(.*)' [(]a (.*) value[)]", + text = "The length of '%s' is requested as if it is a table, but in reality it is a %s.", + format = function(m) return m[1], m[2] end, + hints = { + "You are doing #something. The 'something' here is not a table." + } + }, + { + match = "attempt to get length of a nil value", + text = "The length of something is requested as if it is a table, but in reality it does not exist (is nil).", + format = function(m) return m[1] end, + hints = { + "You are doing #something. The 'something' here is does not exist." + } + }, + { + match = "attempt to get length of a (.*) value", + text = "The length of something is requested as if it is a table, but in reality it is a %s.", + format = function(m) return m[1] end, + hints = { + "You are doing #something. The 'something' here is not a table." + } + }, +} + +module("simplerr") + +-- Get a nicely formatted stack trace. Start is where to start numbering +-- stackMod allows the caller to modify the stack before it is numbered +local function getStack(i, start, stackMod) + i = i or 1 + start = start or 1 + local stack = {} + + -- Invariant: stack level (i + count) >= 2 and <= last stack item + for count = 1, math.huge do -- user visible count + local info = debug.getinfo(i + count, "Sln") + if not info then break end + + local line = info.currentline or "unknown" + if line == -1 and info.name then + table.insert(stack, string.format("function '%s'", info.name)) + else + table.insert(stack, string.format("%s on line %s", info.short_src, line)) + end + end + + -- Allow modification of the stack + if stackMod then stack = stackMod(stack) end + + -- add the numbering + for count = 1, #stack do + local stackLevel = start + count - 1 + stack[count] = string.format("\t%i. %s", stackLevel, stack[count]) + end + + return table.concat(stack, "\n") +end + +-- Translate a runtime error to simplerr format. +-- Decorate with e.g. wrapError to have it actually throw the error. +function runError(msg, stackNr, hints, path, line, stack) + stackNr = stackNr or 1 + hints = hints or {"No hints, sorry."} + hints = "\t- " .. table.concat(hints, "\n\t- ") + + if not path and not line then + local info = debug.getinfo(stackNr + 1, "Sln") or debug.getinfo(stackNr, "Sln") or debug.getinfo(stackNr - 1, "Sln") + path = info.short_src + line = info.currentline + end + + return false, string.format(runErrTranslation, path, line, msg, hints, stack or getStack(stackNr + 1)) +end + +-- Translate the message of an error +local function translateMsg(msg, path, line, errs) + local res + local hints = {"No hints, sorry."} + + for i = 1, #errs do + local trans = errs[i] + if not string.find(msg, trans.match) then continue end + + -- translate + msg = string.Replace(msg, "", "end of the file") + + res = string.format(trans.text, trans.format({string.match(msg, trans.match)}, line, path)) + hints = trans.hints + + break + end + + return res or msg, "\t- " .. table.concat(hints, "\n\t- ") +end + +-- Translate an error into a language understandable by non-programmers +local function translateError(path, line, err, translation, errs, stack) + -- Using .* instead of path because path may be wrong when error is called + local msg, hints = translateMsg(string.match(err, ".*:[0-9-]+: (.*)"), path, line, errs) + local res = string.format(translation, path, line, msg, hints, stack) + return res +end + + +-- Trims the [C] functions at the beginning of the stack +local function trimStart(stack) + while true do + if string.StartWith(stack[1], "function ") then + table.remove(stack, 1) + else + break + end + end + + return stack +end + +-- safeCall uses xpcall, which has the downside that both xpcall and +-- the safeCall function itself end up in the stack trace. +-- This function removes them from the stack trace +local function removeXpcall(stack) + for i = #stack - 1, 1, -1 do + if stack[i] == "function 'xpcall'" and string.find(stack[i + 1], "simplerr") then + table.remove(stack, i) + table.remove(stack, i) -- also remove the simplerr safeCall call + + return stack + end + end + + return stack +end + +-- Combines the two above functions +local function stackModAggregate(stack) + stack = trimStart(stack) + return removeXpcall(stack) +end + +-- Used as the error handler in safeCall +local function errorHandler(err, func) + -- Investigate the stack. Not using err matching because calls to error can give a different path and line + local stack = getStack(func and 1 or 2, 1, stackModAggregate) -- add called func to stack + + -- Fetch the path and line number from the top of the stack + local firstLine = string.sub(stack, 1, string.match(stack, "()\n") - 1) + local path, line = string.match(firstLine, "\t[0-9-]+%. (.*) on line ([0-9-]+)") + line = tonumber(line) + + return {err, path, line, stack} +end + +-- Call a function and catch immediate runtime errors +function safeCall(f, ...) + -- Use xpcall so fetching of debug info is in the stack of the error rather than after it is unwound + local res = {xpcall(f, errorHandler, ...)} + + local succ, errInfo = res[1], res[2] + + if succ then return unpack(res) end + + -- This will only happen if the error is "not enough memory" or "error in error handling". + -- The former tends to crash the game and the latter will mean it'll probably error in the next line. + -- But we will try anyway. + -- Note: stack trace will be less accurate. + if isstring(errInfo) then errInfo = errorHandler(errInfo, f) end + + -- Skip translation if the error is already a simplerr error + -- This prevents nested simplerr errors when runError is called by a file loaded by runFile + local mustTranslate = not string.find(errInfo[1], "------- End of Simplerr error -------") + return false, mustTranslate and translateError(errInfo[2], errInfo[3], errInfo[1], runErrTranslation, runErrs, errInfo[4]) or errInfo[1] +end + +-- Run a file or explain its syntax errors in layman's terms +-- Returns bool succeed, [string error] +-- Do NOT use this on clientside files. +-- Clientside files sent by the server cannot be read using file.Read unless you're the host of a listen server +function runFile(path) + if not file.Exists(path, "LUA") then error(string.format("Could not run file '%s' (file not found)", path)) end + local contents = file.Read(path, "LUA") + + -- Files can make a comment containing #NoSimplerr# to disable simplerr (and thus enable autorefresh) + if string.find(contents, "#NoSimplerr#") then include(path) return true end + + -- Catch syntax errors with CompileString + local err = CompileString(contents, path, false) + + -- CompileString returns the following string whenever a file is empty: Invalid script - or too short. + -- It also prints: Not running script - it's too short. + -- If so, do nothing. + if err == "Invalid script - or too short." then return true end + + -- No syntax errors, check for immediate runtime errors using CompileFile + -- Using the function CompileString returned leads to relative path trouble + if isfunction(err) then return safeCall(CompileFile(path), path) end + + -- Fetch the line number from the error + local line = string.match(err, ".*:([0-9-]+): .*") + line = tonumber(line) + + return false, translateError(path, line, err, synErrTranslation, synErrs) +end + +-- Error wrapper: decorator for runFile and safeCall that throws an error on failure. +-- Breaks execution. Must be the last decorator. +function wrapError(succ, err, ...) + if succ then return succ, err, ... end + + error(err) +end + +-- Hook wrapper: Calls a hook on error +function wrapHook(succ, err, ...) + if not succ then hook.Call("onSimplerrError", nil, err) end + + return succ, err, ... +end + +-- Logging wrapper: decorator for runFile and safeCall that logs failures. +local log = {} +function wrapLog(succ, err, ...) + if succ then return succ, err, ... end + + local data = { + err = err, + time = os.time() + } + + table.insert(log, data) + + return succ, err, ... +end + +-- Retrieve the log +function getLog() return log end + +-- Clear the log +function clearLog() log = {} end diff --git a/gamemodes/darkrp/gamemode/libraries/tablecheck.lua b/gamemodes/darkrp/gamemode/libraries/tablecheck.lua new file mode 100644 index 0000000..3eafe4c --- /dev/null +++ b/gamemodes/darkrp/gamemode/libraries/tablecheck.lua @@ -0,0 +1,364 @@ +--[[ +tablecheck + +WIP + +Author: FPtje Falco + +Purpose: +Allow validating tables by creating schemas of tables. Inspired by Joi (https://github.com/hapijs/joi) + +Requires fn library (https://github.com/FPtje/GModFunctional), + +Example: +```lua +local schema = tc.checkTable{ + name = tc.addHint(isstring, "The name must be a string!"), + id = tc.addHint(isnumber, "The id must be a number!"), + gender = tc.addHint(tc.oneOf{"male", "female", "carp"}, "Gender missing or not recognised!", {"Perhaps you are a carp?"}), +} + +local correct, err, hints = schema({name = "Dick", id = 3, gender = "carp"}) +print(correct) -- true + + +local correct, err, hints = schema({name = "Dick", id = 3, gender = "crap"}) +print(correct) -- false +print(err) -- Gender missing or not recognised! +PrintTable(hints) -- {"Perhaps you are a carp?"} +``` + +For further examples, including nesting and combining of schemas, please see the `unitTests` function for now. +--]] + +module("tc", package.seeall) + +-- Helpers for quick access to metatables +angle = FindMetaTable("Angle") +convar = FindMetaTable("ConVar") +effectdata = FindMetaTable("CEffectData") +entity = FindMetaTable("Entity") +file = FindMetaTable("File") +imaterial = FindMetaTable("IMaterial") +irestore = FindMetaTable("IRestore") +isave = FindMetaTable("ISave") +itexture = FindMetaTable("ITexture") +lualocomotion = FindMetaTable("CLuaLocomotion") +movedata = FindMetaTable("CMoveData") +navarea = FindMetaTable("CNavArea") +navladder = FindMetaTable("CNavLadder") +nextbot = FindMetaTable("NextBot") +npc = FindMetaTable("NPC") +pathfollower = FindMetaTable("PathFollower") +physobj = FindMetaTable("PhysObj") +player = FindMetaTable("Player") +recipientfilter = FindMetaTable("CRecipientFilter") +soundpatch = FindMetaTable("CSoundPatch") +takedamageinfo = FindMetaTable("CTakeDamageInfo") +usercmd = FindMetaTable("CUserCmd") +vector = FindMetaTable("Vector") +vehicle = FindMetaTable("Vehicle") +vmatrix = FindMetaTable("VMatrix") +weapon = FindMetaTable("Weapon") + +-- Assert function, asserts a property and returns the error if false. +-- Allows f to override err and hints by simply returning them +addHint = function(f, err, hints) return function(...) + local res = {f(...)} + res[2] = err + res[3] = hints + + return unpack(res) +end end + +--[[ Validates a table against a schema +Capable of nesting +--]] +function checkTable(schema) + return function(tbl) + if not istable(tbl) then + return false, "Not a table!" + end + + for k, v in pairs(schema or {}) do + local correct, err, hints = tbl[v] ~= nil + if isfunction(v) then correct, err, hints, replace, replaceWith = v(tbl[k], tbl) end + + + if not correct then + err = err or string.format("Element '%s' is corrupt!", k) + return correct, err, hints + end + + -- Update the value + if correct and replace == true and replaceWith then + tbl[k] = replaceWith + end + end + + return true + end +end + +-- Returns whether a value is nil +isnil = fn.Curry(fn.Eq, 2)(nil) + +-- Returns whether a value is a color +iscolor = IsColor + +-- Returns true on the client +client = function() return CLIENT end + +-- returns true on the server +server = function() return SERVER end + +-- Optional value, when filled in it must meet the conditions +optional = function(...) return fn.FOr{isnil, ...} end + +-- Default value, implies optional. Only works in combination with tc.checkTable +-- Note that the tc.addHint is to be the second parameter of default. +-- tc.addHint(tc.default(x)) does NOT work, default(x, tc.addHint(...)) does. +-- example: tc.checkTable{test = tc.default(3, tc.addHint(isnumber, "must be a number"))} +-- example: tc.checkTable{test = tc.default(3)} +default = function(def, f) + return function(val, ...) + if val == nil then + -- second return value is the default value. Expects parent function to actually change the value + return true, nil, nil, true, def + end + -- Return in if statement rather than "return f and f(val) or true" to allow multiple return values + if f then return f(val, ...) else return true end + end +end + +-- A table of which each element must meet condition f +-- i.e. "this must be a table of xxx" +-- example: tc.tableOf(isnumber) demands that the table contains only numbers +tableOf = function(f) return function(tbl, parentTbl) + if not istable(tbl) then return false end + for _, v in pairs(tbl) do + local res = {f(v, parentTbl)} + if not res[1] then + return unpack(res) + end + end + + return true +end end + +-- Checks whether a value is amongst a given set of values +-- exapmle: tc.oneOf{"jobs", "entities", "shipments", "weapons", "vehicles", "ammo"} +oneOf = function(f) return fp{table.HasValue, f} end + +-- A table that is non-empty, also useful for wrapping around tableOf +-- example: tc.nonEmpty(tc.tableOf(isnumber)) +-- example: tc.nonEmpty() -- just checks that the table is non-empty +nonEmpty = function(f) return function(tbl, parentTbl) + if not istable(tbl) or table.IsEmpty(tbl) then return false end + if not f then return true end + return f(tbl, parentTbl) +end end + +-- Number check: minimum +min = function(n) return fn.FAnd{isnumber, fp{fn.Lte, n}} end + +-- Number check: maximum +max = function(n) return fn.FAnd{isnumber, fp{fn.Gte, n}} end + +-- Number check: positive +positive = min(0) + +-- Number check: negative +negative = max(0) + + +-- Whether the input matches regex +-- Note: uses string.match, so it doesn't support full regex. +-- May also allow numbers, since string.match also accepts numbers. +-- Note, also matches on substrings. Use ^pattern$ for a full match. +regex = function(pattern, startpos) return function(val) + return (isstring(val) or isnumber(val)) and tobool(string.match(val, pattern, startpos)) +end end + +-- Requires that the value only contains alphanumeric characters +alphanum = regex("^[a-zA-Z0-9]+$") + + +-- Test cases. Also serve as nice examples +function unitTests() + local id = 0 + + -- unit test helper functions + local function checkCorrect(correct, err, hints) + id = id + 1 + + if correct ~= true then + print(id, "Incorrect value that should be correct!", correct, err, hints) + if hints then PrintTable(hints) end + return + end + + print(id, "Correct") + end + + local function checkIncorrect(correct, err, hints) + id = id + 1 + + if correct then + print(id, "Correct value that should be incorrect!", correct, err, hints) + if hints then PrintTable(hints) end + return + end + + print(id, "Correct") + end + + --[[ + Simple value schema. Checks whether the input is a number. + ]] + local simpleSchema = tc.addHint(isnumber, "Must be a number!") + + -- This is how a schema is to be used. Just call it with the value you want to check. + -- In further unit tests, the schema function is immediately called inside the checkCorrect/checIncorrect call for brevity + local correct, err, hints = simpleSchema(3) + + checkCorrect(correct, err, hints) + + + --[[ + Simple table schema + ]] + local simpleTableSchema = tc.checkTable{ + name = tc.addHint(isstring, "The name must be a string!"), + id = tc.addHint(isnumber, "The id must be a number!"), + gender = tc.addHint(tc.oneOf{"male", "female", "carp"}, "Gender missing or not recognised!", {"Perhaps you are a carp?"}), + nilthing = tc.addHint(tc.isnil, "nilthing must be nil"), + nonEmpty = tc.addHint(tc.nonEmpty(tc.tableOf(isnumber)), "nonEmpty not table of numbers"), + optnum = tc.addHint(tc.optional(isnumber), "optnum given, but not a number"), + strnum = tc.addHint(fn.FOr{isstring, isnumber}, "strnum must either be a string or a number"), + minmax = tc.addHint(fn.FAnd{tc.min(5), tc.max(10)}), + pos = tc.addHint(tc.optional(tc.positive)), + regx = tc.addHint(tc.optional(tc.regex("[a-z]+"))), + letters = tc.addHint(tc.optional(tc.alphanum)), + } + + checkCorrect(simpleTableSchema({name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 5, regx = "asdf", letters = "asdfj", pos = 3})) + + -- Counterexamples, should throw errors + local badTables = { + {}, + {name = 1, id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7}, + {name = "Dick", id = "3", gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7}, + {name = "Dick", id = 3, gender = "other", nonEmpty = {1,2,3}, strnum = "str", minmax = 7}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {}, strnum = "str", minmax = 7}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = {}, minmax = 7}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", optnum = "nope", minmax = 7}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 4}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 11}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str"}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7, regx = "666"}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7, regx = "asdf", letters = ">:D"}, + {name = "Dick", id = 3, gender = "carp", nonEmpty = {1,2,3}, strnum = "str", minmax = 7, regx = "asdf", letters = ">:D", pos = -1}, + } + + for _, tbl in pairs(badTables) do + checkIncorrect(simpleTableSchema(tbl)) + end + + --[[ + Table Schema with no explicit keys + ]] + local nokeysSchema = tc.checkTable{ + tc.addHint(isstring, "The first value must be a string."), + tc.addHint(isnumber, "The second value must be a number!"), + } + checkCorrect(nokeysSchema({"string", 3})) + + --[[ + Nested table schema + ]] + local nestedSchema = tc.checkTable{ + nested = tc.checkTable{ + val = tc.addHint(isnumber, "'val' must be a number!") + } + } + + checkCorrect(nestedSchema({nested = {val = 3}})) + checkIncorrect(nestedSchema({})) + + --[[ + Combining schemas using the fn library + ]] + local andSchema = fn.FAnd{ + tc.checkTable{ + num = tc.addHint(isnumber, "num is not a number") + }, + tc.checkTable{ + str = tc.addHint(isstring, "str is not a string") + } + } + + checkCorrect(andSchema({num = 1, str = "string!"})) + checkIncorrect(andSchema({num = 1})) + checkIncorrect(andSchema({str = "string!"})) + + local orSchema = fn.FOr{ + tc.checkTable{ + num = tc.addHint(isnumber, "num is not a number") + }, + tc.checkTable{ + str = tc.addHint(isstring, "str is not a string") + } + } + checkCorrect(orSchema({num = 1})) + checkCorrect(orSchema({str = "string!"})) + + --[[ + Default value with a check + ]] + local withDefaultSchema = tc.checkTable{ + value = tc.default(10, tc.addHint(isnumber, "must be a number!")) + } + checkCorrect(withDefaultSchema({value = 30})) + checkIncorrect(withDefaultSchema({value = "string"})) + + local empty = {} + checkCorrect(withDefaultSchema(empty)) + if empty.value ~= 10 then + print("Default did NOT set the value to 10!") + else + print("Default test OK!") + end + + --[[ + Default value with no checks + ]] + local withDefaultNoCheck = tc.checkTable{ + value = tc.default(10) + } + checkCorrect(withDefaultNoCheck({})) + checkCorrect(withDefaultNoCheck({value = "string"})) + + --[[ + Creating your own checker function that returns an error message + When both the function and the tc.addHint define error messages, there's a conflict + ]] + local function customCheck(val) + return false, "function error message", {"function hint"} + end + + local customCheckSchema = tc.checkTable{ + value = tc.addHint(customCheck, "added error message", {"added hint"}) + } + checkIncorrect(customCheckSchema{value = 1}) + checkIncorrect(customCheckSchema{}) + + _, err, hints = customCheckSchema{value = 2} + if err ~= "added error message" or hints[1] ~= "added hint" then + print("Wrong conflict solution", err, hints[1]) + else + print("Conflict solution OK!") + end + + print("finished") +end diff --git a/gamemodes/darkrp/gamemode/modules/DO NOT TOUCH THESE MODULES.txt b/gamemodes/darkrp/gamemode/modules/DO NOT TOUCH THESE MODULES.txt new file mode 100644 index 0000000..f786f4b --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/DO NOT TOUCH THESE MODULES.txt @@ -0,0 +1,4 @@ +Do not add, edit or remove anything from this folder. + +Use the DarkRPMod addon instead +https://github.com/FPtje/DarkRPModification diff --git a/gamemodes/darkrp/gamemode/modules/afk/cl_afk.lua b/gamemodes/darkrp/gamemode/modules/afk/cl_afk.lua new file mode 100644 index 0000000..bfd93a1 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/afk/cl_afk.lua @@ -0,0 +1,17 @@ +local TextColor = Color(GetConVar("Healthforeground1"):GetFloat(), GetConVar("Healthforeground2"):GetFloat(), GetConVar("Healthforeground3"):GetFloat(), GetConVar("Healthforeground4"):GetFloat()) + +local function AFKHUDPaint() + if not LocalPlayer():getDarkRPVar("AFK") then return end + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("afk_mode"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 100, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("salary_frozen"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 60, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + if not LocalPlayer():getDarkRPVar("AFKDemoted") then + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("no_auto_demote"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("youre_afk_demoted"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + + draw.DrawNonParsedSimpleText(DarkRP.getPhrase("afk_cmd_to_exit"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) + 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) +end + +hook.Add("HUDPaint", "AFK_HUD", AFKHUDPaint) diff --git a/gamemodes/darkrp/gamemode/modules/afk/sh_commands.lua b/gamemodes/darkrp/gamemode/modules/afk/sh_commands.lua new file mode 100644 index 0000000..e359265 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/afk/sh_commands.lua @@ -0,0 +1,8 @@ +DarkRP.registerDarkRPVar("AFK", net.WriteBit, fn.Compose{tobool, net.ReadBit}) +DarkRP.registerDarkRPVar("AFKDemoted", net.WriteBit, fn.Compose{tobool, net.ReadBit}) + +DarkRP.declareChatCommand{ + command = "afk", + description = "Go AFK", + delay = 1.5 +} diff --git a/gamemodes/darkrp/gamemode/modules/afk/sv_afk.lua b/gamemodes/darkrp/gamemode/modules/afk/sv_afk.lua new file mode 100644 index 0000000..ee8a8a9 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/afk/sv_afk.lua @@ -0,0 +1,129 @@ +-- How to use: +-- If a player uses /afk, they go into AFK mode, they will not be autodemoted and their salary is set to $0 (you can still be killed/vote demoted though!). +-- If a player does not use /afk, and they don't do anything for the demote time specified, they will be automatically demoted to hobo. + +local function AFKDemote(ply) + local shouldDemote, demoteTeam, suppressMsg, msg = hook.Call("playerAFKDemoted", nil, ply) + demoteTeam = demoteTeam or GAMEMODE.DefaultTeam + + if ply:Team() ~= demoteTeam and shouldDemote ~= false then + local rpname = ply:getDarkRPVar("rpname") + ply:changeTeam(demoteTeam, true) + if not suppressMsg then DarkRP.notifyAll(0, 5, msg or DarkRP.getPhrase("hes_afk_demoted", rpname)) end + end + ply:setSelfDarkRPVar("AFKDemoted", true) + ply:setDarkRPVar("job", "AFK") +end + +local function SetAFK(ply) + local rpname = ply:getDarkRPVar("rpname") + ply:setSelfDarkRPVar("AFK", not ply:getDarkRPVar("AFK")) + + ply.blackScreen = ply:getDarkRPVar("AFK") + SendUserMessage("blackScreen", ply, ply:getDarkRPVar("AFK")) + + if ply:getDarkRPVar("AFK") then + DarkRP.retrieveSalary(ply, function(amount) ply.OldSalary = amount end) + ply.OldJob = ply:getDarkRPVar("job") + ply.lastHealth = ply:Health() + DarkRP.notifyAll(0, 5, DarkRP.getPhrase("player_now_afk", rpname)) + + ply.AFKDemote = math.huge + + ply:KillSilent() + ply:Lock() + else + ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime + DarkRP.notifyAll(1, 5, DarkRP.getPhrase("player_no_longer_afk", rpname)) + DarkRP.notify(ply, 0, 5, DarkRP.getPhrase("salary_restored")) + ply:Spawn() + ply:UnLock() + + ply:SetHealth(ply.lastHealth and ply.lastHealth > 0 and ply.lastHealth or 100) + ply.lastHealth = nil + end + + if not ply.demotedWhileDead then + ply:setDarkRPVar("job", ply:getDarkRPVar("AFK") and "AFK" or ply:getDarkRPVar("AFKDemoted") and team.GetName(ply:Team()) or ply.OldJob) + ply:setSelfDarkRPVar("salary", ply:getDarkRPVar("AFK") and 0 or ply.OldSalary or 0) + end + + hook.Run("playerSetAFK", ply, ply:getDarkRPVar("AFK")) +end + +DarkRP.defineChatCommand("afk", function(ply) + if ply.DarkRPLastAFK and not ply:getDarkRPVar("AFK") and ply.DarkRPLastAFK > CurTime() - GAMEMODE.Config.AFKDelay then + DarkRP.notify(ply, 0, 5, DarkRP.getPhrase("unable_afk_spam_prevention")) + return "" + end + + local canAFK = hook.Run("canGoAFK", ply, not ply:getDarkRPVar("AFK")) + + if canAFK == false then return "" end + + ply.DarkRPLastAFK = CurTime() + SetAFK(ply) + + return "" +end) + +local function StartAFKOnPlayer(ply) + ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime +end +hook.Add("PlayerInitialSpawn", "StartAFKOnPlayer", StartAFKOnPlayer) + +local function AFKTimer(ply, key) + ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime + if ply:getDarkRPVar("AFKDemoted") then + ply:setDarkRPVar("job", team.GetName(ply:Team())) + timer.Simple(3, function() if IsValid(ply) then ply:setSelfDarkRPVar("AFKDemoted", nil) end end) + end +end +hook.Add("KeyPress", "DarkRPKeyReleasedCheck", AFKTimer) + +local function KillAFKTimer() + for _, ply in ipairs(player.GetAll()) do + if ply.AFKDemote and CurTime() > ply.AFKDemote and not ply:getDarkRPVar("AFK") and not ply:IsBot() then + SetAFK(ply) + AFKDemote(ply) + ply.AFKDemote = math.huge + end + end +end +timer.Create("DarkRPKeyPressedCheck", 1, 0, function() + KillAFKTimer() +end) + +local function BlockAFKTeamChange(ply, t, force) + if ply:getDarkRPVar("AFK") and (not force or t ~= GAMEMODE.DefaultTeam) then + local TEAM = RPExtraTeams[t] + if TEAM then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. TEAM.command, DarkRP.getPhrase("afk_mode"))) end + return false + end +end +hook.Add("playerCanChangeTeam", "AFKCanChangeTeam", BlockAFKTeamChange) + +-- Freeze AFK player's salary +hook.Add("playerGetSalary", "AFKGetSalary", function(ply, amount) + if ply:getDarkRPVar("AFK") then + return true, "", 0 + end +end) + +-- For when a player's team is changed by force +hook.Add("OnPlayerChangedTeam", "AFKCanChangeTeam", function(ply) + if not ply:getDarkRPVar("AFK") then return end + + ply.OldSalary = ply:getDarkRPVar("salary") + ply.OldJob = nil + ply:setSelfDarkRPVar("salary", 0) +end) + +local function unAFKPlayer(ply) + if ply:getDarkRPVar("AFK") then + SetAFK(ply) + end +end + +hook.Add("playerArrested", "DarkRP_AFK", unAFKPlayer) +hook.Add("playerUnArrested", "DarkRP_AFK", unAFKPlayer) diff --git a/gamemodes/darkrp/gamemode/modules/afk/sv_interface.lua b/gamemodes/darkrp/gamemode/modules/afk/sv_interface.lua new file mode 100644 index 0000000..a32274a --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/afk/sv_interface.lua @@ -0,0 +1,76 @@ +DarkRP.hookStub{ + name = "playerAFKDemoted", + description = "When a player is demoted for being AFK.", + parameters = { + { + name = "ply", + description = "The player being demoted.", + type = "Player" + } + }, + returns = { + { + name = "shouldDemote", + description = "Prevent the player from being actually demoted.", + type = "boolean" + }, + { + name = "team", + description = "The team the player is to be demoted to (shouldDemote must be true.)", + type = "number" + }, + { + name = "suppressMessage", + description = "Suppress the demote message.", + type = "boolean" + }, + { + name = "demoteMessage", + description = "Replacement of the demote message text.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "playerSetAFK", + description = "When a player is set to AFK or returns from AFK.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "afk", + description = "True when the player starts being AFK, false when the player stops being AFK.", + type = "boolean" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "canGoAFK", + description = "When a player can MANUALLY start being AFK by entering the chat command. Note: this hook does NOT get called when a player is set to AFK automatically! That hook will not be added, because I don't want asshole server owners to make AFK rules not apply to admins.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "afk", + description = "True when the player starts being AFK, false when the player stops being AFK.", + type = "boolean" + } + }, + returns = { + { + name = "canGoAFK", + description = "Whether the player is allowed to go AFK", + type = "boolean" + } + } +} diff --git a/gamemodes/darkrp/gamemode/modules/animations/sh_animations.lua b/gamemodes/darkrp/gamemode/modules/animations/sh_animations.lua new file mode 100644 index 0000000..65b3a13 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/animations/sh_animations.lua @@ -0,0 +1,152 @@ +local Anims = {} + +-- Load animations after the languages for translation purposes +hook.Add("loadCustomDarkRPItems", "loadAnimations", function() + Anims[ACT_GMOD_GESTURE_BOW] = DarkRP.getPhrase("bow") + Anims[ACT_GMOD_TAUNT_MUSCLE] = DarkRP.getPhrase("sexy_dance") + Anims[ACT_GMOD_GESTURE_BECON] = DarkRP.getPhrase("follow_me") + Anims[ACT_GMOD_TAUNT_LAUGH] = DarkRP.getPhrase("laugh") + Anims[ACT_GMOD_TAUNT_PERSISTENCE] = DarkRP.getPhrase("lion_pose") + Anims[ACT_GMOD_GESTURE_DISAGREE] = DarkRP.getPhrase("nonverbal_no") + Anims[ACT_GMOD_GESTURE_AGREE] = DarkRP.getPhrase("thumbs_up") + Anims[ACT_GMOD_GESTURE_WAVE] = DarkRP.getPhrase("wave") + Anims[ACT_GMOD_TAUNT_DANCE] = DarkRP.getPhrase("dance") +end) + +function DarkRP.addPlayerGesture(anim, text) + if not anim then DarkRP.error("Argument #1 of DarkRP.addPlayerGesture (animation/gesture) does not exist.", 2) end + if not text then DarkRP.error("Argument #2 of DarkRP.addPlayerGesture (text) does not exist.", 2) end + + Anims[anim] = text +end + +function DarkRP.removePlayerGesture(anim) + if not anim then DarkRP.error("Argument #1 of DarkRP.removePlayerGesture (animation/gesture) does not exist.", 2) end + + Anims[anim] = nil +end + +local function physGunCheck(ply) + local hookName = "darkrp_anim_physgun_" .. ply:EntIndex() + hook.Add("Think", hookName, function() + if IsValid(ply) and + ply:Alive() and + ply:GetActiveWeapon():IsValid() and + ply:GetActiveWeapon():GetClass() == "weapon_physgun" and + ply:KeyDown(IN_ATTACK) and + (ply:GetAllowWeaponsInVehicle() or not ply:InVehicle()) then + local ent = ply:GetEyeTrace().Entity + if IsValid(ent) and ent:IsPlayer() and not ply.SaidHi then + ply.SaidHi = true + ply:DoAnimationEvent(ACT_SIGNAL_GROUP) + end + else + if IsValid(ply) then + ply.SaidHi = nil + end + hook.Remove("Think", hookName) + end + end) +end + +hook.Add("KeyPress", "darkrp_animations", function(ply, key) + if key == IN_ATTACK then + local weapon = ply:GetActiveWeapon() + + if weapon:IsValid() then + local class = weapon:GetClass() + + -- Saying hi/hello to a player + if class == "weapon_physgun" then + physGunCheck(ply) + + -- Hobo throwing poop! + elseif class == "weapon_bugbait" then + local Team = ply:Team() + if RPExtraTeams[Team] and RPExtraTeams[Team].hobo then + ply:DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_THROW) + end + end + end + end +end) + +if SERVER then + local function CustomAnim(ply, cmd, args) + if ply:EntIndex() == 0 then return end + local Gesture = tonumber(args[1] or 0) + if not Anims[Gesture] then return end + + local RP = RecipientFilter() + RP:AddAllPlayers() + + umsg.Start("_DarkRP_CustomAnim", RP) + umsg.Entity(ply) + umsg.Short(Gesture) + umsg.End() + end + concommand.Add("_DarkRP_DoAnimation", CustomAnim) + return +end + +local function KeysAnims(um) + local ply = um:ReadEntity() + local act = um:ReadString() + + if not IsValid(ply) then return end + ply:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act == "usekeys" and ACT_GMOD_GESTURE_ITEM_PLACE or ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST, true) +end +usermessage.Hook("anim_keys", KeysAnims) + +local function CustomAnimation(um) + local ply = um:ReadEntity() + local act = um:ReadShort() + + if not IsValid(ply) then return end + ply:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act, true) +end +usermessage.Hook("_DarkRP_CustomAnim", CustomAnimation) + +local AnimFrame +local function AnimationMenu() + if AnimFrame then return end + + local Panel = vgui.Create("Panel") + Panel:SetPos(0,0) + Panel:SetSize(ScrW(), ScrH()) + function Panel:OnMousePressed() + AnimFrame:Close() + end + + AnimFrame = AnimFrame or vgui.Create("DFrame", Panel) + local Height = table.Count(Anims) * 55 + 32 + AnimFrame:SetSize(130, Height) + AnimFrame:SetPos(ScrW() / 2 + ScrW() * 0.1, ScrH() / 2 - (Height / 2)) + AnimFrame:SetTitle(DarkRP.getPhrase("custom_animation")) + AnimFrame.btnMaxim:SetVisible(false) + AnimFrame.btnMinim:SetVisible(false) + AnimFrame:SetVisible(true) + AnimFrame:MakePopup() + AnimFrame:ParentToHUD() + + function AnimFrame:Close() + Panel:Remove() + AnimFrame:Remove() + AnimFrame = nil + end + + local i = 0 + for k, v in SortedPairs(Anims) do + i = i + 1 + local button = vgui.Create("DButton", AnimFrame) + button:SetPos(10, (i - 1) * 55 + 30) + button:SetSize(110, 50) + button:SetText(v) + + button.DoClick = function() + RunConsoleCommand("_DarkRP_DoAnimation", k) + end + end + AnimFrame:SetSkin(GAMEMODE.Config.DarkRPSkin) +end +concommand.Add("_DarkRP_AnimationMenu", AnimationMenu) diff --git a/gamemodes/darkrp/gamemode/modules/animations/sh_interface.lua b/gamemodes/darkrp/gamemode/modules/animations/sh_interface.lua new file mode 100644 index 0000000..93aeddb --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/animations/sh_interface.lua @@ -0,0 +1,37 @@ +DarkRP.addPlayerGesture = DarkRP.stub{ + name = "addPlayerGesture", + description = "Add a player gesture to the DarkRP animations menu (the one that opens with the keys weapon.). Note: This function must be called BOTH serverside AND clientside!", + parameters = { + { + name = "anim", + description = "The gesture enumeration.", + type = "number", + optional = false + }, + { + name = "text", + description = "The textual description of the animation. This is what players see on the button in the menu.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removePlayerGesture = DarkRP.stub{ + name = "removePlayerGesture", + description = "Removes a player gesture from the DarkRP animations menu (the one that opens with the keys weapon.). Note: This function must be called BOTH serverside AND clientside!", + parameters = { + { + name = "anim", + description = "The gesture enumeration.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} diff --git a/gamemodes/darkrp/gamemode/modules/base/cl_drawfunctions.lua b/gamemodes/darkrp/gamemode/modules/base/cl_drawfunctions.lua new file mode 100644 index 0000000..c5461bb --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/cl_drawfunctions.lua @@ -0,0 +1,30 @@ +-- concatenate a space to avoid the text being parsed as valve string +local function safeText(text) + return string.match(text, "^#([a-zA-Z_]+)$") and text .. " " or text +end + +DarkRP.deLocalise = safeText + +function draw.DrawNonParsedText(text, font, x, y, color, xAlign) + return draw.DrawText(safeText(text), font, x, y, color, xAlign) +end + +function draw.DrawNonParsedSimpleText(text, font, x, y, color, xAlign, yAlign) + return draw.SimpleText(safeText(text), font, x, y, color, xAlign, yAlign) +end + +function draw.DrawNonParsedSimpleTextOutlined(text, font, x, y, color, xAlign, yAlign, outlineWidth, outlineColor) + return draw.SimpleTextOutlined(safeText(text), font, x, y, color, xAlign, yAlign, outlineWidth, outlineColor) +end + +function surface.DrawNonParsedText(text) + return surface.DrawText(safeText(text)) +end + +function chat.AddNonParsedText(...) + local tbl = {...} + for i = 2, #tbl, 2 do + tbl[i] = safeText(tbl[i]) + end + return chat.AddText(unpack(tbl)) +end diff --git a/gamemodes/darkrp/gamemode/modules/base/cl_entityvars.lua b/gamemodes/darkrp/gamemode/modules/base/cl_entityvars.lua new file mode 100644 index 0000000..b9dcd7a --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/cl_entityvars.lua @@ -0,0 +1,139 @@ +DarkRP.ClientsideDarkRPVars = DarkRP.ClientsideDarkRPVars or {} + +--[[--------------------------------------------------------------------------- +Interface +---------------------------------------------------------------------------]] +local pmeta = FindMetaTable("Player") +-- This function is made local to optimise getDarkRPVar, which is called often +-- enough to warrant optimizing. See https://github.com/FPtje/DarkRP/pull/3212 +local get_user_id = pmeta.UserID +function pmeta:getDarkRPVar(var, fallback) + local user_id = get_user_id(self) + + -- Special case: when in the EntityRemoved hook, UserID returns -1. In this + -- case, hope that we still have a stored userID lying around somewhere. + -- See https://github.com/FPtje/DarkRP/pull/3270 + if user_id == -1 then + user_id = self._darkrp_stored_user_id_for_entity_removed_hook + end + + local vars = DarkRP.ClientsideDarkRPVars[user_id] + if vars == nil then return fallback end + + local results = vars[var] + if results == nil then return fallback end + + return results +end + +--[[--------------------------------------------------------------------------- +Retrieve the information of a player var +---------------------------------------------------------------------------]] +local function RetrievePlayerVar(userID, var, value) + local ply = Player(userID) + DarkRP.ClientsideDarkRPVars[userID] = DarkRP.ClientsideDarkRPVars[userID] or {} + + hook.Call("DarkRPVarChanged", nil, ply, var, DarkRP.ClientsideDarkRPVars[userID][var], value) + DarkRP.ClientsideDarkRPVars[userID][var] = value + + -- Backwards compatibility + if IsValid(ply) then + ply.DarkRPVars = DarkRP.ClientsideDarkRPVars[userID] + end +end + +--[[--------------------------------------------------------------------------- +Retrieve a player var. +Read the usermessage and attempt to set the DarkRP var +---------------------------------------------------------------------------]] +local function doRetrieve() + local userID = net.ReadUInt(16) + local var, value = DarkRP.readNetDarkRPVar() + + RetrievePlayerVar(userID, var, value) +end +net.Receive("DarkRP_PlayerVar", doRetrieve) + +--[[--------------------------------------------------------------------------- +Retrieve the message to remove a DarkRPVar +---------------------------------------------------------------------------]] +local function doRetrieveRemoval() + local userID = net.ReadUInt(16) + local vars = DarkRP.ClientsideDarkRPVars[userID] or {} + local var = DarkRP.readNetDarkRPVarRemoval() + local ply = Player(userID) + + hook.Call("DarkRPVarChanged", nil, ply, var, vars[var], nil) + + vars[var] = nil +end +net.Receive("DarkRP_PlayerVarRemoval", doRetrieveRemoval) + +--[[--------------------------------------------------------------------------- +Initialize the DarkRPVars at the start of the game +---------------------------------------------------------------------------]] +local function InitializeDarkRPVars(len) + local plyCount = net.ReadUInt(8) + + for i = 1, plyCount, 1 do + local userID = net.ReadUInt(16) + local varCount = net.ReadUInt(DarkRP.DARKRP_ID_BITS + 2) + + for j = 1, varCount, 1 do + local var, value = DarkRP.readNetDarkRPVar() + RetrievePlayerVar(userID, var, value) + end + end +end +net.Receive("DarkRP_InitializeVars", InitializeDarkRPVars) +timer.Simple(0, fp{RunConsoleCommand, "_sendDarkRPvars"}) + +net.Receive("DarkRP_DarkRPVarDisconnect", function(len) + local userID = net.ReadUInt(16) + local ply = Player(userID) + + -- If the player is already gone, then immediately clear the data and move on. + if not IsValid(ply) then + DarkRP.ClientsideDarkRPVars[userID] = nil + return + end + -- Otherwise, we need to wait until the player is actually removed + -- clientside. The net message may come in _much_ earlier than the message + -- that the player disconnected and should therefore be removed. + local hook_name = "darkrp_remove_darkrp_var_" .. userID + + -- Workaround: the player's user ID is -1 in the EntityRemoved hook. This + -- stores the user ID in a separate variable so that it is still accessible. + -- See https://github.com/Facepunch/garrysmod-issues/issues/6117 + -- + -- This will allow getDarkRPVar to keep working + if IsValid(ply) then + ply._darkrp_stored_user_id_for_entity_removed_hook = userID + end + + hook.Add("EntityRemoved", hook_name, function(ent) + if ent ~= ply then return end + hook.Remove("EntityRemoved", hook_name) + + -- Placing this in a timer allows for the rest of the hook runners to + -- still use the DarkRPVars until the entity is _really_ gone. + -- See https://github.com/FPtje/DarkRP/pull/3270 + timer.Simple(0, function() + DarkRP.ClientsideDarkRPVars[userID] = nil + end) + end) +end) + +--[[--------------------------------------------------------------------------- +Request the DarkRPVars when they haven't arrived +---------------------------------------------------------------------------]] +timer.Create("DarkRPCheckifitcamethrough", 15, 0, function() + for _, v in ipairs(player.GetAll()) do + if v:getDarkRPVar("rpname") then continue end + + RunConsoleCommand("_sendDarkRPvars") + return + end + + timer.Remove("DarkRPCheckifitcamethrough") +end) diff --git a/gamemodes/darkrp/gamemode/modules/base/cl_fonts.lua b/gamemodes/darkrp/gamemode/modules/base/cl_fonts.lua new file mode 100644 index 0000000..ffc7092 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/cl_fonts.lua @@ -0,0 +1,158 @@ +--[[--------------------------------------------------------------------------- +The fonts that DarkRP uses +---------------------------------------------------------------------------]] +local function loadFonts() + surface.CreateFont("DarkRPHUD1", { + size = 20, + weight = 600, + antialias = true, + shadow = true, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("DarkRPHUD2", { + size = 23, + weight = 400, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("Roboto20", { + size = 20, + weight = 600, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("Trebuchet18", { + size = 18, + weight = 500, + antialias = true, + shadow = false, + font = "Trebuchet MS", + extended = true, + }) + + surface.CreateFont("Trebuchet20", { + size = 20, + weight = 500, + antialias = true, + shadow = false, + font = "Trebuchet MS", + extended = true, + }) + + surface.CreateFont("Trebuchet24", { + size = 24, + weight = 500, + antialias = true, + shadow = false, + font = "Trebuchet MS", + extended = true, + }) + + surface.CreateFont("Trebuchet48", { + size = 48, + weight = 500, + antialias = true, + shadow = false, + font = "Trebuchet MS", + extended = true, + }) + + surface.CreateFont("TabLarge", { + size = 18, + weight = 700, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("UiBold", { + size = 16, + weight = 800, + antialias = true, + shadow = false, + font = "Verdana", + extended = true, + }) + + surface.CreateFont("HUDNumber5", { + size = 30, + weight = 800, + antialias = true, + shadow = false, + font = "Verdana", + extended = true, + }) + + surface.CreateFont("ScoreboardHeader", { + size = 32, + weight = 500, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("ScoreboardSubtitle", { + size = 22, + weight = 500, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("ScoreboardPlayerName", { + size = 19, + weight = 500, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("ScoreboardPlayerName2", { + size = 15, + weight = 500, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("ScoreboardPlayerNameBig", { + size = 22, + weight = 500, + antialias = true, + shadow = false, + font = "Roboto", + extended = true, + }) + + surface.CreateFont("AckBarWriting", { + size = 20, + weight = 500, + antialias = true, + shadow = false, + font = "Akbar", + extended = true, + }) + + surface.CreateFont("DarkRP_tipjar", { + size = 100, + weight = 500, + antialias = true, + shadow = true, + font = "Verdana", + extended = true, + }) +end +loadFonts() \ No newline at end of file diff --git a/gamemodes/darkrp/gamemode/modules/base/cl_gamemode_functions.lua b/gamemodes/darkrp/gamemode/modules/base/cl_gamemode_functions.lua new file mode 100644 index 0000000..2031af0 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/cl_gamemode_functions.lua @@ -0,0 +1,84 @@ +local GUIToggled = false +local mouseX, mouseY = ScrW() / 2, ScrH() / 2 +function GM:ShowSpare1() + local jobTable = LocalPlayer():getJobTable() + + -- We need to check for the existance of jobTable here, because in very rare edge cases, the player's team isn't set, when the getJobTable-function is called here. + if jobTable and jobTable.ShowSpare1 then + return jobTable.ShowSpare1(LocalPlayer()) + end + + GUIToggled = not GUIToggled + + if GUIToggled then + gui.SetMousePos(mouseX, mouseY) + else + mouseX, mouseY = gui.MousePos() + end + gui.EnableScreenClicker(GUIToggled) +end + +function GM:ShowSpare2() + local jobTable = LocalPlayer():getJobTable() + + -- We need to check for the existance of jobTable here, because in very rare edge cases, the player's team isn't set, when the getJobTable-function is called here. + if jobTable and jobTable.ShowSpare2 then + return jobTable.ShowSpare2(LocalPlayer()) + end + + -- DarkRP.toggleF4Menu() +end + +function GM:PlayerStartVoice(ply) + if ply == LocalPlayer() then + ply.DRPIsTalking = true + return -- Not the original rectangle for yourself! ugh! + end + self.Sandbox.PlayerStartVoice(self, ply) +end + +function GM:PlayerEndVoice(ply) + if ply == LocalPlayer() then + ply.DRPIsTalking = false + return + end + + self.Sandbox.PlayerEndVoice(self, ply) +end + +function GM:OnPlayerChat() +end + +local FKeyBinds = { + ["gm_showhelp"] = "ShowHelp", + ["gm_showteam"] = "ShowTeam", + ["gm_showspare1"] = "ShowSpare1", + ["gm_showspare2"] = "ShowSpare2" +} + +function GM:PlayerBindPress(ply, bind, pressed) + self.Sandbox.PlayerBindPress(self, ply, bind, pressed) + + local bnd = string.match(string.lower(bind), "gm_[a-z]+[12]?") + if bnd and FKeyBinds[bnd] then + hook.Call(FKeyBinds[bnd], GAMEMODE) + end + + if not self.Config.deadvoice and not ply:Alive() and string.find(string.lower(bind), "voicerecord") then return true end +end + +function GM:InitPostEntity() + hook.Call("teamChanged", GAMEMODE, GAMEMODE.DefaultTeam, GAMEMODE.DefaultTeam) +end + +function GM:teamChanged(before, after) +end + +local function OnChangedTeam(um) + local oldTeam, newTeam = um:ReadShort(), um:ReadShort() + hook.Call("teamChanged", GAMEMODE, oldTeam, newTeam) -- backwards compatibility + hook.Call("OnPlayerChangedTeam", GAMEMODE, LocalPlayer(), oldTeam, newTeam) +end +usermessage.Hook("OnChangedTeam", OnChangedTeam) + +timer.Simple(0, function() GAMEMODE.ShowTeam = DarkRP.openKeysMenu end) diff --git a/gamemodes/darkrp/gamemode/modules/base/cl_interface.lua b/gamemodes/darkrp/gamemode/modules/base/cl_interface.lua new file mode 100644 index 0000000..1afe02e --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/cl_interface.lua @@ -0,0 +1,131 @@ +DarkRP.PLAYER.isInRoom = DarkRP.stub{ + name = "isInRoom", + description = "Whether the player is in the same room as the LocalPlayer.", + parameters = {}, + returns = { + { + name = "inRoom", + description = "Whether the player is in the same room.", + type = "boolean" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.deLocalise = DarkRP.stub{ + name = "deLocalise", + description = "Makes sure the string will not be localised when drawn or printed.", + parameters = { + { + name = "text", + description = "The text to delocalise.", + type = "string", + optional = false + } + }, + returns = { + { + name = "text", + description = "The delocalised text.", + type = "string" + } + }, + metatable = DarkRP +} + +DarkRP.textWrap = DarkRP.stub{ + name = "textWrap", + description = "Wrap a text around when reaching a certain width.", + parameters = { + { + name = "text", + description = "The text to wrap.", + type = "string", + optional = false + }, + { + name = "font", + description = "The font of the text.", + type = "string", + optional = false + }, + { + name = "width", + description = "The maximum width in pixels.", + type = "number", + optional = false + } + }, + returns = { + { + name = "text", + description = "The wrapped string.", + type = "string" + } + }, + metatable = DarkRP +} + +DarkRP.setPreferredJobModel = DarkRP.stub{ + name = "setPreferredJobModel", + description = "Set the model preferred by the player (if the job allows multiple models).", + parameters = { + { + name = "teamNr", + description = "The team number of the job.", + type = "number", + optional = false + }, + { + name = "model", + description = "The preferred model for the job.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.getPreferredJobModel = DarkRP.stub{ + name = "getPreferredJobModel", + description = "Get the model preferred by the player (if the job allows multiple models).", + parameters = { + { + name = "teamNr", + description = "The team number of the job.", + type = "number", + optional = false + } + }, + returns = { + { + name = "model", + description = "The preferred model for the job.", + type = "string" + } + }, + metatable = DarkRP +} + +DarkRP.hookStub{ + name = "teamChanged", + description = "When your team is changed.", + deprecated = "Use the OnPlayerChangedTeam hook instead.", + parameters = { + { + name = "before", + description = "The team before the change.", + type = "number" + }, + { + name = "after", + description = "The team after the change.", + type = "number" + } + }, + returns = { + + } +} diff --git a/gamemodes/darkrp/gamemode/modules/base/cl_jobmodels.lua b/gamemodes/darkrp/gamemode/modules/base/cl_jobmodels.lua new file mode 100644 index 0000000..bbca144 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/cl_jobmodels.lua @@ -0,0 +1,100 @@ +-- Create a table for the preferred playermodels +-- +-- Note: in DarkRP before 2024-09, there was a different table called +-- `darkp_playermodels` (note the misspelling of "darkp"). This table was +-- missing the server column, meaning that preferred job models would persist +-- across multiple servers. To make preferred job models store per server, this +-- new table (without the spelling mistake) was created. +-- +-- See the original issue to create the player model preference feature: +-- https://github.com/FPtje/DarkRP/issues/979 and the subsequent refactor at +-- https://github.com/FPtje/DarkRP/pull/3266 +sql.Query([[CREATE TABLE IF NOT EXISTS darkrp_playermodels( + server TEXT NOT NULL, + jobcmd TEXT NOT NULL, + model TEXT NOT NULL, + PRIMARY KEY (server, jobcmd) +);]]) + + +local preferredModels = {} + + +--[[--------------------------------------------------------------------------- +Interface functions +---------------------------------------------------------------------------]] +function DarkRP.setPreferredJobModel(teamNr, model) + local job = RPExtraTeams[teamNr] + if not job then return end + preferredModels[job.command] = model + sql.Query(string.format([[REPLACE INTO darkrp_playermodels(server, jobcmd, model) VALUES(%s, %s, %s);]], sql.SQLStr(game.GetIPAddress()), sql.SQLStr(job.command), sql.SQLStr(model))) + + net.Start("DarkRP_preferredjobmodel") + net.WriteUInt(teamNr, 8) + net.WriteString(model) + net.SendToServer() +end + +function DarkRP.getPreferredJobModel(teamNr) + local job = RPExtraTeams[teamNr] + if not job then return end + return preferredModels[job.command] +end + +--[[--------------------------------------------------------------------------- +Load the preferred models +---------------------------------------------------------------------------]] +local function sendModels() + net.Start("DarkRP_preferredjobmodels") + for _, job in pairs(RPExtraTeams) do + if not preferredModels[job.command] then net.WriteBit(false) continue end + + net.WriteBit(true) + net.WriteString(preferredModels[job.command]) + end + net.SendToServer() +end + +local function jobHasModel(job, model) + return istable(job.model) and table.HasValue(job.model, model) or job.model == model +end + +local function setPreferredModels(models) + for _, v in ipairs(models) do + local job = DarkRP.getJobByCommand(v.jobcmd) + if job == nil or not jobHasModel(job, v.model) then continue end + + preferredModels[v.jobcmd] = v.model + end +end + +-- The old table, darkp_playermodels, acts as a global mapping of preferred +-- models for jobs. +local function setModelsFromOldTable() + local oldTableExists = tobool(sql.QueryValue([[SELECT 1 FROM sqlite_master WHERE type='table' AND name='darkp_playermodels']])) + if not oldTableExists then return end + + local models = sql.Query([[SELECT jobcmd, model FROM darkp_playermodels;]]) + + if not models then return end + setPreferredModels(models) +end + +-- The newer table is server specific. +local function setModelsFromNewTable() + local models = sql.Query(string.format([[SELECT jobcmd, model FROM darkrp_playermodels WHERE server = %s;]], sql.SQLStr(game.GetIPAddress()))) + + if not models then return end + setPreferredModels(models) +end + +timer.Simple(0, function() + -- Run after the jobs have loaded, to make sure the jobs can be looked up. + + -- Set models from the old table, before overriding them with data from the + -- new table. That way, server specific preferences always have precedence. + setModelsFromOldTable() + setModelsFromNewTable() + + sendModels() +end) diff --git a/gamemodes/darkrp/gamemode/modules/base/cl_util.lua b/gamemodes/darkrp/gamemode/modules/base/cl_util.lua new file mode 100644 index 0000000..1d095db --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/cl_util.lua @@ -0,0 +1,107 @@ +local plyMeta = FindMetaTable("Player") + +--[[--------------------------------------------------------------------------- +Show a black screen +---------------------------------------------------------------------------]] +local function blackScreen(um) + local toggle = um:ReadBool() + if toggle then + local black = color_black + local w, h = ScrW(), ScrH() + hook.Add("HUDPaintBackground", "BlackScreen", function() + surface.SetDrawColor(black) + surface.DrawRect(0, 0, w, h) + end) + else + hook.Remove("HUDPaintBackground", "BlackScreen") + end +end +usermessage.Hook("blackScreen", blackScreen) + +--[[--------------------------------------------------------------------------- +Wrap strings to not become wider than the given amount of pixels +---------------------------------------------------------------------------]] +local function charWrap(text, remainingWidth, maxWidth) + local totalWidth = 0 + + text = text:gsub(".", function(char) + totalWidth = totalWidth + surface.GetTextSize(char) + + -- Wrap around when the max width is reached + if totalWidth >= remainingWidth then + -- totalWidth needs to include the character width because it's inserted in a new line + totalWidth = surface.GetTextSize(char) + remainingWidth = maxWidth + return "\n" .. char + end + + return char + end) + + return text, totalWidth +end + +function DarkRP.textWrap(text, font, maxWidth) + local totalWidth = 0 + + surface.SetFont(font) + + local spaceWidth = surface.GetTextSize(' ') + text = text:gsub("(%s?[%S]+)", function(word) + local char = string.sub(word, 1, 1) + if char == "\n" or char == "\t" then + totalWidth = 0 + end + + local wordlen = surface.GetTextSize(word) + totalWidth = totalWidth + wordlen + + -- Wrap around when the max width is reached + if wordlen >= maxWidth then -- Split the word if the word is too big + local splitWord, splitPoint = charWrap(word, maxWidth - (totalWidth - wordlen), maxWidth) + totalWidth = splitPoint + return splitWord + elseif totalWidth < maxWidth then + return word + end + + -- Split before the word + if char == ' ' then + totalWidth = wordlen - spaceWidth + return '\n' .. string.sub(word, 2) + end + + totalWidth = wordlen + return '\n' .. word + end) + + return text +end + +--[[--------------------------------------------------------------------------- +Decides whether a given player is in the same room as the local player +note: uses a heuristic +---------------------------------------------------------------------------]] +function plyMeta:isInRoom() + local tracedata = {} + tracedata.start = LocalPlayer():GetShootPos() + tracedata.endpos = self:GetShootPos() + local trace = util.TraceLine(tracedata) + + return not trace.HitWorld +end + +--[[--------------------------------------------------------------------------- +Key name to key int mapping +---------------------------------------------------------------------------]] +local keyNames +function input.KeyNameToNumber(str) + if not keyNames then + keyNames = {} + for i = 1, 107, 1 do + keyNames[input.GetKeyName(i)] = i + end + end + + return keyNames[str] +end diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_checkitems.lua b/gamemodes/darkrp/gamemode/modules/base/sh_checkitems.lua new file mode 100644 index 0000000..d717a37 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_checkitems.lua @@ -0,0 +1,628 @@ +--[[ +The base elements are shared by every custom item +]] +local baseSchema = tc.checkTable{ + buttonColor = + tc.addHint( + tc.optional(tc.tableOf(isnumber)), + "The buttonColor must be a Color value." + ), + + category = + tc.addHint( + tc.optional(isstring), + "The category must be the name of an existing category!" + ), + + customCheck = + tc.addHint( + tc.optional(isfunction), + "The customCheck must be a function." + ), + + CustomCheckFailMsg = + tc.addHint( + tc.optional(isstring, isfunction), + "The CustomCheckFailMsg must be either a string or a function." + ), + + sortOrder = + tc.addHint( + tc.optional(isnumber), + "The sortOrder must be a number." + ), + + label = + tc.addHint( + tc.optional(isstring), + "The label must be a valid string." + ), +} + +--[[ +Properties shared by anything buyable +]] +local buyableSchema = fn.FAnd{baseSchema, tc.checkTable{ + allowed = + tc.addHint( + tc.optional(tc.tableOf(isnumber), isnumber), + "The allowed field must be either an existing team or a table of existing teams.", + {"Is there a job here that doesn't exist (anymore)?"} + ), + + getPrice = + tc.addHint( + tc.optional(isfunction), + "The getPrice must be a function." + ), + + model = + tc.addHint( + isstring, + "The model must be valid." + ), + + price = + tc.addHint( + function(v, tbl) return isnumber(v) or isfunction(tbl.getPrice) end, + "The price must be an existing number or (for advanced users) the getPrice field must be a function." + ), + + spawn = + tc.addHint( + tc.optional(isfunction), + "The spawn must be a function." + ), + allowPurchaseWhileDead = + tc.addHint( + tc.default(false), + "The allowPurchaseWhileDead must be either true or false" + ) +}} + +-- The command of an entity must be unique +local uniqueEntity = function(cmd, tbl) + for _, v in pairs(DarkRPEntities) do + if v.cmd ~= cmd then continue end + + return + false, + "This entity does not have a unique command.", + { + "There must be some other entity that has the same thing for 'cmd'.", + "Fix this by changing the 'cmd' field of your entity to something else." + } + end + + return true +end + +-- The command of a job must be unique +local uniqueJob = function(v, tbl) + local job = DarkRP.getJobByCommand(v) + + if not job then return true end + + return + false, + "This job does not have a unique command.", + { + "There must be some other job that has the same command.", + "Fix this by changing the 'command' of your job to something else." + } +end + +--[[ +Validate jobs +]] +DarkRP.validateJob = fn.FAnd{baseSchema, tc.checkTable{ + name = + tc.addHint( + isstring, + "The name must be a valid string." + ), + + color = + tc.addHint( + tc.tableOf(isnumber), + "The color must be a Color value.", + {"Color values look like this: Color(r, g, b, a), where r, g, b and a are numbers between 0 and 255."} + ), + + model = + tc.addHint( + fn.FOr{isstring, tc.nonEmpty(tc.tableOf(isstring))}, + "The model must either be a table of correct model strings or a single correct model string.", + { + "This error could happens when the model does not exist on the server.", + "Are you sure the model path is right?", + "Is the model from an addon that is not properly installed?" + } + ), + + description = + tc.addHint( + isstring, + "The description must be a string." + ), + + weapons = + tc.addHint( + tc.optional(tc.tableOf(isstring)), + "The weapons must be a valid table of strings.", + {"Example: weapons = {\"med_kit\", \"weapon_bugbait\"},"} + ), + + command = + fn.FAnd + { + tc.addHint( + isstring, + "The command must be a string." + ), + uniqueJob + }, + + max = + tc.addHint( + fn.FAnd{isnumber, fp{fn.Lte, 0}}, + "The max must be a number greater than or equal to zero.", + { + "Zero means infinite.", + "A decimal between 0 and 1 is seen as a percentage." + } + ), + + salary = + tc.addHint( + fn.FAnd{isnumber, fp{fn.Lte, 0}}, + "The salary must be a number and it must be greater than zero." + ), + + admin = + tc.default(0, + tc.addHint( + fn.FAnd{isnumber, fp{fn.Lte, 0}, fp{fn.Gte, 2}}, + "The admin value must be a number and it must be greater than or equal to zero and smaller than three." + ) + ), + + vote = + tc.addHint( + tc.optional(isbool), + "The vote must be either true or false." + ), + + ammo = + tc.addHint( + tc.optional(tc.tableOf(isnumber)), + "The ammo must be a table containing numbers.", + {"See example on https://darkrp.miraheze.org/wiki/DarkRP:CustomJobFields"} + ), + + hasLicense = + tc.addHint( + tc.optional(isbool), + "The hasLicense must be either true or false." + ), + + NeedToChangeFrom = + tc.addHint( + tc.optional(tc.tableOf(isnumber), isnumber), + "The NeedToChangeFrom must be either an existing team or a table of existing teams", + {"Is there a job here that doesn't exist (anymore)?"} + ), + + modelScale = + tc.addHint( + tc.optional(isnumber), + "The modelScale must be a number." + ), + + maxpocket = + tc.addHint( + tc.optional(isnumber), + "The maxPocket must be a number." + ), + + maps = + tc.addHint( + tc.optional(tc.tableOf(isstring)), + "The maps value must be a table of valid map names." + ), + + candemote = + tc.default(true, + tc.addHint( + isbool, + "The candemote value must be either true or false." + ) + ), + + mayor = + tc.addHint( + tc.optional(isbool), + "The mayor value must be either true or false." + ), + + chief = + tc.addHint( + tc.optional(isbool), + "The chief value must be either true or false." + ), + + medic = + tc.addHint( + tc.optional(isbool), + "The medic value must be either true or false." + ), + + cook = + tc.addHint( + tc.optional(isbool), + "The cook value must be either true or false." + ), + + hobo = + tc.addHint( + tc.optional(isbool), + "The hobo value must be either true or false." + ), + + playerClass = + tc.addHint( + tc.optional(isstring), + "The playerClass must be a valid string." + ), + + CanPlayerSuicide = + tc.addHint( + tc.optional(isfunction), + "The CanPlayerSuicide must be a function." + ), + + PlayerCanPickupWeapon = + tc.addHint( + tc.optional(isfunction), + "The PlayerCanPickupWeapon must be a function." + ), + + PlayerDeath = + tc.addHint( + tc.optional(isfunction), + "The PlayerDeath must be a function." + ), + + PlayerLoadout = + tc.addHint( + tc.optional(isfunction), + "The PlayerLoadout must be a function." + ), + + PlayerSelectSpawn = + tc.addHint( + tc.optional(isfunction), + "The PlayerSelectSpawn must be a function." + ), + + PlayerSetModel = + tc.addHint( + tc.optional(isfunction), + "The PlayerSetModel must be a function." + ), + + PlayerSpawn = + tc.addHint( + tc.optional(isfunction), + "The PlayerSpawn must be a function." + ), + + PlayerSpawnProp = + tc.addHint( + tc.optional(isfunction), + "The PlayerSpawnProp must be a function." + ), + + RequiresVote = + tc.addHint( + tc.optional(isfunction), + "The RequiresVote must be a function." + ), + + ShowSpare1 = + tc.addHint( + tc.optional(isfunction), + "The ShowSpare1 must be a function." + ), + + ShowSpare2 = + tc.addHint( + tc.optional(isfunction), + "The ShowSpare2 must be a function." + ), + + canStartVote = + tc.addHint( + tc.optional(isfunction), + "The canStartVote must be a function." + ), + + canStartVoteReason = + tc.addHint( + tc.optional(isstring, isfunction), + "The canStartVoteReason must be either a string or a function." + ), +}} + +--[[ +Validate shipments +]] +DarkRP.validateShipment = fn.FAnd{buyableSchema, tc.checkTable{ + name = + tc.addHint( + isstring, + "The name must be a valid string." + ), + + entity = + tc.addHint( + isstring, "The entity of the shipment must be a string." + ), + + amount = + tc.addHint( + fn.FAnd{isnumber, fp{fn.Lte, 0}}, "The amount must be a number and it must be greater than zero." + ), + + separate = + tc.addHint( + tc.optional(isbool), "the separate field must be either true or false." + ), + + pricesep = + tc.addHint( + function(v, tbl) return not tbl.separate or isnumber(v) and v >= 0 end, + "The pricesep must be a number and it must be greater than or equal to zero." + ), + + noship = + tc.addHint( + tc.optional(isbool), + "The noship must be either true or false." + ), + + shipmodel = + tc.addHint( + tc.optional(isstring), + "The shipmodel must be a valid model." + ), + + weight = + tc.addHint( + tc.optional(isnumber), + "The weight must be a number." + ), + + spareammo = + tc.addHint( + tc.optional(isnumber), + "The spareammo must be a number." + ), + + clip1 = + tc.addHint( + tc.optional(isnumber), + "The clip1 must be a number." + ), + + clip2 = + tc.addHint( + tc.optional(isnumber), + "The clip2 must be a number." + ), + + shipmentClass = + tc.addHint( + tc.optional(isstring), + "The shipmentClass must be a string." + ), + + onBought = + tc.addHint( + tc.optional(isfunction), + "The onBought must be a function." + ), + +}} + +--[[ +Validate vehicles +]] +DarkRP.validateVehicle = fn.FAnd{buyableSchema, tc.checkTable{ + name = + tc.addHint( + isstring, + "The name of the vehicle must be a string." + ), + + distance = + tc.addHint( + tc.optional(isnumber), + "The distance must be a number." + ), + + angle = + tc.addHint( + tc.optional(isangle), + "The distance must be a valid Angle." + ), +}} + +--[[ +Validate Entities +]] +DarkRP.validateEntity = fn.FAnd{buyableSchema, tc.checkTable{ + ent = + tc.addHint( + isstring, + "The ent field must be a string." + ), + + max = + tc.addHint( + function(v, tbl) return isnumber(v) or isfunction(tbl.getMax) end, + "The max must be an existing number or (for advanced users) the getMax field must be a function." + ), + + cmd = + fn.FAnd + { + tc.addHint(isstring, "The cmd must be a valid string."), + uniqueEntity + }, + + name = + tc.addHint( + isstring, + "The name must be a valid string." + ), + + allowTools = + tc.default(false, + tc.addHint( + tc.optional(isbool), + "The allowTools must be either true or false." + ) + ), + + delay = + tc.addHint( + tc.optional(isnumber), + "The delay must be a number." + ), +}} + + +-- Checks whether a team already has an agenda assigned. +-- Jobs cannot have multiple agendas. + +local overlappingAgendaCheck = function(t, tbl) + local agenda = DarkRP.getAgendas()[t] + + -- Team being -1 means the job is disabled + if agenda == nil or t == -1 then return true end + + local teamName = team.GetName(t) + local err = "At least one job has multiple agendas assigned to them" + local hints = { + string.format([[The problem lies with the job called "%s"]], teamName), + string.format([[It is assigned to agendas "%s" and "%s"]], agenda.Title or "unknown", tbl.Title or "unknown"), + [[A job can only have ONE agenda. Otherwise things would become confusing, since only ONE agenda is always drawn on the screen.]] + } + + if agenda.Title == tbl.Title then + table.insert(hints, "The titles of the two agendas are the same. It looks like perhaps you've made the same agenda more than once.") + table.insert(hints, "Removing one of them should get rid of this error.") + end + + return false, err, hints +end + +--[[ +Validate Agendas +]] +local managerNumberCheck = tc.addHint( + isnumber, + "The Manager must either be a single team or a non-empty table of existing teams.", + {"Is there a job here that doesn't exist (anymore)?"} +) + +DarkRP.validateAgenda = tc.checkTable{ + Title = + tc.addHint( + isstring, + "The title must be a string." + ), + + -- Custom function to ensure the right error message is thrown + Manager = function(manager, tbl) + -- Check whether the manager is an existing team + -- that does not already have an agenda assigned + if isnumber(manager) then + return fn.FAnd{overlappingAgendaCheck}(manager, tbl) + + -- Check whether the manager is a table of existing teams + -- and that none of the teams already have agendas assigned + elseif istable(manager) then + return tc.nonEmpty( + tc.tableOf( + fn.FAnd{managerNumberCheck, overlappingAgendaCheck} + ) + )(manager, tbl) + end + + return managerNumberCheck(manager, tbl) + end, + Listeners = + tc.default({}, -- Default to empty table + -- Checks for a table of valid teams that do not already have an + -- agenda assigned + fn.FAnd{ + tc.addHint( + tc.tableOf(isnumber), + "The Listeners must be a table of existing teams.", + { + "Is there a job here that doesn't exist (anymore)?", + "Are you trying to have multiple manager jobs in this agenda? In that case you must put the list of manager jobs in curly braces.", + [[Like so: DarkRP.createAgenda("Some agenda", {TEAM_MANAGER1, TEAM_MANAGER2}, {TEAM_LISTENER1, TEAM_LISTENER2})]] + } + ), + tc.tableOf(overlappingAgendaCheck) + } + ) +} + +--[[ +Validate Categories +]] +DarkRP.validateCategory = tc.checkTable{ + name = + tc.addHint( + isstring, + "The name must be a string." + ), + + categorises = + tc.addHint( + tc.oneOf{"jobs", "entities", "shipments", "weapons", "vehicles", "ammo"}, + [[The categorises must be one of "jobs", "entities", "shipments", "weapons", "vehicles", "ammo"]], + { + "Mind that this is case sensitive.", + "Also mind the quotation marks." + } + ), + + startExpanded = + tc.addHint( + isbool, + "The startExpanded must be either true or false." + ), + + color = + tc.addHint( + tc.tableOf(isnumber), + "The color must be a Color value." + ), + + canSee = + tc.addHint( + tc.optional(isfunction), + "The canSee must be a function." + ), + + sortOrder = + tc.addHint( + tc.optional(isnumber), + "The sortOrder must be a number." + ), +} diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_commands.lua b/gamemodes/darkrp/gamemode/modules/base/sh_commands.lua new file mode 100644 index 0000000..e36688e --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_commands.lua @@ -0,0 +1,71 @@ +DarkRP.declareChatCommand{ + command = "rpname", + description = "Set your RP name", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "name", + description = "Set your RP name", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "nick", + description = "Set your RP name", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "buy", + description = "Buy a pistol", + delay = 1.5, + condition = fn.FAnd { + fn.Compose{fn.Curry(fn.GetValue, 2)("enablebuypistol"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode}, + fn.Compose{fn.Not, fn.Curry(fn.GetValue, 2)("noguns"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode} + } +} + +DarkRP.declareChatCommand{ + command = "buyshipment", + description = "Buy a shipment", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "buyvehicle", + description = "Buy a vehicle", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "buyammo", + description = "Purchase ammo", + delay = 1.5, + condition = fn.Compose{fn.Not, fn.Curry(fn.GetValue, 2)("noguns"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode} +} + +DarkRP.declareChatCommand{ + command = "price", + description = "Set the price of the microwave or gunlab you're looking at", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "setprice", + description = "Set the price of the microwave or gunlab you're looking at", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "forcerpname", + description = "Forcefully change a player's RP name", + delay = 0.5, + tableArgs = true +} + +DarkRP.declareChatCommand{ + command = "freerpname", + description = "Remove a RP name from the database so a player can use it", + delay = 1.5 +} diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_createitems.lua b/gamemodes/darkrp/gamemode/modules/base/sh_createitems.lua new file mode 100644 index 0000000..ebd7ebe --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_createitems.lua @@ -0,0 +1,922 @@ +local plyMeta = FindMetaTable("Player") + +----------------------------------------------------------- +-- Job commands -- +----------------------------------------------------------- +local function declareTeamCommands(CTeam) + local k = 0 + for num, v in pairs(RPExtraTeams) do + if v.command == CTeam.command then + k = num + end + end + + local chatcommandCondition = function(ply) + local plyTeam = ply:Team() + + if plyTeam == k then return false end + if CTeam.admin == 1 and not ply:IsAdmin() or CTeam.admin == 2 and not ply:IsSuperAdmin() then return false end + if isnumber(CTeam.NeedToChangeFrom) and plyTeam ~= CTeam.NeedToChangeFrom then return false end + if istable(CTeam.NeedToChangeFrom) and not table.HasValue(CTeam.NeedToChangeFrom, plyTeam) then return false end + if CTeam.customCheck and CTeam.customCheck(ply) == false then return false end + if ply:isArrested() then return false end + local numPlayers = team.NumPlayers(k) + if CTeam.max ~= 0 and ((CTeam.max % 1 == 0 and numPlayers >= CTeam.max) or (CTeam.max % 1 ~= 0 and (numPlayers + 1) / player.GetCount() > CTeam.max)) then return false end + if ply.LastJob and 10 - (CurTime() - ply.LastJob) >= 0 then return false end + if ply.LastVoteCop and CurTime() - ply.LastVoteCop < 80 then return false end + + return true + end + + if CTeam.vote or CTeam.RequiresVote then + DarkRP.declareChatCommand{ + command = "vote" .. CTeam.command, + description = "Vote to become " .. CTeam.name .. ".", + delay = 1.5, + condition = + function(ply) + if CTeam.RequiresVote and not CTeam.RequiresVote(ply, k) then return false end + if CTeam.canStartVote and not CTeam.canStartVote(ply) then return false end + + return chatcommandCondition(ply) + end + } + + DarkRP.declareChatCommand{ + command = CTeam.command, + description = "Become " .. CTeam.name .. " and skip the vote.", + delay = 1.5, + condition = + function(ply) + local requiresVote = CTeam.RequiresVote and CTeam.RequiresVote(ply, k) + + if requiresVote then return false end + if requiresVote ~= false and CTeam.admin == 0 and not ply:IsAdmin() or CTeam.admin == 1 and not ply:IsSuperAdmin() then return false end + if CTeam.canStartVote and not CTeam.canStartVote(ply) then return false end + + return chatcommandCondition(ply) + end + } + else + DarkRP.declareChatCommand{ + command = CTeam.command, + description = "Become " .. CTeam.name .. ".", + delay = 1.5, + condition = chatcommandCondition + } + end +end + +local function addTeamCommands(CTeam, max) + if CLIENT then return end + + local k = 0 + for num, v in pairs(RPExtraTeams) do + if v.command == CTeam.command then + k = num + end + end + + if CTeam.vote or CTeam.RequiresVote then + DarkRP.defineChatCommand("vote" .. CTeam.command, function(ply) + if CTeam.RequiresVote and not CTeam.RequiresVote(ply, k) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("job_doesnt_require_vote_currently")) + + return "" + end + + if CTeam.canStartVote and not CTeam.canStartVote(ply) then + local reason = isfunction(CTeam.canStartVoteReason) and CTeam.canStartVoteReason(ply, CTeam) or CTeam.canStartVoteReason or "" + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/vote" .. CTeam.command, reason)) + + return "" + end + + if CTeam.admin == 1 and not ply:IsAdmin() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", "/" .. "vote" .. CTeam.command)) + + return "" + elseif CTeam.admin > 1 and not ply:IsSuperAdmin() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_sadmin", "/" .. "vote" .. CTeam.command)) + + return "" + end + + if isnumber(CTeam.NeedToChangeFrom) and ply:Team() ~= CTeam.NeedToChangeFrom then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_be_before", team.GetName(CTeam.NeedToChangeFrom), CTeam.name)) + + return "" + elseif istable(CTeam.NeedToChangeFrom) and not table.HasValue(CTeam.NeedToChangeFrom, ply:Team()) then + local teamnames = "" + + for _, b in pairs(CTeam.NeedToChangeFrom) do + teamnames = teamnames .. " or " .. team.GetName(b) + end + + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_be_before", string.sub(teamnames, 5), CTeam.name)) + + return "" + end + + if CTeam.customCheck and not CTeam.customCheck(ply) then + local message = isfunction(CTeam.CustomCheckFailMsg) and CTeam.CustomCheckFailMsg(ply, CTeam) or CTeam.CustomCheckFailMsg or DarkRP.getPhrase("unable", team.GetName(t), "") + DarkRP.notify(ply, 1, 4, message) + + return "" + end + + local allowed, time = ply:changeAllowed(k) + if not allowed then + local notif = time and DarkRP.getPhrase("have_to_wait", math.ceil(time), "/job, " .. DarkRP.getPhrase("banned_or_demoted")) or DarkRP.getPhrase("unable", team.GetName(k), DarkRP.getPhrase("banned_or_demoted")) + DarkRP.notify(ply, 1, 4, notif) + + return "" + end + + if ply:Team() == k then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", CTeam.command, "")) + + return "" + end + + local numPlayers = team.NumPlayers(k) + if max ~= 0 and ((max % 1 == 0 and numPlayers >= max) or (max % 1 ~= 0 and (numPlayers + 1) / player.GetCount() > max)) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("team_limit_reached", CTeam.name)) + + return "" + end + + if ply.LastJob and 10 - (CurTime() - ply.LastJob) >= 0 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(10 - (CurTime() - ply.LastJob)), GAMEMODE.Config.chatCommandPrefix .. CTeam.command)) + + return "" + end + + ply.LastVoteCop = ply.LastVoteCop or -80 + + if CurTime() - ply.LastVoteCop < 80 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(80 - (CurTime() - ply:GetTable().LastVoteCop)), GAMEMODE.Config.chatCommandPrefix .. CTeam.command)) + + return "" + end + + DarkRP.createVote(DarkRP.getPhrase("wants_to_be", ply:Nick(), CTeam.name), "job", ply, 20, function(vote, choice) + local target = vote.target + if not IsValid(target) then return end + + if choice >= 0 then + target:changeTeam(k) + else + DarkRP.notifyAll(1, 4, DarkRP.getPhrase("has_not_been_made_team", target:Nick(), CTeam.name)) + end + end, nil, nil, { + targetTeam = k + }) + + ply.LastVoteCop = CurTime() + + return "" + end) + + local function onJobCommand(ply, hasPriv) + if hasPriv then + ply:changeTeam(k) + return + end + + local a = CTeam.admin + if a > 0 and not ply:IsAdmin() + or a > 1 and not ply:IsSuperAdmin() + then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", CTeam.name)) + return + end + + if not CTeam.RequiresVote and + (a == 0 and not ply:IsAdmin() + or a == 1 and not ply:IsSuperAdmin() + or a == 2) + or CTeam.RequiresVote and CTeam.RequiresVote(ply, k) + then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_make_vote", CTeam.name)) + return + end + + ply:changeTeam(k) + end + DarkRP.defineChatCommand(CTeam.command, function(ply) + CAMI.PlayerHasAccess(ply, "DarkRP_GetJob_" .. CTeam.command, fp{onJobCommand, ply}) + + return "" + end) + else + DarkRP.defineChatCommand(CTeam.command, function(ply) + if CTeam.admin == 1 and not ply:IsAdmin() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", "/" .. CTeam.command)) + + return "" + end + + if CTeam.admin > 1 and not ply:IsSuperAdmin() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_sadmin", "/" .. CTeam.command)) + + return "" + end + + ply:changeTeam(k) + + return "" + end) + end + + concommand.Add("rp_" .. CTeam.command, function(ply, cmd, args) + if ply:EntIndex() ~= 0 and not ply:IsAdmin() then + ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_admin", cmd)) + return + end + + if CTeam.admin > 1 and not ply:IsSuperAdmin() and ply:EntIndex() ~= 0 then + ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_sadmin", cmd)) + return + end + + if CTeam.vote then + if CTeam.admin >= 1 and ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then + ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_sadmin", cmd)) + return + elseif CTeam.admin > 1 and ply:IsSuperAdmin() and ply:EntIndex() ~= 0 then + ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_to_make_vote", CTeam.name)) + return + end + end + + if not args or not args[1] then + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return + end + + local target = DarkRP.findPlayer(args[1]) + + if not target then + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("could_not_find", tostring(args[1]))) + return + end + + target:changeTeam(k, true) + local nick + if (ply:EntIndex() ~= 0) then + nick = ply:Nick() + else + nick = "Console" + end + DarkRP.notify(target, 0, 4, DarkRP.getPhrase("x_made_you_a_y", nick, CTeam.name)) + end) +end + +local function addEntityCommands(tblEnt) + DarkRP.declareChatCommand{ + command = tblEnt.cmd, + description = "Purchase a " .. tblEnt.name, + delay = tblEnt.delay or GAMEMODE.Config.EntitySpamTime, + condition = + function(ply) + if not tblEnt.allowPurchaseWhileDead and not ply:Alive() then return false end + if ply:isArrested() then return false end + if istable(tblEnt.allowed) and not table.HasValue(tblEnt.allowed, ply:Team()) then return false end + if not ply:canAfford(tblEnt.price) then return false end + if tblEnt.customCheck and tblEnt.customCheck(ply) == false then return false end + + return true + end + } + + if CLIENT then return end + + -- Default spawning function of an entity + -- used if tblEnt.spawn is not defined + local function defaultSpawn(ply, tr, tblE) + local ent = ents.Create(tblE.ent) + + if not ent:IsValid() then error("Entity '" .. tblE.ent .. "' does not exist or is not valid.") end + if ent.Setowning_ent then ent:Setowning_ent(ply) end + + ent:SetPos(tr.HitPos) + -- These must be set before :Spawn() + ent.SID = ply.SID + ent.allowed = tblE.allowed + ent.DarkRPItem = tblE + ent:Spawn() + ent:Activate() + + DarkRP.placeEntity(ent, tr, ply) + + local phys = ent:GetPhysicsObject() + if phys:IsValid() then phys:Wake() end + + return ent + end + + local function buythis(ply, args) + if ply:isArrested() then return "" end + if not tblEnt.allowPurchaseWhileDead and not ply:Alive() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", tblEnt.name))) + return "" + end + if istable(tblEnt.allowed) and not table.HasValue(tblEnt.allowed, ply:Team()) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("incorrect_job", tblEnt.name)) + return "" + end + + if tblEnt.customCheck and not tblEnt.customCheck(ply) then + local message = isfunction(tblEnt.CustomCheckFailMsg) and tblEnt.CustomCheckFailMsg(ply, tblEnt) or + tblEnt.CustomCheckFailMsg or + DarkRP.getPhrase("not_allowed_to_purchase") + DarkRP.notify(ply, 1, 4, message) + return "" + end + + if ply:customEntityLimitReached(tblEnt) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", tblEnt.name)) + return "" + end + + local canbuy, suppress, message, price = hook.Call("canBuyCustomEntity", nil, ply, tblEnt) + + local cost = price or tblEnt.getPrice and tblEnt.getPrice(ply, tblEnt.price) or tblEnt.price + + if not ply:canAfford(cost) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cant_afford", tblEnt.name)) + return "" + end + + if canbuy == false then + if not suppress and message then DarkRP.notify(ply, 1, 4, message) end + return "" + end + + ply:addMoney(-cost) + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + local ent = (tblEnt.spawn or defaultSpawn)(ply, tr, tblEnt) + ent.onlyremover = not tblEnt.allowTools + -- Repeat these properties to alleviate work in tblEnt.spawn: + ent.SID = ply.SID + ent.allowed = tblEnt.allowed + ent.DarkRPItem = tblEnt + + hook.Call("playerBoughtCustomEntity", nil, ply, tblEnt, ent, cost) + + if cost == 0 then + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_got_yourself", tblEnt.name)) + else + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", tblEnt.name, DarkRP.formatMoney(cost), "")) + end + + ply:addCustomEntity(tblEnt) + return "" + end + DarkRP.defineChatCommand(tblEnt.cmd, buythis) +end + +RPExtraTeams = {} +local jobByCmd = {} +DarkRP.getJobByCommand = function(cmd) + if not jobByCmd[cmd] then return nil, nil end + return RPExtraTeams[jobByCmd[cmd]], jobByCmd[cmd] +end +plyMeta.getJobTable = function(ply) + local tbl = RPExtraTeams[ply:Team()] + -- don't error when the player has not fully joined yet + if not tbl and (ply.DarkRPInitialised or ply.DarkRPDataRetrievalFailed) then + DarkRP.error( + string.format("There is a player with an invalid team!\n\nThe player's name is %s, their team number is \"%s\", which has the name \"%s\"", + ply:EntIndex() == 0 and "Console" or IsValid(ply) and ply:Nick() or "unknown", + ply:Team(), + team.GetName(ply:Team())), + 1, + { + "It is the server owner's responsibility to figure out why that player has no valid team.", + "This error is very likely to be caused by an earlier error. If you don't see any errors in your own console, look at the server console." + } + ) + end + return tbl +end + +function DarkRP.createJob(Name, colorOrTable, model, Description, Weapons, command, maximum_amount_of_this_class, Salary, admin, Vote, Haslicense, NeedToChangeFrom, CustomCheck) + local tableSyntaxUsed = not IsColor(colorOrTable) + + local CustomTeam = tableSyntaxUsed and colorOrTable or + {color = colorOrTable, model = model, description = Description, weapons = Weapons, command = command, + max = maximum_amount_of_this_class, salary = Salary, admin = admin or 0, vote = tobool(Vote), hasLicense = Haslicense, + NeedToChangeFrom = NeedToChangeFrom, customCheck = CustomCheck + } + CustomTeam.name = Name + CustomTeam.default = DarkRP.DARKRP_LOADING + + -- Disabled job + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["jobs"][CustomTeam.command] then return end + + local valid, err, hints = DarkRP.validateJob(CustomTeam) + if not valid then DarkRP.error(string.format("Corrupt team: %s!\n%s", CustomTeam.name or "", err), 2, hints) end + + if not (GM or GAMEMODE):CustomObjFitsMap(CustomTeam) then return end + + local jobCount = #RPExtraTeams + 1 + + CustomTeam.team = jobCount + + CustomTeam.salary = math.floor(CustomTeam.salary) + + CustomTeam.customCheck = CustomTeam.customCheck and fp{DarkRP.simplerrRun, CustomTeam.customCheck} + CustomTeam.CustomCheckFailMsg = isfunction(CustomTeam.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, CustomTeam.CustomCheckFailMsg} or CustomTeam.CustomCheckFailMsg + CustomTeam.CanPlayerSuicide = CustomTeam.CanPlayerSuicide and fp{DarkRP.simplerrRun, CustomTeam.CanPlayerSuicide} + CustomTeam.PlayerCanPickupWeapon = CustomTeam.PlayerCanPickupWeapon and fp{DarkRP.simplerrRun, CustomTeam.PlayerCanPickupWeapon} + CustomTeam.PlayerDeath = CustomTeam.PlayerDeath and fp{DarkRP.simplerrRun, CustomTeam.PlayerDeath} + CustomTeam.PlayerLoadout = CustomTeam.PlayerLoadout and fp{DarkRP.simplerrRun, CustomTeam.PlayerLoadout} + CustomTeam.PlayerSelectSpawn = CustomTeam.PlayerSelectSpawn and fp{DarkRP.simplerrRun, CustomTeam.PlayerSelectSpawn} + CustomTeam.PlayerSetModel = CustomTeam.PlayerSetModel and fp{DarkRP.simplerrRun, CustomTeam.PlayerSetModel} + CustomTeam.PlayerSpawn = CustomTeam.PlayerSpawn and fp{DarkRP.simplerrRun, CustomTeam.PlayerSpawn} + CustomTeam.PlayerSpawnProp = CustomTeam.PlayerSpawnProp and fp{DarkRP.simplerrRun, CustomTeam.PlayerSpawnProp} + CustomTeam.RequiresVote = CustomTeam.RequiresVote and fp{DarkRP.simplerrRun, CustomTeam.RequiresVote} + CustomTeam.ShowSpare1 = CustomTeam.ShowSpare1 and fp{DarkRP.simplerrRun, CustomTeam.ShowSpare1} + CustomTeam.ShowSpare2 = CustomTeam.ShowSpare2 and fp{DarkRP.simplerrRun, CustomTeam.ShowSpare2} + CustomTeam.canStartVote = CustomTeam.canStartVote and fp{DarkRP.simplerrRun, CustomTeam.canStartVote} + + jobByCmd[CustomTeam.command] = table.insert(RPExtraTeams, CustomTeam) + DarkRP.addToCategory(CustomTeam, "jobs", CustomTeam.category) + + team.SetUp(jobCount, Name, CustomTeam.color) + + timer.Simple(0, function() + declareTeamCommands(CustomTeam) + addTeamCommands(CustomTeam, CustomTeam.max) + end) + + -- Precache model here. Not right before the job change is done + if istable(CustomTeam.model) then + for _, v in pairs(CustomTeam.model) do util.PrecacheModel(v) end + else + util.PrecacheModel(CustomTeam.model) + end + return jobCount +end +AddExtraTeam = DarkRP.createJob + +local function removeCustomItem(tbl, category, hookName, reloadF4, i) + local item = tbl[i] + tbl[i] = nil + if category then DarkRP.removeFromCategory(item, category) end + if istable(item) and (item.command or item.cmd) then DarkRP.removeChatCommand(item.command or item.cmd) end + hook.Run(hookName, i, item) + if CLIENT and reloadF4 and IsValid(DarkRP.getF4MenuPanel()) then DarkRP.getF4MenuPanel():Remove() end -- Rebuild entire F4 menu frame +end + +function DarkRP.removeJob(i) + local job = RPExtraTeams[i] + jobByCmd[job.command] = nil + + DarkRP.removeChatCommand("vote" .. job.command) + removeCustomItem(RPExtraTeams, "jobs", "onJobRemoved", true, i) +end + +RPExtraTeamDoors = {} +RPExtraTeamDoorIDs = {} +local maxTeamDoorID = 0 +function DarkRP.createEntityGroup(name, ...) + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["doorgroups"][name] then return end + RPExtraTeamDoors[name] = {...} + RPExtraTeamDoors[name].name = name + + maxTeamDoorID = maxTeamDoorID + 1 + RPExtraTeamDoorIDs[name] = maxTeamDoorID +end +AddDoorGroup = DarkRP.createEntityGroup + +DarkRP.removeEntityGroup = fp{removeCustomItem, RPExtraTeamDoors, nil, "onEntityGroupRemoved", false} + +CustomVehicles = {} +CustomShipments = {} +local shipByName = {} +DarkRP.getShipmentByName = function(name) + name = string.lower(name or "") + + if not shipByName[name] then return nil, nil end + return CustomShipments[shipByName[name]], shipByName[name] +end + +function DarkRP.createShipment(name, model, entity, price, Amount_of_guns_in_one_shipment, Sold_separately, price_separately, noshipment, classes, shipmodel, CustomCheck) + local tableSyntaxUsed = istable(model) + + price = tonumber(price) + local shipmentmodel = shipmodel or "models/Items/item_item_crate.mdl" + + local customShipment = tableSyntaxUsed and model or + {model = model, entity = entity, price = price, amount = Amount_of_guns_in_one_shipment, + seperate = Sold_separately, pricesep = price_separately, noship = noshipment, allowed = classes, + shipmodel = shipmentmodel, customCheck = CustomCheck, weight = 5} + + -- The pains of backwards compatibility when dealing with ancient spelling errors... + if customShipment.separate ~= nil then + customShipment.seperate = customShipment.separate + end + customShipment.separate = customShipment.seperate + + if customShipment.allowed == nil then + customShipment.allowed = {} + for k in pairs(team.GetAllTeams()) do + table.insert(customShipment.allowed, k) + end + end + + customShipment.name = name + customShipment.default = DarkRP.DARKRP_LOADING + customShipment.shipmodel = customShipment.shipmodel or shipmentmodel + + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["shipments"][customShipment.name] then return end + + local valid, err, hints = DarkRP.validateShipment(customShipment) + if not valid then DarkRP.error(string.format("Corrupt shipment: %s!\n%s", name or "", err), 2, hints) end + + customShipment.spawn = customShipment.spawn and fp{DarkRP.simplerrRun, customShipment.spawn} + customShipment.allowed = isnumber(customShipment.allowed) and {customShipment.allowed} or customShipment.allowed + customShipment.customCheck = customShipment.customCheck and fp{DarkRP.simplerrRun, customShipment.customCheck} + customShipment.CustomCheckFailMsg = isfunction(customShipment.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, customShipment.CustomCheckFailMsg} or customShipment.CustomCheckFailMsg + + if not customShipment.noship then DarkRP.addToCategory(customShipment, "shipments", customShipment.category) end + if customShipment.separate then DarkRP.addToCategory(customShipment, "weapons", customShipment.category) end + + shipByName[string.lower(name or "")] = table.insert(CustomShipments, customShipment) + util.PrecacheModel(customShipment.model) +end +AddCustomShipment = DarkRP.createShipment + +function DarkRP.removeShipment(i) + local ship = CustomShipments[i] + shipByName[ship.name] = nil + removeCustomItem(CustomShipments, "shipments", "onShipmentRemoved", true, i) +end + +function DarkRP.createVehicle(Name_of_vehicle, model, price, Jobs_that_can_buy_it, customcheck) + local vehicle = istable(Name_of_vehicle) and Name_of_vehicle or + {name = Name_of_vehicle, model = model, price = price, allowed = Jobs_that_can_buy_it, customCheck = customcheck} + + vehicle.default = DarkRP.DARKRP_LOADING + + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["vehicles"][vehicle.name] then return end + + local found = false + for k in pairs(DarkRP.getAvailableVehicles()) do + if string.lower(k) == string.lower(vehicle.name) then found = true break end + end + + local valid, err, hints = DarkRP.validateVehicle(vehicle) + if not valid then DarkRP.error(string.format("Corrupt vehicle: %s!\n%s", vehicle.name or "", err), 2, hints) end + + if not found then DarkRP.error("Vehicle invalid: " .. vehicle.name .. ". Unknown vehicle name.", 2) end + + vehicle.customCheck = vehicle.customCheck and fp{DarkRP.simplerrRun, vehicle.customCheck} + vehicle.CustomCheckFailMsg = isfunction(vehicle.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, vehicle.CustomCheckFailMsg} or vehicle.CustomCheckFailMsg + + table.insert(CustomVehicles, vehicle) + DarkRP.addToCategory(vehicle, "vehicles", vehicle.category) +end +AddCustomVehicle = DarkRP.createVehicle + +DarkRP.removeVehicle = fp{removeCustomItem, CustomVehicles, "vehicles", "onVehicleRemoved", true} + +--[[--------------------------------------------------------------------------- +Decides whether a custom job or shipmet or whatever can be used in a certain map +---------------------------------------------------------------------------]] +function GM:CustomObjFitsMap(obj) + if not obj or not obj.maps then return true end + + local map = string.lower(game.GetMap()) + for _, v in pairs(obj.maps) do + if string.lower(v) == map then return true end + end + return false +end + +DarkRPEntities = {} +function DarkRP.createEntity(name, entity, model, price, max, command, classes, CustomCheck) + local tableSyntaxUsed = istable(entity) + + local tblEnt = tableSyntaxUsed and entity or + {ent = entity, model = model, price = price, max = max, + cmd = command, allowed = classes, customCheck = CustomCheck} + tblEnt.name = name + tblEnt.default = DarkRP.DARKRP_LOADING + + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["entities"][tblEnt.name] then return end + + if isnumber(tblEnt.allowed) then + tblEnt.allowed = {tblEnt.allowed} + end + + local valid, err, hints = DarkRP.validateEntity(tblEnt) + if not valid then DarkRP.error(string.format("Corrupt entity: %s!\n%s", name or "", err), 2, hints) end + + tblEnt.customCheck = tblEnt.customCheck and fp{DarkRP.simplerrRun, tblEnt.customCheck} + tblEnt.CustomCheckFailMsg = isfunction(tblEnt.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, tblEnt.CustomCheckFailMsg} or tblEnt.CustomCheckFailMsg + tblEnt.getPrice = tblEnt.getPrice and fp{DarkRP.simplerrRun, tblEnt.getPrice} + tblEnt.getMax = tblEnt.getMax and fp{DarkRP.simplerrRun, tblEnt.getMax} + tblEnt.spawn = tblEnt.spawn and fp{DarkRP.simplerrRun, tblEnt.spawn} + + -- if SERVER and FPP then + -- FPP.AddDefaultBlocked(blockTypes, tblEnt.ent) + -- end + + table.insert(DarkRPEntities, tblEnt) + DarkRP.addToCategory(tblEnt, "entities", tblEnt.category) + timer.Simple(0, function() addEntityCommands(tblEnt) end) +end +AddEntity = DarkRP.createEntity + +DarkRP.removeEntity = fp{removeCustomItem, DarkRPEntities, "entities", "onEntityRemoved", true} + +-- here for backwards compatibility +DarkRPAgendas = {} + +local agendas = {} +-- Returns the agenda managed by the player +plyMeta.getAgenda = fn.Compose{fn.Curry(fn.Flip(fn.GetValue), 2)(DarkRPAgendas), plyMeta.Team} + +-- Returns the agenda this player is member of +function plyMeta:getAgendaTable() + return agendas[self:Team()] +end + +DarkRP.getAgendas = fp{fn.Id, agendas} + +function DarkRP.createAgenda(Title, Manager, Listeners) + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["agendas"][Title] then return end + + local agenda = {Manager = Manager, Title = Title, Listeners = Listeners, ManagersByKey = {}} + agenda.default = DarkRP.DARKRP_LOADING + + local valid, err, hints = DarkRP.validateAgenda(agenda) + if not valid then DarkRP.error(string.format("Corrupt agenda: %s!\n%s", agenda.Title or "", err), 2, hints) end + + for _, v in pairs(agenda.Listeners) do + agendas[v] = agenda + end + + for _, v in pairs(istable(agenda.Manager) and agenda.Manager or {agenda.Manager}) do + agendas[v] = agenda + DarkRPAgendas[v] = agenda -- backwards compat + agenda.ManagersByKey[v] = true + end + + if SERVER then + timer.Simple(0, function() + -- Run after scripts have loaded + agenda.text = hook.Run("agendaUpdated", nil, agenda, "") + end) + end +end +AddAgenda = DarkRP.createAgenda + +function DarkRP.removeAgenda(title) + local agenda + for k, v in pairs(agendas) do + if v.Title == title then + agenda = v + agendas[k] = nil + end + end + + for k, v in pairs(DarkRPAgendas) do + if v.Title == title then DarkRPAgendas[k] = nil end + end + hook.Run("onAgendaRemoved", title, agenda) +end + +GM.DarkRPGroupChats = {} +local groupChatNumber = 0 +function DarkRP.createGroupChat(funcOrTeam, ...) + local gm = GM or GAMEMODE + gm.DarkRPGroupChats = gm.DarkRPGroupChats or {} + if DarkRP.DARKRP_LOADING then + groupChatNumber = groupChatNumber + 1 + if DarkRP.disabledDefaults["groupchat"][groupChatNumber] then return end + end + -- People can enter either functions or a list of teams as parameter(s) + if isfunction(funcOrTeam) then + table.insert(gm.DarkRPGroupChats, fp{DarkRP.simplerrRun, funcOrTeam}) + else + local teams = {funcOrTeam, ...} + table.insert(gm.DarkRPGroupChats, function(ply) return table.HasValue(teams, ply:Team()) end) + end +end +GM.AddGroupChat = function(_, ...) DarkRP.createGroupChat(...) end + +DarkRP.removeGroupChat = fp{removeCustomItem, GM.DarkRPGroupChats, nil, "onGroupChatRemoved", false} + +DarkRP.getGroupChats = fp{fn.Id, GM.DarkRPGroupChats} + +GM.AmmoTypes = {} + +function DarkRP.createAmmoType(ammoType, name, model, price, amountGiven, customCheck) + local gm = GM or GAMEMODE + gm.AmmoTypes = gm.AmmoTypes or {} + local ammo = istable(name) and name or { + name = name, + model = model, + price = price, + amountGiven = amountGiven, + customCheck = customCheck + } + ammo.ammoType = ammoType + ammo.default = DarkRP.DARKRP_LOADING + + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["ammo"][ammo.name] then return end + + ammo.customCheck = ammo.customCheck and fp{DarkRP.simplerrRun, ammo.customCheck} + ammo.CustomCheckFailMsg = isfunction(ammo.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, ammo.CustomCheckFailMsg} or ammo.CustomCheckFailMsg + ammo.id = table.insert(gm.AmmoTypes, ammo) + + DarkRP.addToCategory(ammo, "ammo", ammo.category) +end +GM.AddAmmoType = function(_, ...) DarkRP.createAmmoType(...) end + +DarkRP.removeAmmoType = fp{removeCustomItem, GM.AmmoTypes, "ammo", "onAmmoTypeRemoved", true} + +local demoteGroups = {} +function DarkRP.createDemoteGroup(name, tbl) + if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["demotegroups"][name] then return end + if not tbl or not tbl[1] then error("No members in the demote group!") end + + local set = demoteGroups[tbl[1]] or disjoint.MakeSet(tbl[1]) + set.name = name + for i = 2, #tbl do + set = (demoteGroups[tbl[i]] or disjoint.MakeSet(tbl[i])) + set + set.name = name + end + + for _, teamNr in ipairs(tbl) do + if demoteGroups[teamNr] then + -- Unify the sets if there was already one there + demoteGroups[teamNr] = demoteGroups[teamNr] + set + else + demoteGroups[teamNr] = set + end + end +end + +function DarkRP.removeDemoteGroup(name) + local foundSet + for k, v in pairs(demoteGroups) do + local set = disjoint.FindSet(v) + if set.name == name then + foundSet = set + demoteGroups[k] = nil + end + end + hook.Run("onDemoteGroupRemoved", name, foundSet) +end + +function DarkRP.getDemoteGroup(teamNr) + demoteGroups[teamNr] = demoteGroups[teamNr] or disjoint.MakeSet(teamNr) + return disjoint.FindSet(demoteGroups[teamNr]) +end + +DarkRP.getDemoteGroups = fp{fn.Id, demoteGroups} + +local categories = { + jobs = {}, + entities = {}, + shipments = {}, + weapons = {}, + vehicles = {}, + ammo = {}, +} +local categoriesMerged = false -- whether categories and custom items are merged. + +DarkRP.getCategories = fp{fn.Id, categories} + +local categoryOrder = function(a, b) + local aso = a.sortOrder or 100 + local bso = b.sortOrder or 100 + return aso < bso or aso == bso and a.name < b.name +end + +local function insertCategory(destination, tbl) + -- Override existing category of applicable + for k, cat in pairs(destination) do + if cat.name ~= tbl.name then continue end + + destination[k] = tbl + tbl.members = cat.members + return + end + + table.insert(destination, tbl) + local i = #destination + + while i > 1 do + if categoryOrder(destination[i - 1], tbl) then break end + destination[i - 1], destination[i] = destination[i], destination[i - 1] + i = i - 1 + end +end + +function DarkRP.createCategory(tbl) + local valid, err, hints = DarkRP.validateCategory(tbl) + if not valid then DarkRP.error(string.format("Corrupt category: %s!\n%s", tbl.name or "", err), 2, hints) end + tbl.members = {} + + local destination = categories[tbl.categorises] + insertCategory(destination, tbl) + + -- Too many people made the mistake of not creating a category for weapons as well as shipments + -- when having shipments that can also be sold separately. + if tbl.categorises == "shipments" then + insertCategory(categories.weapons, table.Copy(tbl)) + end +end + +function DarkRP.addToCategory(item, kind, cat) + cat = cat or "Другое" + item.category = cat + + -- The merge process will take care of the category: + if not categoriesMerged then return end + + -- Post-merge: manual insertion into category + local cats = categories[kind] + for _, c in ipairs(cats) do + if c.name ~= cat then continue end + + insertCategory(c.members, item) + return + end + + DarkRP.errorNoHalt(string.format([[The category of "%s" ("%s") does not exist!]], item.name, cat), 2, { + "Make sure the category is created with DarkRP.createCategory.", + "The category name is case sensitive!", + "Categories must be created before DarkRP finished loading.", + }) +end + +function DarkRP.removeFromCategory(item, kind) + local cats = categories[kind] + if not cats then DarkRP.error(string.format("Invalid category kind '%s'.", kind), 2) end + local cat = item.category + if not cat then return end + for _, v in pairs(cats) do + if v.name ~= item.category then continue end + for k, mem in pairs(v.members) do + if mem ~= item then continue end + table.remove(v.members, k) + break + end + break + end +end + +-- Assign custom stuff to their categories +local function mergeCategories(customs, catKind, path) + local cats = categories[catKind] + local catByName = {} + for _, v in pairs(cats) do catByName[v.name] = v end + for _, v in pairs(customs) do + -- Override default thing categories: + local catName = v.default and (GAMEMODE.Config.CategoryOverride[catKind] or {})[v.name] or v.category or "Другое" + local cat = catByName[catName] + if not cat then + DarkRP.errorNoHalt(string.format([[The category of "%s" ("%s") does not exist!]], v.name, catName), 3, { + "Make sure the category is created with DarkRP.createCategory.", + "The category name is case sensitive!", + "Categories must be created before DarkRP finished loading." + }, path, -1, path) + cat = catByName.Другое + end + + cat.members = cat.members or {} + table.insert(cat.members, v) + end + + -- Sort category members + for _, v in pairs(cats) do table.sort(v.members, categoryOrder) end +end + +hook.Add("loadCustomDarkRPItems", "mergeCategories", function() + local shipments = fn.Filter(fc{fn.Not, fp{fn.GetValue, "noship"}}, CustomShipments) + local guns = fn.Filter(fp{fn.GetValue, "separate"}, CustomShipments) + + mergeCategories(RPExtraTeams, "jobs", "your jobs") + mergeCategories(DarkRPEntities, "entities", "your custom entities") + mergeCategories(shipments, "shipments", "your custom shipments") + mergeCategories(guns, "weapons", "your custom weapons") + mergeCategories(CustomVehicles, "vehicles", "your custom vehicles") + mergeCategories(GAMEMODE.AmmoTypes, "ammo", "your custom ammo") + + categoriesMerged = true +end) diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_entityvars.lua b/gamemodes/darkrp/gamemode/modules/base/sh_entityvars.lua new file mode 100644 index 0000000..0646836 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_entityvars.lua @@ -0,0 +1,100 @@ +DarkRP.RegisteredDarkRPVarsMaxId = DarkRP.RegisteredDarkRPVarsMaxId or 0 +DarkRP.RegisteredDarkRPVars = DarkRP.RegisteredDarkRPVars or {} +DarkRP.RegisteredDarkRPVarsById = DarkRP.RegisteredDarkRPVarsById or {} + +-- the amount of bits assigned to the value that determines which DarkRPVar we're sending/receiving +local DARKRP_ID_BITS = 8 +local UNKNOWN_DARKRPVAR = 255 -- Should be equal to 2^DARKRP_ID_BITS - 1 +DarkRP.DARKRP_ID_BITS = DARKRP_ID_BITS + +function DarkRP.registerDarkRPVar(name, writeFn, readFn) + -- After a reload, only update the write and read function + if DarkRP.RegisteredDarkRPVars[name] then + DarkRP.RegisteredDarkRPVars[name].writeFn = writeFn + DarkRP.RegisteredDarkRPVars[name].readFn = readFn + return + end + + DarkRP.RegisteredDarkRPVarsMaxId = DarkRP.RegisteredDarkRPVarsMaxId + 1 + + -- UNKNOWN_DARKRPVAR is reserved for unknown values + if DarkRP.RegisteredDarkRPVarsMaxId >= UNKNOWN_DARKRPVAR then DarkRP.error(string.format("Too many DarkRPVar registrations! DarkRPVar '%s' triggered this error", name), 2) end + + DarkRP.RegisteredDarkRPVars[name] = {id = DarkRP.RegisteredDarkRPVarsMaxId, name = name, writeFn = writeFn, readFn = readFn} + DarkRP.RegisteredDarkRPVarsById[DarkRP.RegisteredDarkRPVarsMaxId] = DarkRP.RegisteredDarkRPVars[name] +end + +-- Unknown values have unknown types and unknown identifiers, so this is sent inefficiently +local function writeUnknown(name, value) + net.WriteUInt(UNKNOWN_DARKRPVAR, DARKRP_ID_BITS) + net.WriteString(name) + net.WriteType(value) +end + +-- Read the value of a DarkRPVar that was not registered +local function readUnknown() + return net.ReadString(), net.ReadType(net.ReadUInt(8)) +end + +function DarkRP.writeNetDarkRPVar(name, value) + local DarkRPVar = DarkRP.RegisteredDarkRPVars[name] + if not DarkRPVar then return writeUnknown(name, value) end + + net.WriteUInt(DarkRPVar.id, DARKRP_ID_BITS) + return DarkRPVar.writeFn(value) +end + +function DarkRP.writeNetDarkRPVarRemoval(name) + local DarkRPVar = DarkRP.RegisteredDarkRPVars[name] + if not DarkRPVar then + net.WriteUInt(UNKNOWN_DARKRPVAR, DARKRP_ID_BITS) + net.WriteString(name) + return + end + + net.WriteUInt(DarkRPVar.id, DARKRP_ID_BITS) +end + +function DarkRP.readNetDarkRPVar() + local DarkRPVarId = net.ReadUInt(DARKRP_ID_BITS) + local DarkRPVar = DarkRP.RegisteredDarkRPVarsById[DarkRPVarId] + + if DarkRPVarId == UNKNOWN_DARKRPVAR then + local name, value = readUnknown() + + return name, value + end + + local val = DarkRPVar.readFn(value) + + return DarkRPVar.name, val +end + +function DarkRP.readNetDarkRPVarRemoval() + local id = net.ReadUInt(DARKRP_ID_BITS) + return id == UNKNOWN_DARKRPVAR and net.ReadString() or DarkRP.RegisteredDarkRPVarsById[id].name +end + +-- The money is a double because it accepts higher values than Int and UInt, which are undefined for >32 bits +DarkRP.registerDarkRPVar("money", net.WriteDouble, net.ReadDouble) +DarkRP.registerDarkRPVar("salary", fp{fn.Flip(net.WriteInt), 32}, fp{net.ReadInt, 32}) +DarkRP.registerDarkRPVar("rpname", net.WriteString, net.ReadString) +DarkRP.registerDarkRPVar("job", net.WriteString, net.ReadString) +DarkRP.registerDarkRPVar("HasGunlicense", net.WriteBit, fc{tobool, net.ReadBit}) +DarkRP.registerDarkRPVar("Arrested", net.WriteBit, fc{tobool, net.ReadBit}) +DarkRP.registerDarkRPVar("wanted", net.WriteBit, fc{tobool, net.ReadBit}) +DarkRP.registerDarkRPVar("wantedReason", net.WriteString, net.ReadString) +DarkRP.registerDarkRPVar("agenda", net.WriteString, net.ReadString) + +--[[--------------------------------------------------------------------------- +RP name override +---------------------------------------------------------------------------]] +local pmeta = FindMetaTable("Player") +pmeta.SteamName = pmeta.SteamName or pmeta.Name +function pmeta:Name() + if not self:IsValid() then DarkRP.error("Attempt to call Name/Nick/GetName on a non-existing player!", SERVER and 1 or 2) end + return GAMEMODE.Config.allowrpnames and self:getDarkRPVar("rpname") + or self:SteamName() +end +pmeta.GetName = pmeta.Name +pmeta.Nick = pmeta.Name diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_gamemode_functions.lua b/gamemodes/darkrp/gamemode/modules/base/sh_gamemode_functions.lua new file mode 100644 index 0000000..f2b34a2 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_gamemode_functions.lua @@ -0,0 +1,86 @@ +function GM:SetupMove(ply, mv, cmd) + if ply:isArrested() then + mv:SetMaxClientSpeed(self.Config.arrestspeed) + end + return self.Sandbox.SetupMove(self, ply, mv, cmd) +end + +function GM:StartCommand(ply, usrcmd) + -- Used in arrest_stick and unarrest_stick but addons can use it too! + local wep = ply:GetActiveWeapon() + if wep:IsValid() and isfunction(wep.startDarkRPCommand) then + wep:startDarkRPCommand(usrcmd) + end +end + +function GM:OnPlayerChangedTeam(ply, oldTeam, newTeam) + if RPExtraTeams[oldTeam] and RPExtraTeams[oldTeam].OnPlayerLeftTeam then + RPExtraTeams[oldTeam].OnPlayerLeftTeam(ply, newTeam) + end + + if RPExtraTeams[newTeam] and RPExtraTeams[newTeam].OnPlayerChangedTeam then + RPExtraTeams[newTeam].OnPlayerChangedTeam(ply, oldTeam, newTeam) + end + + if CLIENT then return end + + local agenda = ply:getAgendaTable() + + -- Remove agenda text when last manager left + if agenda and agenda.ManagersByKey[oldTeam] then + local found = false + for man, _ in pairs(agenda.ManagersByKey) do + if team.NumPlayers(man) > 0 then found = true break end + end + if not found then agenda.text = nil end + end + + ply:setSelfDarkRPVar("agenda", agenda and agenda.text or nil) +end + +hook.Add("loadCustomDarkRPItems", "CAMI privs", function() + CAMI.RegisterPrivilege{ + Name = "DarkRP_SeeEvents", + MinAccess = "admin" + } + + CAMI.RegisterPrivilege{ + Name = "DarkRP_GetAdminWeapons", + MinAccess = "admin" + } + + CAMI.RegisterPrivilege{ + Name = "DarkRP_SetDoorOwner", + MinAccess = "admin" + } + + CAMI.RegisterPrivilege{ + Name = "DarkRP_ChangeDoorSettings", + MinAccess = "superadmin" + } + + CAMI.RegisterPrivilege{ + Name = "DarkRP_AdminCommands", + MinAccess = "admin" + } + + CAMI.RegisterPrivilege{ + Name = "DarkRP_SetMoney", + MinAccess = "superadmin" + } + + CAMI.RegisterPrivilege{ + Name = "DarkRP_SetLicense", + MinAccess = "superadmin" + } + + for _, v in pairs(RPExtraTeams) do + if not v.vote or v.admin and v.admin > 1 then continue end + + local toAdmin = {[0] = "admin", [1] = "superadmin"} + CAMI.RegisterPrivilege{ + Name = "DarkRP_GetJob_" .. v.command, + MinAccess = toAdmin[v.admin or 0]-- Add privileges for the teams that are voted for + } + end +end) diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_interface.lua b/gamemodes/darkrp/gamemode/modules/base/sh_interface.lua new file mode 100644 index 0000000..b8e6934 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_interface.lua @@ -0,0 +1,1527 @@ +DarkRP.registerDarkRPVar = DarkRP.stub{ + name = "registerDarkRPVar", + description = "Register a DarkRPVar by name. You should definitely register DarkRPVars. Registering DarkRPVars will make networking much more efficient.", + parameters = { + { + name = "name", + description = "The name of the DarkRPVar.", + type = "string", + optional = false + }, + { + name = "writeFn", + description = "The function that writes a value for this DarkRPVar. Examples: net.WriteString, function(val) net.WriteUInt(val, 8) end.", + type = "function", + optional = false + }, + { + name = "readFn", + description = "The function that reads and returns a value for this DarkRPVar. Examples: net.ReadString, function() return net.ReadUInt(8) end.", + type = "function", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.writeNetDarkRPVar = DarkRP.stub{ + name = "writeNetDarkRPVar", + description = "Internal function. You probably shouldn't need this. DarkRP calls this function when sending DarkRPVar net messages. This function writes the net data for a specific DarkRPVar.", + parameters = { + { + name = "name", + description = "The name of the DarkRPVar.", + type = "string", + optional = false + }, + { + name = "value", + description = "The value of the DarkRPVar.", + type = "any", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.writeNetDarkRPVarRemoval = DarkRP.stub{ + name = "writeNetDarkRPVarRemoval", + description = "Internal function. You probably shouldn't need this. DarkRP calls this function when sending DarkRPVar net messages. This function sets a DarkRPVar to nil.", + parameters = { + { + name = "name", + description = "The name of the DarkRPVar.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.readNetDarkRPVar = DarkRP.stub{ + name = "readNetDarkRPVar", + description = "Internal function. You probably shouldn't need this. DarkRP calls this function when reading DarkRPVar net messages. This function reads the net data for a specific DarkRPVar.", + parameters = { + }, + returns = { + { + name = "name", + description = "The name of the DarkRPVar.", + type = "string" + }, + { + name = "value", + description = "The value of the DarkRPVar.", + type = "any" + } + }, + metatable = DarkRP +} + +DarkRP.readNetDarkRPVarRemoval = DarkRP.stub{ + name = "readNetDarkRPVarRemoval", + description = "Internal function. You probably shouldn't need this. DarkRP calls this function when reading DarkRPVar net messages. This function the removal of a DarkRPVar.", + parameters = { + }, + returns = { + { + name = "name", + description = "The name of the DarkRPVar.", + type = "string" + } + }, + metatable = DarkRP +} + +DarkRP.findPlayer = DarkRP.stub{ + name = "findPlayer", + description = "Find a single player based on vague information.", + parameters = { + { + name = "info", + description = "The information of the player (UserID, SteamID, name).", + type = "string", + optional = false + } + }, + returns = { + { + name = "found", + description = "The player that matches the description.", + type = "Player" + } + }, + metatable = DarkRP +} + +DarkRP.findPlayers = DarkRP.stub{ + name = "findPlayers", + description = "Find a list of players based on vague information.", + parameters = { + { + name = "info", + description = "The information of the player (UserID, SteamID, name).", + type = "string", + optional = false + } + }, + returns = { + { + name = "found", + description = "Table of players that match the description.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.nickSortedPlayers = DarkRP.stub{ + name = "nickSortedPlayers", + description = "A table of players sorted by RP name.", + parameters = {}, + returns = { + { + name = "players", + description = "The list of players sorted by RP name.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.explodeArg = DarkRP.stub{ + name = "explodeArg", + description = "String arguments exploded into a table. It accounts for substrings in quotes, which makes it more intelligent than string.Explode", + parameters = { + { + name = "arg", + description = "The full string of the argument", + type = "string", + optional = false + }, + }, + returns = { + { + name = "args", + description = "The table of arguments", + type = "table" + } + }, + metatable = DarkRP +} + + +DarkRP.formatMoney = DarkRP.stub{ + name = "formatMoney", + description = "Format a number as a money value. Includes currency symbol.", + parameters = { + { + name = "amount", + description = "The money to format, e.g. 100000.", + type = "number", + optional = false + } + }, + returns = { + { + name = "money", + description = "The money as a nice string, e.g. \"$100,000\".", + type = "string" + } + }, + metatable = DarkRP +} + +DarkRP.getJobByCommand = DarkRP.stub{ + name = "getJobByCommand", + description = "Get the job table and number from the command of the job.", + parameters = { + { + name = "command", + description = "The command of the job, without preceding slash (e.g. 'medic' for medic)", + type = "string", + optional = false + } + }, + returns = { + { + name = "tbl", + description = "A table containing all information about the job.", + type = "table" + }, + { + name = "jobindex", + description = "The index of the job (for 'medic' it's the value of TEAM_MEDIC).", + type = "number" + } + }, + metatable = DarkRP +} + +DarkRP.simplerrRun = DarkRP.stub{ + name = "simplerrRun", + description = "Run a function with the given parameters and send any runtime errors to admins.", + parameters = { + { + name = "f", + description = "The function to be called.", + type = "function", + optional = false + }, + { + name = "args", + description = "The arguments to be given to f.", + type = "vararg", + optional = true + }, + }, + returns = { + { + name = "retVals", + description = "The return values of f.", + type = "vararg" + } + }, + metatable = DarkRP +} + +DarkRP.error = DarkRP.stub{ + name = "error", + description = "Throw a simplerr formatted error. Also halts the stack, which means that statements after calling this function will not execute.", + parameters = { + { + name = "message", + description = "The message of the error.", + type = "string", + optional = false + }, + { + name = "stack", + description = "From which point in the function call stack to report the error. 1 to include the function that called DarkRP.error, 2 to exclude it, etc. The default value is 1.", + type = "number", + optional = true + }, + { + name = "hints", + description = "Table containing hint strings. Use these hints to explain the error, describe possible causes or provide help to solve the problem.", + type = "table", + optional = true + }, + { + name = "path", + description = "Override the path of the error. Will be shown in the error message. By default this is determined by the stack level.", + type = "string", + optional = true + }, + { + name = "line", + description = "Override the line number of the error. By default this is determined by the stack level.", + type = "number", + optional = true + }, + + }, + returns = { + { + name = "succeed", + description = "Simplerr return value: whether the calculation succeeded. Always false. This return value will never be reached.", + type = "boolean" + }, + { + name = "msg", + description = "Simplerr return value: nicely formatted error message. This return value will never be reached.", + type = "string" + } + }, + metatable = DarkRP +} + +DarkRP.errorNoHalt = DarkRP.stub{ + name = "errorNoHalt", + description = "Throw a simplerr formatted error. Unlike DarkRP.error, this does not halt the stack. This means that statements after calling this function will be executed like normal.", + parameters = { + { + name = "message", + description = "The message of the error.", + type = "string", + optional = false + }, + { + name = "stack", + description = "From which point in the function call stack to report the error. 1 to include the function that called DarkRP.error, 2 to exclude it, etc. The default value is 1.", + type = "number", + optional = true + }, + { + name = "hints", + description = "Table containing hint strings. Use these hints to explain the error, describe possible causes or provide help to solve the problem.", + type = "table", + optional = true + }, + { + name = "path", + description = "Override the path of the error. Will be shown in the error message. By default this is determined by the stack level.", + type = "string", + optional = true + }, + { + name = "line", + description = "Override the line number of the error. By default this is determined by the stack level.", + type = "number", + optional = true + }, + + }, + returns = { + { + name = "succeed", + description = "Simplerr return value: whether the calculation succeeded. Always false.", + type = "boolean" + }, + { + name = "msg", + description = "Simplerr return value: nicely formatted error message.", + type = "string" + } + }, + metatable = DarkRP +} + +-- This function is one of the few that's already defined before the stub is created +DarkRP.stub{ + name = "SteamName", + description = "Retrieve a player's real (steam) name.", + parameters = { + + }, + returns = { + { + name = "name", + description = "The player's steam name.", + type = "string" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.getJobTable = DarkRP.stub{ + name = "getJobTable", + description = "Get the job table of a player.", + parameters = { + }, + returns = { + { + name = "job", + description = "Table with the job information.", + type = "table" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.getDarkRPVar = DarkRP.stub{ + name = "getDarkRPVar", + description = "Get the value of a DarkRPVar, which is shared between server and client.", + parameters = { + { + name = "var", + description = "The name of the variable.", + type = "string", + optional = false + }, + { + name = "fallback", + description = "The value to return if the DarkRPVar doesn't exist.", + type = "any", + optional = true + } + }, + returns = { + { + name = "value", + description = "The value of the DarkRP var.", + type = "any" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.getAgenda = DarkRP.stub{ + name = "getAgenda", + description = "Get the agenda a player manages.", + deprecated = "Use ply:getAgendaTable() instead.", + parameters = { + }, + returns = { + { + name = "agenda", + description = "The agenda.", + type = "table" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.getAgendaTable = DarkRP.stub{ + name = "getAgendaTable", + description = "Get the agenda a player can see. Note: when a player is not the manager of an agenda, it returns the agenda of the manager.", + parameters = { + }, + returns = { + { + name = "agenda", + description = "The agenda.", + type = "table" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.hasDarkRPPrivilege = DarkRP.stub{ + name = "hasDarkRPPrivilege", + description = "Whether the player has a certain privilege.", + parameters = { + { + name = "priv", + description = "The name of the privilege.", + type = "string", + optional = false + } + }, + returns = { + { + name = "answer", + description = "Whether the player has the privilege.", + type = "boolean" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.getEyeSightHitEntity = DarkRP.stub{ + name = "getEyeSightHitEntity", + description = "Get the entity that is closest to a player's line of sight and its distance.", + parameters = { + { + name = "searchDistance", + description = "How far to look. You usually don't want this function to return an entity millions of units away. The default is 100 units.", + type = "number", + optional = true + }, + { + name = "hitDistance", + description = "The maximum distance between the player's line of sight and the object. Basically how far the player can be 'looking away' from the object. The default is 15 units.", + type = "number", + optional = true + }, + { + name = "filter", + description = "The filter for which entities to look for. By default it only looks for players.", + type = "function", + optional = true + } + }, + returns = { + { + name = "closestEnt", + description = "The entity that is closest to the player's line of sight. Returns nil when not found.", + type = "Entity" + }, + { + name = "distance", + description = "The (minimum) distance between the player's line of sight and the object.", + type = "number" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.VECTOR.isInSight = DarkRP.stub{ + name = "isInSight", + description = "Decides whether the vector could be seen by the player if they were to look at it.", + parameters = { + { + name = "filter", + description = "Trace filter that decides what the player can see through.", + type = "table", + optional = false + }, + { + name = "ply", + description = "The player for whom the vector may or may not be visible.", + type = "Player", + optional = false + } + }, + returns = { + { + name = "answer", + description = "Whether the player can see the position.", + type = "boolean" + }, + { + name = "HitPos", + description = "The position of the thing that blocks the player's sight.", + type = "Vector" + } + }, + metatable = DarkRP.VECTOR +} + +DarkRP.hookStub{ + name = "UpdatePlayerSpeed", + description = "Change a player's walking and running speed.", + deprecated = "Use GMod's SetupMove and Move hooks instead.", + parameters = { + { + name = "ply", + description = "The player for whom the speed changes.", + type = "Player" + } + }, + returns = { + } +} + +--[[--------------------------------------------------------------------------- +Creating custom items +---------------------------------------------------------------------------]] +DarkRP.createJob = DarkRP.stub{ + name = "createJob", + description = "Create a job for DarkRP.", + parameters = { + { + name = "name", + description = "The name of the job.", + type = "string", + optional = false + }, + { + name = "tbl", + description = "Table containing the information for the job.", + type = "table", + optional = false + } + }, + returns = { + { + name = "team", + description = "The team number of the job you've created.", + type = "number" + } + }, + metatable = DarkRP +} +AddExtraTeam = DarkRP.createJob + +DarkRP.removeJob = DarkRP.stub{ + name = "removeJob", + description = "Remove a job from DarkRP.", + parameters = { + { + name = "i", + description = "The TEAM_ number of the job. Also the index of the job in RPExtraTeams.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeShipment = DarkRP.stub{ + name = "removeShipment", + description = "Remove a shipment from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "i", + description = "The index of the item.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeVehicle = DarkRP.stub{ + name = "removeVehicle", + description = "Remove a vehicle from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "i", + description = "The index of the item.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeEntity = DarkRP.stub{ + name = "removeEntity", + description = "Remove an entity from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "i", + description = "The index of the item.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeGroupChat = DarkRP.stub{ + name = "removeGroupChat", + description = "Remove a groupchat from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "i", + description = "The index of the item.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeAmmoType = DarkRP.stub{ + name = "removeAmmoType", + description = "Remove an ammotype from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "i", + description = "The index of the item.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeEntityGroup = DarkRP.stub{ + name = "removeEntityGroup", + description = "Remove an entitygroup from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "name", + description = "The name of the item.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeAgenda = DarkRP.stub{ + name = "removeAgenda", + description = "Remove a agenda from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "name", + description = "The name of the item.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeDemoteGroup = DarkRP.stub{ + name = "removeDemoteGroup", + description = "Remove an demotegroup from DarkRP. NOTE: Must be called from BOTH server AND client to properly get it removed!", + parameters = { + { + name = "name", + description = "The name of the item.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.createEntityGroup = DarkRP.stub{ + name = "createEntityGroup", + description = "Create a entity group for DarkRP.", + parameters = { + { + name = "name", + description = "The name of the entity group.", + type = "string", + optional = false + }, + { + name = "teamNrs", + description = "Vararg team numbers.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} +AddDoorGroup = DarkRP.createEntityGroup + +DarkRP.createShipment = DarkRP.stub{ + name = "createShipment", + description = "Create a shipment for DarkRP.", + parameters = { + { + name = "name", + description = "The name of the shipment.", + type = "string", + optional = false + }, + { + name = "tbl", + description = "Table containing the information for the shipment.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} +AddCustomShipment = DarkRP.createShipment + +DarkRP.createVehicle = DarkRP.stub{ + name = "createVehicle", + description = "Create a vehicle for DarkRP.", + parameters = { + { + name = "name", + description = "The name of the vehicle.", + type = "string", + optional = false + }, + { + name = "tbl", + description = "Table containing the information for the vehicle.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} +AddCustomVehicle = DarkRP.createVehicle + +DarkRP.createEntity = DarkRP.stub{ + name = "createEntity", + description = "Create a entity for DarkRP.", + parameters = { + { + name = "name", + description = "The name of the entity.", + type = "string", + optional = false + }, + { + name = "tbl", + description = "Table containing the information for the entity.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} +AddCustomVehicle = DarkRP.createEntity + +DarkRP.createAgenda = DarkRP.stub{ + name = "createAgenda", + description = "Create an agenda for groups of jobs to communicate.", + parameters = { + { + name = "title", + description = "The name of the agenda.", + type = "string", + optional = false + }, + { + name = "manager", + description = "The team numer of the manager of the agenda (the one who can set the agenda).", + type = "number", + optional = false + }, + { + name = "listeners", + description = "The jobs that can see this agenda.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} +AddAgenda = DarkRP.createAgenda + +DarkRP.getAgendas = DarkRP.stub{ + name = "getAgendas", + description = "Get all agendas. Note: teams that share an agenda use the exact same agenda table. E.g. when you change the agenda of the CP, the agenda of the Chief will automatically be updated as well. Make sure this property is maintained when modifying the agenda table. Not maintaining that property will lead to players not seeing the right agenda text.", + parameters = { + + }, + returns = { + { + name = "agendas", + description = "Table in which the keys are team numbers and the values agendas.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.createGroupChat = DarkRP.stub{ + name = "createGroupChat", + description = "Create a group chat.", + parameters = { + { + name = "functionOrJob", + description = "A function that returns whether the person can see the group chat, or a team number.", + type = "any", + optional = false + }, + { + name = "teamNr", + description = "VarArg team number.", + type = "number", + optional = true + } + }, + returns = { + }, + metatable = DarkRP +} +GM.AddGroupChat = DarkRP.createGroupChat + +DarkRP.createAmmoType = DarkRP.stub{ + name = "createAmmoType", + description = "Create an ammo type.", + parameters = { + { + name = "name", + description = "The name of the ammo.", + type = "string", + optional = false + }, + { + name = "tbl", + description = "Table containing the information for the ammo.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.createDemoteGroup = DarkRP.stub{ + name = "createDemoteGroup", + description = "Create a demote group. When you get banned (demoted) from one of the jobs in this group, you will be banned from every job in this group.", + parameters = { + { + name = "name", + description = "The name of the demote group.", + type = "string", + optional = false + }, + { + name = "tbl", + description = "Table consisting of a list of job.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.getDemoteGroup = DarkRP.stub{ + name = "getDemoteGroup", + description = "Get the demote group of a team. Every team in the same group will return the same object.", + parameters = { + { + name = "teamNr", + description = "Table consisting of a list of job.", + type = "number", + optional = false + } + }, + returns = { + { + name = "set", + description = "The demote group identifier.", + type = "Disjoint-Set" + } + }, + metatable = DarkRP +} + +DarkRP.getGroupChats = DarkRP.stub{ + name = "getGroupChats", + description = "Get all group chats.", + parameters = { + + }, + returns = { + { + name = "set", + description = "Table with functions that decide who can hear who.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.getDemoteGroups = DarkRP.stub{ + name = "getDemoteGroups", + description = "Get all demote groups Every team in the same group will return the same object.", + parameters = { + + }, + returns = { + { + name = "set", + description = "Table in which the keys are team numbers and the values Disjoint-Set.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.createCategory = DarkRP.stub{ + name = "createCategory", + description = "Create a category for the F4 menu.", + parameters = { + { + name = "tbl", + description = "Table describing the category.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.addToCategory = DarkRP.stub{ + name = "addToCategory", + description = "Create a category for the F4 menu.", + parameters = { + { + name = "item", + description = "Table of the custom entity/job/etc.", + type = "table", + optional = false + }, + { + name = "kind", + description = "The kind of the category (e.g. 'jobs' for job stuff).", + type = "string", + optional = false + }, + { + name = "cat", + description = "The name of the category. Note that the category must exist. Defaults to 'Other'.", + type = "string", + optional = true + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeFromCategory = DarkRP.stub{ + name = "removeFromCategory", + description = "Create a category for the F4 menu.", + parameters = { + { + name = "item", + description = "Table of the custom entity/job/etc.", + type = "table", + optional = false + }, + { + name = "kind", + description = "The kind of the category (e.g. 'jobs' for job stuff).", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.getCategories = DarkRP.stub{ + name = "getCategories", + description = "Get all categories for all F4 menu tabs.", + parameters = { + }, + returns = { + { + name = "tbl", + description = "all categories.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.ValidatedPhysicsInit = DarkRP.stub{ + name = "ValidatedPhysicsInit", + description = "Initialise the physics of an entity, throw a discriptive error when this fails.", + parameters = { + { + name = "ent", + description = "Entity for which to create the PhysObj.", + type = "entity", + optional = false + }, + { + name = "kind", + description = "The SOLID_ enum type. By default this is SOLID_VPHYSICS", + type = "number", + optional = true + }, + { + name = "hint", + description = "Optional hint for the error message.", + type = "string", + optional = true + } + }, + returns = { + { + name = "success", + description = "Whether creating the PhysObj succeeded", + type = "boolean" + } + }, + metatable = DarkRP +} + +DarkRP.hookStub{ + name = "DarkRPVarChanged", + description = "Called when a DarkRPVar was changed.", + parameters = { + { + name = "ply", + description = "The player for whom the DarkRPVar changed.", + type = "Player" + }, + { + name = "varname", + description = "The name of the variable that has changed.", + type = "string" + }, + { + name = "oldValue", + description = "The old value of the DarkRPVar.", + type = "any" + }, + { + name = "newvalue", + description = "The new value of the DarkRPVar.", + type = "any" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "canBuyPistol", + description = "Whether a player can buy a pistol.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "shipmentTable", + description = "The table, as defined in the shipments file.", + type = "table" + } + }, + returns = { + { + name = "canBuy", + description = "Whether it can be bought.", + type = "boolean" + }, + { + name = "suppressMessage", + description = "Suppress the notification message when it cannot be bought.", + type = "boolean" + }, + { + name = "message", + description = "A replacement for the message that shows if it cannot be bought.", + type = "string" + }, + { + name = "price", + description = "An optional override for the price.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "canBuyShipment", + description = "Whether a player can buy a shipment.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "shipmentTable", + description = "The table, as defined in the shipments file.", + type = "table" + } + }, + returns = { + { + name = "canBuy", + description = "Whether it can be bought.", + type = "boolean" + }, + { + name = "suppressMessage", + description = "Suppress the notification message when it cannot be bought.", + type = "boolean" + }, + { + name = "message", + description = "A replacement for the message that shows if it cannot be bought.", + type = "string" + }, + { + name = "price", + description = "An optional override for the price.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "canBuyVehicle", + description = "Whether a player can buy a vehicle.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "vehicleTable", + description = "The table, as defined in the vehicles file.", + type = "table" + } + }, + returns = { + { + name = "canBuy", + description = "Whether it can be bought.", + type = "boolean" + }, + { + name = "suppressMessage", + description = "Suppress the notification message when it cannot be bought.", + type = "boolean" + }, + { + name = "message", + description = "A replacement for the message that shows if it cannot be bought.", + type = "string" + }, + { + name = "price", + description = "An optional override for the price.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "canBuyAmmo", + description = "Whether a player can buy ammo.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "ammoTable", + description = "The table, as defined in the a ammo file.", + type = "table" + } + }, + returns = { + { + name = "canBuy", + description = "Whether it can be bought.", + type = "boolean" + }, + { + name = "suppressMessage", + description = "Suppress the notification message when it cannot be bought.", + type = "boolean" + }, + { + name = "message", + description = "A replacement for the message that shows if it cannot be bought.", + type = "string" + }, + { + name = "price", + description = "An optional override for the price.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "canBuyCustomEntity", + description = "Whether a player can a certain custom entity.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "entTable", + description = "The table, as defined by the user.", + type = "table" + } + }, + returns = { + { + name = "canBuy", + description = "Whether it can be bought.", + type = "boolean" + }, + { + name = "suppressMessage", + description = "Suppress the notification message when it cannot be bought.", + type = "boolean" + }, + { + name = "message", + description = "A replacement for the message that shows if it cannot be bought.", + type = "string" + }, + { + name = "price", + description = "An optional override for the price.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "onJobRemoved", + description = "Called when a job was removed.", + parameters = { + { + name = "num", + description = "The TEAM_ number of the job.", + type = "number" + }, + { + name = "jobbtable", + description = "The table containing all the job info.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onShipmentRemoved", + description = "Called when a shipment was removed.", + parameters = { + { + name = "num", + description = "The index of this item.", + type = "number" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onVehicleRemoved", + description = "Called when a vehicle was removed.", + parameters = { + { + name = "num", + description = "The index of this item.", + type = "number" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onEntityRemoved", + description = "Called when a buyable entity was removed.", + parameters = { + { + name = "num", + description = "The index of this item.", + type = "number" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onGroupChatRemoved", + description = "Called when a groupchat was removed.", + parameters = { + { + name = "num", + description = "The index of this item.", + type = "number" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onAmmoTypeRemoved", + description = "Called when a ammotype was removed.", + parameters = { + { + name = "num", + description = "The index of this item.", + type = "number" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onEntityGroupRemoved", + description = "Called when an entity group was removed.", + parameters = { + { + name = "name", + description = "The name of this item.", + type = "string" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onAgendaRemoved", + description = "Called when an agenda was removed.", + parameters = { + { + name = "name", + description = "The name of this item.", + type = "string" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onDemoteGroupRemoved", + description = "Called when a job was demotegroup.", + parameters = { + { + name = "name", + description = "The name of this item.", + type = "string" + }, + { + name = "itemTable", + description = "The table containing all the info about this item.", + type = "table" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "loadCustomDarkRPItems", + description = "Runs right after the scripts from the DarkRPMod are run. You can add custom jobs, entities, shipments and whatever in this hook.", + parameters = { + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "postLoadCustomDarkRPItems", + description = "Runs right after loadCustomDarkRPItems. All custom DarkRP content will be loaded by this time.", + parameters = { + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "DarkRPStartedLoading", + description = "Runs at the very start of loading DarkRP. Not even sandbox has loaded here yet.", + parameters = { + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "DarkRPFinishedLoading", + description = "Runs right after DarkRP itself has loaded. All DarkRPMod stuff (except for disabled_defaults) is loaded during this hook. NOTE! NO CUSTOM STUFF WILL BE AVAILABLE DURING THIS HOOK. USE `loadCustomDarkRPItems` INSTEAD IF YOU WANT THAT!", + parameters = { + }, + returns = { + } +} diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_playerclass.lua b/gamemodes/darkrp/gamemode/modules/base/sh_playerclass.lua new file mode 100644 index 0000000..a451e0f --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_playerclass.lua @@ -0,0 +1,44 @@ +local PLAYER_CLASS = {} + +-- Value of -1 = set to config value, if a corresponding setting exists +PLAYER_CLASS.DisplayName = "DarkRP Base Player Class" +PLAYER_CLASS.WalkSpeed = -1 +PLAYER_CLASS.RunSpeed = -1 +PLAYER_CLASS.DuckSpeed = 0.3 +PLAYER_CLASS.UnDuckSpeed = 0.3 +PLAYER_CLASS.TeammateNoCollide = false +PLAYER_CLASS.StartHealth = -1 + +function PLAYER_CLASS:Loadout() + -- Let gamemode decide +end + +function PLAYER_CLASS:SetModel() + -- Let gamemode decide +end + +function PLAYER_CLASS:ShouldDrawLocal() + -- Let gamemode decide +end + +function PLAYER_CLASS:CreateMove(cmd) + -- Let gamemode decide +end + +function PLAYER_CLASS:CalcView(view) + -- Let gamemode decide +end + +function PLAYER_CLASS:GetHandsModel() + -- Let gamemode decide +end + +function PLAYER_CLASS:StartMove(mv, cmd) + -- Let gamemode decide +end + +function PLAYER_CLASS:FinishMove(mv) + -- Let gamemode decide +end + +player_manager.RegisterClass("player_darkrp", PLAYER_CLASS, "player_sandbox") diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_simplerr.lua b/gamemodes/darkrp/gamemode/modules/base/sh_simplerr.lua new file mode 100644 index 0000000..9973355 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_simplerr.lua @@ -0,0 +1,86 @@ +-- simplerrRun: Run a function with the given parameters and send any runtime errors to admins +DarkRP.simplerrRun = fc{ + fn.Snd, -- On success ignore the first return value + simplerr.wrapError, + simplerr.wrapHook, + simplerr.wrapLog, + simplerr.safeCall +} + +-- error: throw a runtime error without exiting the stack +-- parameters: msg, [stackNr], [hints], [path], [line] +DarkRP.errorNoHalt = fc{ + simplerr.wrapHook, + simplerr.wrapLog, + simplerr.runError, + function(msg, err, ...) return msg, err and err + 3 or 4, ... end -- Raise error level one higher +} + +-- error: throw a runtime error +-- parameters: msg, [stackNr], [hints], [path], [line] +DarkRP.error = fc{ + simplerr.wrapError, + DarkRP.errorNoHalt +} + +-- Print errors from the server in the console and show a message in chat +if CLIENT then + local function showError(count, errs) + local one = count == 1 + chat.AddText(Color(255, 0, 0), string.format("There %s %i Lua problem%s!", one and "is" or "are", count, one and "" or 's')) + chat.AddText(color_white, "\tPlease check your console for more information!") + chat.AddText(color_white, "\tNote: This error likely breaks your server. Make sure to solve the error!") + + for i = 1, count do + MsgC(Color(137, 222, 255), errs[i] .. "\n") + end + end + + net.Receive("DarkRP_simplerrError", function() + local count = net.ReadUInt(16) + local errs = {} + + for i = 1, count do + table.insert(errs, net.ReadString()) + end + + showError(count, errs) + end) + hook.Add("onSimplerrError", "DarkRP_Simplerr", function(err) showError(1, {err}) end) + + return +end + +-- Serverside part +local plyMeta = FindMetaTable("Player") +util.AddNetworkString("DarkRP_simplerrError") + +-- Send all errors to the client +local function sendErrors(plys, errs) + local count = #errs + local one = count == 1 + + DarkRP.notify(plys, 1, 120, string.format("There %s %i Lua problem%s!\nPlease check your console for more information!", one and "is" or "are", count, one and "" or 's')) + net.Start("DarkRP_simplerrError") + net.WriteUInt(#errs, 16) + fn.ForEach(fn.Flip(net.WriteString), errs) + net.Send(plys) +end + +-- Annoy all admins when an error occurs +local function annoyAdmins(err) + local admins = fn.Filter(plyMeta.IsAdmin, player.GetAll()) + sendErrors(admins, {err}) +end +hook.Add("onSimplerrError", "DarkRP_Simplerr", annoyAdmins) + +-- Annoy joining admin with errors +local function annoyAdmin(ply) + if not IsValid(ply) or not ply:IsAdmin() then return end + local errs = table.Copy(simplerr.getLog()) + if table.IsEmpty(errs) then return end + + fn.Map(fp{fn.GetValue, "err"}, errs) + sendErrors(ply, errs) +end +hook.Add("PlayerInitialSpawn", "DarkRP_Simplerr", function(ply) timer.Simple(1, fp{annoyAdmin, ply}) end) diff --git a/gamemodes/darkrp/gamemode/modules/base/sh_util.lua b/gamemodes/darkrp/gamemode/modules/base/sh_util.lua new file mode 100644 index 0000000..8c37907 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sh_util.lua @@ -0,0 +1,432 @@ +--[[--------------------------------------------------------------------------- +Utility functions +---------------------------------------------------------------------------]] + +local vector = FindMetaTable("Vector") +local meta = FindMetaTable("Player") + +--[[--------------------------------------------------------------------------- +Decides whether the vector could be seen by the player if they were to look at it +---------------------------------------------------------------------------]] +function vector:isInSight(filter, ply) + ply = ply or LocalPlayer() + local trace = {} + trace.start = ply:EyePos() + trace.endpos = self + trace.filter = filter + trace.mask = -1 + local TheTrace = util.TraceLine(trace) + + return not TheTrace.Hit, TheTrace.HitPos +end + +--[[--------------------------------------------------------------------------- +Turn a money amount into a pretty string +---------------------------------------------------------------------------]] +local function attachCurrency(str) + local config = GAMEMODE.Config + return config.currencyLeft and config.currency .. str or str .. config.currency +end + +function DarkRP.formatMoney(n) + if not n then return attachCurrency("0") end + + if n >= 1e14 then return attachCurrency(tostring(n)) end + if n <= -1e14 then return "-" .. attachCurrency(tostring(math.abs(n))) end + + local config = GAMEMODE.Config + + local negative = n < 0 + + n = tostring(math.abs(n)) + + local dp = string.find(n, ".", 1, true) or #n + 1 + + for i = dp - 4, 1, -3 do + n = n:sub(1, i) .. config.currencyThousandSeparator .. n:sub(i + 1) + end + + -- Make sure the amount is padded with zeroes + if n[#n - 1] == "." then + n = n .. "0" + end + + return (negative and "-" or "") .. attachCurrency(n) +end + +--[[--------------------------------------------------------------------------- +Find a player based on given information + +Note that there is a searching priority: + * UserID + * SteamID64 + * SteamID + * Nick + * SteamName + +Note also that there are _separate_ loops. This is to make sure the function +gives the same result, regardless of the order in which players are iterated +over. +---------------------------------------------------------------------------]] +function DarkRP.findPlayer(info) + if not info or info == "" then return nil end + local pls = player.GetAll() + + local count = #pls + local numberInfo = tonumber(info) + + -- First check if the input matches a player by UserID or SteamID64. This is + -- only necessary if the input can be parsed as a number. + if numberInfo then + for k = 1, count do + local v = pls[k] + + if numberInfo == v:UserID() then + return v + end + end + + for k = 1, count do + local v = pls[k] + + if info == v:SteamID64() then + return v + end + end + end + + local lowerInfo = string.lower(tostring(info)) + if string.StartsWith(lowerInfo, "steam_") then + for k = 1, count do + local v = pls[k] + + if info == v:SteamID() then + return v + end + end + end + + for k = 1, count do + local v = pls[k] + + if string.find(string.lower(v:Nick()), lowerInfo, 1, true) ~= nil then + return v + end + end + + for k = 1, count do + local v = pls[k] + + if string.find(string.lower(v:SteamName()), lowerInfo, 1, true) ~= nil then + return v + end + end + + return nil +end + +--[[--------------------------------------------------------------------------- +Find multiple players based on a string criterium +Taken from FAdmin]] +---------------------------------------------------------------------------*/ +function DarkRP.findPlayers(info) + if not info then return nil end + local pls = player.GetAll() + local found = {} + local players + + if string.lower(info) == "*" or string.lower(info) == "" then return pls end + + local InfoPlayers = {} + for A in string.gmatch(info .. ";", "([a-zA-Z0-9:_.]*)[;(,%s)%c]") do + if A ~= "" then + table.insert(InfoPlayers, A) + end + end + + for _, PlayerInfo in ipairs(InfoPlayers) do + -- Playerinfo is always to be treated as UserID when it's a number + -- otherwise people with numbers in their names could get confused with UserID's of other players + if tonumber(PlayerInfo) then + local foundPlayer = Player(PlayerInfo) + if IsValid(foundPlayer) and not found[foundPlayer] then + found[foundPlayer] = true + players = players or {} + table.insert(players, foundPlayer) + end + continue + end + + local stringPlayerInfo = string.lower(PlayerInfo) + for _, v in ipairs(pls) do + -- Prevent duplicates + if found[v] then continue end + local steamId = v:SteamID() + + -- Find by Steam ID + if (PlayerInfo == steamId or steamId == "UNKNOWN") or + -- Find by Partial Nick + string.find(string.lower(v:Nick()), stringPlayerInfo, 1, true) ~= nil or + -- Find by steam name + (v.SteamName and string.find(string.lower(v:SteamName()), stringPlayerInfo, 1, true) ~= nil) then + found[v] = true + players = players or {} + table.insert(players, v) + end + end + end + + return players +end + +function meta:getEyeSightHitEntity(searchDistance, hitDistance, filter) + searchDistance = searchDistance or 100 + hitDistance = (hitDistance or 15) * (hitDistance or 15) + + filter = filter or function(p) return p:IsPlayer() and p ~= self end + + self:LagCompensation(true) + + local shootPos = self:GetShootPos() + local entities = ents.FindInSphere(shootPos, searchDistance) + local aimvec = self:GetAimVector() + + local smallestDistance = math.huge + local foundEnt + + for _, ent in ipairs(entities) do + if not IsValid(ent) or filter(ent) == false then continue end + + local center = ent:GetPos() + + -- project the center vector on the aim vector + local projected = shootPos + (center - shootPos):Dot(aimvec) * aimvec + + if aimvec:Dot((projected - shootPos):GetNormalized()) < 0 then continue end + + -- the point on the model that has the smallest distance to your line of sight + local nearestPoint = ent:NearestPoint(projected) + local distance = nearestPoint:DistToSqr(projected) + + if distance < smallestDistance then + local trace = { + start = self:GetShootPos(), + endpos = nearestPoint, + filter = {self, ent} + } + local traceLine = util.TraceLine(trace) + if traceLine.Hit then continue end + + smallestDistance = distance + foundEnt = ent + end + end + + self:LagCompensation(false) + + if smallestDistance < hitDistance then + return foundEnt, math.sqrt(smallestDistance) + end + + return nil +end + +--[[--------------------------------------------------------------------------- +Print the currently available vehicles +---------------------------------------------------------------------------]] +local function GetAvailableVehicles(ply) + if SERVER and IsValid(ply) and not ply:IsAdmin() then return end + local print = SERVER and ServerLog or Msg + + print(DarkRP.getPhrase("rp_getvehicles") .. "\n") + for k in pairs(DarkRP.getAvailableVehicles()) do + print("\"" .. k .. "\"" .. "\n") + end +end +if SERVER then + concommand.Add("rp_getvehicles_sv", GetAvailableVehicles) +else + concommand.Add("rp_getvehicles", GetAvailableVehicles) +end + +--[[--------------------------------------------------------------------------- +Whether a player has a DarkRP privilege +---------------------------------------------------------------------------]] +function meta:hasDarkRPPrivilege(priv) + if FAdmin then + return FAdmin.Access.PlayerHasPrivilege(self, priv) + end + return self:IsAdmin() +end + +--[[--------------------------------------------------------------------------- +Convenience function to return the players sorted by name +---------------------------------------------------------------------------]] +function DarkRP.nickSortedPlayers() + local plys = player.GetAll() + table.sort(plys, function(a,b) return a:Nick() < b:Nick() end) + return plys +end + +--[[--------------------------------------------------------------------------- +Convert a string to a table of arguments +---------------------------------------------------------------------------]] +local bitlshift, stringgmatch, stringsub, tableinsert = bit.lshift, string.gmatch, string.sub, table.insert +function DarkRP.explodeArg(arg) + local args = {} + + local from, to, diff = 1, 0, 0 + local inQuotes, wasQuotes = false, false + + for c in stringgmatch(arg, '.') do + to = to + 1 + + if c == '"' then + inQuotes = not inQuotes + wasQuotes = true + + continue + end + + if c == ' ' and not inQuotes then + diff = wasQuotes and 1 or 0 + wasQuotes = false + tableinsert(args, stringsub(arg, from + diff, to - 1 - diff)) + from = to + 1 + end + end + diff = wasQuotes and 1 or 0 + + if from ~= to + 1 then tableinsert(args, stringsub(arg, from + diff, to + 1 - bitlshift(diff, 1))) end + + return args +end + +--[[--------------------------------------------------------------------------- +Initialize Physics, throw an error on failure +---------------------------------------------------------------------------]] +function DarkRP.ValidatedPhysicsInit(ent, solidType, hint) + solidType = solidType or SOLID_VPHYSICS + + if ent:PhysicsInit(solidType) then return true end + + local class = ent:GetClass() + + if solidType == SOLID_BSP then + DarkRP.errorNoHalt(string.format("%s has no physics and will be motionless", class), 2, { + "Is this a brush model? SOLID_BSP physics cannot initialize on entities that don't have brush models", + "The physics limit may have been hit", + hint + }) + + return false + end + + if solidType == SOLID_VPHYSICS then + local mdl = ent:GetModel() + + if not mdl or mdl == "" then + DarkRP.errorNoHalt(string.format("Cannot init physics on entity \"%s\" because it has no model", class), 2, {hint}) + return false + end + + mdl = string.lower(mdl) + + if util.IsValidProp(mdl) then + -- Has physics, we must have hit the limit + DarkRP.errorNoHalt(string.format("physics limit hit - %s will be motionless", class), 2, {hint}) + + return false + end + + if not file.Exists(mdl, "GAME") then + DarkRP.errorNoHalt(string.format("%s has missing model \"%s\" and will be invisible and motionless", class, mdl), 2, { + "Is the model path correct?", + "Is the model from an addon that is not installed?", + "Is the model from a game that isn't (properly) mounted? E.g. Counter Strike: Source", + hint + }) + + return false + end + + DarkRP.errorNoHalt(string.format("%s has model \"%s\" with no physics and will be motionless", class, mdl), 2, { + "Does this model have an associated physics model (modelname.phy)?", + "Is this model supposed to have physics? Many models, like effects and view models aren't made to have physics", + hint + }) + + return false + end + + DarkRP.errorNoHalt(string.format("Unable to initilize physics on entity \"%s\"", class, {hint}), 2) + + return false +end + +--[[--------------------------------------------------------------------------- +Like tonumber, but makes sure it's an integer +---------------------------------------------------------------------------]] +function DarkRP.toInt(value) + value = tonumber(value) + return value and math.floor(value) +end + +--[[------------------------------------------------------------------------- +Check the database for integrity errors. Use in cases when stuff doesn't load +on restart, or you get corruption errors. +---------------------------------------------------------------------------]] +if SERVER then util.AddNetworkString("DarkRP_databaseCheckMessage") end +if CLIENT then net.Receive("DarkRP_databaseCheckMessage", fc{print, net.ReadString}) end + +local function checkDatabase(ply) + local dbFile = SERVER and "sv.db" or "cl.db" + local display = (CLIENT or not IsValid(ply)) and print or function(msg) + net.Start("DarkRP_databaseCheckMessage") + net.WriteString(msg) + net.Send(ply) + end + + if SERVER and IsValid(ply) and not ply:IsSuperAdmin() then + display("You must be superadmin") + return + end + + if MySQLite and MySQLite.isMySQL() then + display(string.format([[WARNING: DarkRP is using MySQL. This only + checks the local SQLite database stored in the %s file in the + garrysmod/ folder. The check will continue.]], dbFile)) + end + + local check = sql.QueryValue("PRAGMA INTEGRITY_CHECK") + if check == false then + display([[The query to check the database failed. Shit's surely + fucked, but the cause is unknown.]]) + return + end + + if check == "ok" then + display(string.format("Your %s database file is good.", dbFile)) + return + end + + display(string.format([[There are errors in your %s database file. It's corrupt! + + This can cause the following problems: + - Data not loading, think of blocked models, doors, players' money and RP names + - Settings resetting to their default values + - Lua errors on startup + + The cause of the problem is that the %s file in your garrysmod/ folder on + %s is corrupt. How this came to be is unknown, but here's what you can do to solve it: + + - Delete %s, and run a file integrity check. Warning: You will lose ALL data stored in it! + - Take the file and try to repair it. This is sadly something that requires some technical knowledge, + and may not always succeed. + + The specific error, by the way, is as follows: + %s + ]], dbFile, dbFile, SERVER and "the server" or "your own computer", dbFile, check)) + +end +concommand.Add("darkrp_check_db_" .. (SERVER and "sv" or "cl"), checkDatabase) diff --git a/gamemodes/darkrp/gamemode/modules/base/sv_data.lua b/gamemodes/darkrp/gamemode/modules/base/sv_data.lua new file mode 100644 index 0000000..3814207 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sv_data.lua @@ -0,0 +1,627 @@ +--[[--------------------------------------------------------------------------- +Functions and variables +---------------------------------------------------------------------------]] +local setUpNonOwnableDoors, + setUpTeamOwnableDoors, + setUpGroupDoors, + migrateDB + +--[[--------------------------------------------------------- + Database initialize + ---------------------------------------------------------]] +function DarkRP.initDatabase() + MySQLite.begin() + -- Gotta love the difference between SQLite and MySQL + local is_mysql = MySQLite.isMySQL() + local AUTOINCREMENT = is_mysql and "AUTO_INCREMENT" or "AUTOINCREMENT" + -- in MySQL, the engine is set to InnoDB. InnoDB has been the default + -- for a while, but people might be running old versions of MySQL. + -- SQLite has no database engine, so it is not explicitly set. + local ENGINE_INNODB = is_mysql and "ENGINE=InnoDB" or "" + + -- Table that holds all position data (jail, spawns etc.) + -- Queue these queries because other queries depend on the existence of the darkrp_position table + -- Race conditions could occur if the queries are executed simultaneously + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS darkrp_position( + id INTEGER NOT NULL PRIMARY KEY ]] .. AUTOINCREMENT .. [[, + map VARCHAR(45) NOT NULL, + type CHAR(1) NOT NULL, + x INTEGER NOT NULL, + y INTEGER NOT NULL, + z INTEGER NOT NULL + ) ]] .. ENGINE_INNODB .. [[; + ]]) + + -- team spawns require extra data + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS darkrp_jobspawn( + id INTEGER NOT NULL PRIMARY KEY REFERENCES darkrp_position(id) + ON UPDATE CASCADE + ON DELETE CASCADE, + + teamcmd VARCHAR(255) NOT NULL + ) ]] .. ENGINE_INNODB .. [[; + ]]) + + -- This table is kept for compatibility with older addons and websites + -- See https://github.com/FPtje/DarkRP/issues/819 + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS playerinformation( + uid BIGINT NOT NULL, + steamID VARCHAR(50) NOT NULL PRIMARY KEY + ) ]] .. ENGINE_INNODB .. [[ + ]]) + + -- Player information + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS darkrp_player( + uid BIGINT NOT NULL PRIMARY KEY, + rpname VARCHAR(45), + salary INTEGER NOT NULL DEFAULT 45, + wallet BIGINT NOT NULL + ) ]] .. ENGINE_INNODB .. [[; + ]]) + + -- Door data + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS darkrp_door( + idx INTEGER NOT NULL, + map VARCHAR(45) NOT NULL, + title VARCHAR(25), + isLocked BOOLEAN, + isDisabled BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY(idx, map) + ) ]] .. ENGINE_INNODB .. [[; + ]]) + + -- Some doors are owned by certain teams + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS darkrp_doorjobs( + idx INTEGER NOT NULL, + map VARCHAR(45) NOT NULL, + job VARCHAR(255) NOT NULL, + + PRIMARY KEY(idx, map, job) + ) ]] .. ENGINE_INNODB .. [[; + ]]) + + -- Door groups + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS darkrp_doorgroups( + idx INTEGER NOT NULL, + map VARCHAR(45) NOT NULL, + doorgroup VARCHAR(100) NOT NULL, + + PRIMARY KEY(idx, map) + ) ]] .. ENGINE_INNODB .. [[ + ]]) + + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS darkrp_dbversion(version INTEGER NOT NULL PRIMARY KEY) ]] .. ENGINE_INNODB .. [[ + ]]) + + -- Load the last DBVersion into DarkRP.DBVersion, to allow checks to see whether migration is needed. + MySQLite.queueQuery([[ + SELECT MAX(version) AS version FROM darkrp_dbversion + ]], function(data) + -- The database is created with the schema of the latest version. On + -- initialization the version is not set yet. Set it to the latest + -- version. + if not data or not data[1] or not tonumber(data[1].version) then + DarkRP.DBVersion = 20211228 + MySQLite.query([[ + REPLACE INTO darkrp_dbversion VALUES(20211228) + ]]) + else + DarkRP.DBVersion = tonumber(data[1].version) + end + end) + + MySQLite.commit(fp{migrateDB, -- Migrate the database + function() -- Initialize the data after all the tables have been created + setUpNonOwnableDoors() + setUpTeamOwnableDoors() + setUpGroupDoors() + + if MySQLite.isMySQL() then -- In a listen server, the connection with the external database is often made AFTER the listen server host has joined, + --so he walks around with the settings from the SQLite database + for _, v in ipairs(player.GetAll()) do + DarkRP.offlinePlayerData(v:SteamID(), function(data) + local Data = data and data[1] + if not IsValid(v) or not Data then return end + + v:setDarkRPVar("rpname", Data.rpname) + v:setSelfDarkRPVar("salary", Data.salary) + v:setDarkRPVar("money", Data.wallet) + end) + end + end + + hook.Call("DarkRPDBInitialized") + end}) +end + +--[[--------------------------------------------------------------------------- +Database migration +backwards compatibility with older versions of DarkRP +---------------------------------------------------------------------------]] +function migrateDB(callback) + -- Simple function that checks the database version, migrates if + -- necessary, and recurses to perform the next migration, until the last + -- migration has been performed. + -- Calls callback when the migration is finished or not necessary. + local function migrate(version) + if version < 20160610 then + MySQLite.begin() + if MySQLite.isMySQL() then + -- if only SQLite were this easy + MySQLite.queueQuery([[DROP INDEX rpname ON darkrp_player]]) + else + -- darkrp_player used to have a UNIQUE rpname field. + -- This sucks, get rid of it + MySQLite.queueQuery([[PRAGMA foreign_keys=OFF]]) + + MySQLite.queueQuery([[ + CREATE TABLE IF NOT EXISTS new_darkrp_player( + uid BIGINT NOT NULL PRIMARY KEY, + rpname VARCHAR(45), + salary INTEGER NOT NULL DEFAULT 45, + wallet INTEGER NOT NULL + ); + ]]) + + MySQLite.queueQuery([[INSERT INTO new_darkrp_player SELECT * FROM darkrp_player]]) + + MySQLite.queueQuery([[DROP TABLE darkrp_player]]) + + MySQLite.queueQuery([[ALTER TABLE new_darkrp_player RENAME TO darkrp_player]]) + + MySQLite.queueQuery([[PRAGMA foreign_keys=ON]]) + end + MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20160610)]]) + MySQLite.commit(fp{migrate, 20160610}) + return + end + + if version < 20181013 then + -- migrate from darkrp_jobown to darkrp_doorjobs + MySQLite.tableExists("darkrp_jobown", function(exists) + if not exists then migrate(20181013) return end + + MySQLite.begin() + -- Create a temporary table that links job IDs to job commands + MySQLite.queueQuery("CREATE TABLE IF NOT EXISTS TempJobCommands(id INT NOT NULL PRIMARY KEY, cmd VARCHAR(255) NOT NULL);") + if MySQLite.isMySQL() then + local jobCommands = {} + for k, v in pairs(RPExtraTeams) do + table.insert(jobCommands, "(" .. k .. "," .. MySQLite.SQLStr(v.command) .. ")") + end + + -- This WOULD work with SQLite if the implementation in GMod wasn't out of date. + MySQLite.queueQuery("INSERT IGNORE INTO TempJobCommands VALUES " .. table.concat(jobCommands, ",") .. ";") + else + for k, v in pairs(RPExtraTeams) do + MySQLite.queueQuery("INSERT INTO TempJobCommands VALUES(" .. k .. ", " .. MySQLite.SQLStr(v.command) .. ");") + end + end + + MySQLite.queueQuery("REPLACE INTO darkrp_doorjobs SELECT darkrp_jobown.idx AS idx, darkrp_jobown.map AS map, TempJobCommands.cmd AS job FROM darkrp_jobown JOIN TempJobCommands ON darkrp_jobown.job = TempJobCommands.id;") + + -- Clean up the transition table and the old table + MySQLite.queueQuery("DROP TABLE TempJobCommands;") + MySQLite.queueQuery("DROP TABLE darkrp_jobown;") + MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20181013)]]) + MySQLite.commit(fp{migrate, 20181013}) + end) + return + end + + if version < 20181014 then + MySQLite.query([[SELECT * FROM darkrp_jobspawn]], function(oldData) + oldData = oldData or {} + MySQLite.begin() + + MySQLite.queueQuery([[DROP TABLE darkrp_jobspawn]]) + + MySQLite.queueQuery([[ + CREATE TABLE darkrp_jobspawn( + id INTEGER NOT NULL PRIMARY KEY REFERENCES darkrp_position(id) + ON UPDATE CASCADE + ON DELETE CASCADE, + + teamcmd VARCHAR(255) NOT NULL + ); + ]]) + + for i, row in pairs(oldData) do + local teamcmd = (RPExtraTeams[tonumber(row.team)] or {}).command + if not teamcmd then continue end + + MySQLite.queueQuery(string.format([[INSERT INTO darkrp_jobspawn(id, teamcmd) VALUES(%s, %s)]], row.id, MySQLite.SQLStr(teamcmd))) + end + + MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20181014)]]) + + MySQLite.commit(fp{migrate, 20181014}) + end) + return + end + + if version < 20190914 then + MySQLite.begin() + -- Migration not necessary for SQLite, since BIGINT and + -- INTEGER are considered the same in SQLite + -- https://www.sqlite.org/datatype3.html + if MySQLite.isMySQL() then + MySQLite.queueQuery([[DROP TRIGGER IF EXISTS JobPositionFKDelete]]) + MySQLite.queueQuery([[ALTER TABLE darkrp_player MODIFY wallet BIGINT;]]) + end + MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20190914)]]) + MySQLite.commit(fp{migrate, 20190914}) + + return + end + + if version < 20211228 then + MySQLite.begin() + -- Migrate all tables to InnoDB if they weren't already. + -- See https://github.com/FPtje/DarkRP/issues/3157 + if MySQLite.isMySQL() then + MySQLite.queueQuery([[ALTER TABLE darkrp_dbversion ENGINE = InnoDB;]]) + MySQLite.queueQuery([[ALTER TABLE darkrp_door ENGINE = InnoDB;]]) + MySQLite.queueQuery([[ALTER TABLE darkrp_doorgroups ENGINE = InnoDB;]]) + MySQLite.queueQuery([[ALTER TABLE darkrp_doorjobs ENGINE = InnoDB;]]) + MySQLite.queueQuery([[ALTER TABLE darkrp_jobspawn ENGINE = InnoDB;]]) + MySQLite.queueQuery([[ALTER TABLE darkrp_player ENGINE = InnoDB;]]) + MySQLite.queueQuery([[ALTER TABLE darkrp_position ENGINE = InnoDB;]]) + MySQLite.queueQuery([[ALTER TABLE playerinformation ENGINE = InnoDB;]]) + end + + MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20211228)]]) + MySQLite.commit(fp{migrate, 20211228}) + + return + end + + -- All migrations finished + callback() + end + migrate(DarkRP.DBVersion) +end + +--[[--------------------------------------------------------- +Players + ---------------------------------------------------------]] +function DarkRP.storeRPName(ply, name) + if not name or string.len(name) < 2 then return end + hook.Call("onPlayerChangedName", nil, ply, ply:getDarkRPVar("rpname"), name) + ply:setDarkRPVar("rpname", name) + + MySQLite.query([[UPDATE darkrp_player SET rpname = ]] .. MySQLite.SQLStr(name) .. [[ WHERE UID = ]] .. ply:SteamID64() .. ";") + MySQLite.query([[UPDATE darkrp_player SET rpname = ]] .. MySQLite.SQLStr(name) .. [[ WHERE UID = ]] .. ply:UniqueID() .. ";") +end + +function DarkRP.retrieveRPNames(name, callback) + MySQLite.query("SELECT COUNT(*) AS count FROM darkrp_player WHERE rpname = " .. MySQLite.SQLStr(name) .. ";", function(r) + callback(tonumber(r[1].count) > 0) + end) +end + +function DarkRP.offlinePlayerData(steamid, callback, failed) + local sid64 = util.SteamIDTo64(steamid) + local uniqueid = util.CRC("gm_" .. string.upper(steamid) .. "_gm") + + MySQLite.query(string.format([[REPLACE INTO playerinformation VALUES(%s, %s);]], MySQLite.SQLStr(sid64), MySQLite.SQLStr(steamid)), nil, failed) + + local query = [[ + SELECT rpname, wallet, salary, "SID64" AS kind + FROM darkrp_player + where uid = %s + + UNION + + SELECT rpname, wallet, salary, "UniqueID" AS kind + FROM darkrp_player + where uid = %s + ; + ]] + + MySQLite.query( + query:format(sid64, uniqueid), + function(data, ...) + -- The database has no record of the player data in SteamID64 form + -- Otherwise the first row would have kind SID64 + if data and data[1] and data[1].kind == "UniqueID" then + -- The rpname must be unique + -- adding a new row with uid = SteamID64, but the same rpname will remove the uid=UniqueID row + + local replquery = [[ + REPLACE INTO darkrp_player(uid, rpname, wallet, salary) + VALUES (%s, %s, %s, %s) + ]] + + MySQLite.begin() + MySQLite.queueQuery( + replquery:format( + sid64, + data[1].rpname == "NULL" and "NULL" or MySQLite.SQLStr(data[1].rpname), + data[1].wallet, + data[1].salary + ), + nil, + failed + ) + MySQLite.commit() + end + + return callback and callback(data, ...) + end + , failed + ) +end + +function DarkRP.retrievePlayerData(ply, callback, failed, attempts, err) + attempts = attempts or 0 + + if attempts > 3 then return failed(err) end + + DarkRP.offlinePlayerData(ply:SteamID(), callback, function(sqlErr) + if not IsValid(ply) then return end + + DarkRP.retrievePlayerData(ply, callback, failed, attempts + 1, sqlErr) + end) +end + +function DarkRP.createPlayerData(ply, name, wallet, salary, onError) + MySQLite.query([[REPLACE INTO darkrp_player VALUES(]] .. + ply:SteamID64() .. [[, ]] .. + MySQLite.SQLStr(utf8.force(name)) .. [[, ]] .. + salary .. [[, ]] .. + wallet .. ");", nil, onError) + + -- Backwards compatibility + MySQLite.query([[REPLACE INTO darkrp_player VALUES(]] .. + ply:UniqueID() .. [[, ]] .. + MySQLite.SQLStr(utf8.force(name)) .. [[, ]] .. + salary .. [[, ]] .. + wallet .. ");", nil, onError) +end + +function DarkRP.storeMoney(ply, amount) + if not isnumber(amount) or amount < 0 or amount >= 1 / 0 then + DarkRP.errorNoHalt("Some addon attempted to store a invalid money amount " .. tostring(amount) .. " for Player " .. ply:Nick() .. " (" .. ply:SteamID() .. ")", 1, { + "This money amount will not be stored in the database, but it may be set in the game.", + "The database simply stores the last valid, non-negative amount of money.", + "Please try to find the very first time this error happened for this player. Then look at the files mentioned in this error.", + "That will tell you which addon is causing this.", + "IMPORTANT: This is NOT a DarkRP bug!", + "Note: The player can simply rejoin to fix their negative money, until whatever causes this happens again." + }) + return + end + + -- Also keep deprecated UniqueID data at least somewhat up to date + MySQLite.query([[UPDATE darkrp_player SET wallet = ]] .. amount .. [[ WHERE uid = ]] .. ply:UniqueID() .. [[ OR uid = ]] .. ply:SteamID64()) +end + +function DarkRP.storeOfflineMoney(sid64, amount) + if isnumber(sid64) or isstring(sid64) and string.len(sid64) < 17 then -- smaller than 76561197960265728 is not a SteamID64 + DarkRP.errorNoHalt([[Some addon is giving DarkRP.storeOfflineMoney a UniqueID as its first argument, but this function now expects a SteamID64]], 1, { + "The function used to take UniqueIDs, but it does not anymore.", + "If you are a server owner, please look closely to the files mentioned in this error", + "After all, these files will tell you WHICH addon is doing it", + "This is NOT a DarkRP bug!", + "Your server will continue working normally", + "But whichever addon just tried to store an offline player's money", + "Will NOT take effect!" + }) + end + + if not isnumber(amount) or amount < 0 or amount >= 1 / 0 then + DarkRP.errorNoHalt("Some addon attempted to store a invalid money amount " .. tostring(amount) .. " for an offline player with steamID64 " .. sid64, 1, { + "This money amount will not be stored in the database.", + "Please try to find the very first time this error happened for this player. Then look at the files mentioned in this error.", + "That will tell you which addon is causing this.", + "IMPORTANT: This is NOT a DarkRP bug!" + }) + return + end + + -- Also store on deprecated UniqueID + local uniqueid = util.CRC("gm_" .. string.upper(util.SteamIDFrom64(sid64)) .. "_gm") + MySQLite.query([[UPDATE darkrp_player SET wallet = ]] .. amount .. [[ WHERE uid = ]] .. uniqueid .. [[ OR uid = ]] .. sid64) +end + +local function resetAllMoney(ply, cmd, args) + if ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then return end + MySQLite.query("UPDATE darkrp_player SET wallet = " .. GAMEMODE.Config.startingmoney .. " ;") + for _, v in ipairs(player.GetAll()) do + v:setDarkRPVar("money", GAMEMODE.Config.startingmoney) + end + if ply:IsPlayer() then + DarkRP.notifyAll(0, 4, DarkRP.getPhrase("reset_money", ply:Nick())) + else + DarkRP.notifyAll(0, 4, DarkRP.getPhrase("reset_money", "Console")) + end +end +concommand.Add("rp_resetallmoney", resetAllMoney) + +function DarkRP.storeSalary(ply, amount) + ply:setSelfDarkRPVar("salary", math.floor(amount)) + + return amount +end + +function DarkRP.retrieveSalary(ply, callback) + local val = + ply:getJobTable() and ply:getJobTable().salary or + RPExtraTeams[GAMEMODE.DefaultTeam].salary or + (GM or GAMEMODE).Config.normalsalary + + if callback then callback(val) end + + return val +end + +--[[--------------------------------------------------------------------------- +Players +---------------------------------------------------------------------------]] +local meta = FindMetaTable("Player") +function meta:restorePlayerData() + self.DarkRPUnInitialized = true + + local function onError(err) + if not IsValid(self) then return end + self.DarkRPUnInitialized = true -- no information should be saved from here, or the playerdata might be reset + + self:setDarkRPVar("money", GAMEMODE.Config.startingmoney) + self:setSelfDarkRPVar("salary", DarkRP.retrieveSalary(self)) + local name = string.sub(string.gsub(self:SteamName(), "\\\"", "\""), 1, 30) + self:setDarkRPVar("rpname", name) + + self.DarkRPDataRetrievalFailed = true -- marker on the player that says shit is fucked + DarkRP.error("Failed to retrieve player information from the database. ", nil, {"This means your database or the connection to your database is fucked.", "This is the error given by the database:\n\t\t" .. tostring(err)}) + end + + DarkRP.retrievePlayerData(self, function(data) + if not IsValid(self) then return end + + self.DarkRPUnInitialized = nil + + local info = data and data[1] or {} + if not info.rpname or info.rpname == "NULL" then info.rpname = string.sub(string.gsub(self:SteamName(), "\\\"", "\""), 1, 30) end + + info.wallet = info.wallet or GAMEMODE.Config.startingmoney + info.salary = DarkRP.retrieveSalary(self) + + self:setDarkRPVar("money", tonumber(info.wallet)) + self:setSelfDarkRPVar("salary", tonumber(info.salary)) + + self:setDarkRPVar("rpname", info.rpname) + + if not data then + info = hook.Call("onPlayerFirstJoined", nil, self, info) or info + DarkRP.createPlayerData(self, info.rpname, info.wallet, info.salary, onError) + end + end, onError) +end + +--[[--------------------------------------------------------- + Doors + ---------------------------------------------------------]] +function DarkRP.storeDoorData(ent) + if not ent:CreatedByMap() then return end + local map = string.lower(game.GetMap()) + local nonOwnable = ent:getKeysNonOwnable() + local title = ent:getKeysTitle() + + MySQLite.query([[REPLACE INTO darkrp_door VALUES(]] .. ent:doorIndex() .. [[, ]] .. MySQLite.SQLStr(map) .. [[, ]] .. (title and MySQLite.SQLStr(title) or "NULL") .. [[, ]] .. "NULL" .. [[, ]] .. (nonOwnable and 1 or 0) .. [[);]]) +end + +function setUpNonOwnableDoors() + MySQLite.query("SELECT idx, title, isLocked, isDisabled FROM darkrp_door WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";", function(r) + if not r then return end + + for _, row in pairs(r) do + local e = DarkRP.doorIndexToEnt(tonumber(row.idx)) + + if not IsValid(e) then continue end + if e:isKeysOwnable() then + if tobool(row.isDisabled) then + e:setKeysNonOwnable(tobool(row.isDisabled)) + end + if row.isLocked and row.isLocked ~= "NULL" then + e:Fire((tobool(row.isLocked) and "" or "un") .. "lock", "", 0) + end + e:setKeysTitle(row.title ~= "NULL" and row.title or nil) + end + end + end) +end + +local keyValueActions = { + ["DarkRPNonOwnable"] = function(ent, val) ent:setKeysNonOwnable(tobool(val)) end, + ["DarkRPTitle"] = function(ent, val) ent:setKeysTitle(val) end, + ["DarkRPDoorGroup"] = function(ent, val) if RPExtraTeamDoors[val] then ent:setDoorGroup(val) end end, + ["DarkRPCanLockpick"] = function(ent, val) ent.DarkRPCanLockpick = tobool(val) end +} + +local function onKeyValue(ent, key, value) + if not ent:isDoor() then return end + + if keyValueActions[key] then + keyValueActions[key](ent, value) + end +end +hook.Add("EntityKeyValue", "darkrp_doors", onKeyValue) + +function DarkRP.storeTeamDoorOwnability(ent) + if not ent:CreatedByMap() then return end + local map = string.lower(game.GetMap()) + + MySQLite.query("DELETE FROM darkrp_doorjobs WHERE idx = " .. ent:doorIndex() .. " AND map = " .. MySQLite.SQLStr(map) .. ";") + for k in pairs(ent:getKeysDoorTeams() or {}) do + MySQLite.query("INSERT INTO darkrp_doorjobs VALUES(" .. ent:doorIndex() .. ", " .. MySQLite.SQLStr(map) .. ", " .. MySQLite.SQLStr(RPExtraTeams[k].command) .. ");") + end +end + +function setUpTeamOwnableDoors() + MySQLite.query("SELECT idx, job FROM darkrp_doorjobs WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";", function(r) + if not r then return end + local map = string.lower(game.GetMap()) + + for _, row in pairs(r) do + row.idx = tonumber(row.idx) + + local e = DarkRP.doorIndexToEnt(row.idx) + if not IsValid(e) then continue end + + local _, job = DarkRP.getJobByCommand(row.job) + + if job then + e:addKeysDoorTeam(job) + else + print(("can't find job %s for door %d, removing from database"):format(row.job, row.idx)) + MySQLite.query(("DELETE FROM darkrp_doorjobs WHERE idx = %d AND map = %s AND job = %s;"):format(row.idx, MySQLite.SQLStr(map), MySQLite.SQLStr(row.job))) + end + end + end) +end + +function DarkRP.storeDoorGroup(ent, group) + if not ent:CreatedByMap() then return end + local map = MySQLite.SQLStr(string.lower(game.GetMap())) + local index = ent:doorIndex() + + if group == "" or not group then + MySQLite.query("DELETE FROM darkrp_doorgroups WHERE map = " .. map .. " AND idx = " .. index .. ";") + return + end + + MySQLite.query("REPLACE INTO darkrp_doorgroups VALUES(" .. index .. ", " .. map .. ", " .. MySQLite.SQLStr(group) .. ");"); +end + +function setUpGroupDoors() + local map = MySQLite.SQLStr(string.lower(game.GetMap())) + MySQLite.query("SELECT idx, doorgroup FROM darkrp_doorgroups WHERE map = " .. map, function(data) + if not data then return end + + for _, row in pairs(data) do + local ent = DarkRP.doorIndexToEnt(tonumber(row.idx)) + + if not IsValid(ent) or not ent:isKeysOwnable() then + continue + end + + if not RPExtraTeamDoorIDs[row.doorgroup] then continue end + ent:setDoorGroup(row.doorgroup) + end + end) +end + +hook.Add("PostCleanupMap", "DarkRP.hooks", function() + setUpNonOwnableDoors() + setUpTeamOwnableDoors() + setUpGroupDoors() +end) diff --git a/gamemodes/darkrp/gamemode/modules/base/sv_entityvars.lua b/gamemodes/darkrp/gamemode/modules/base/sv_entityvars.lua new file mode 100644 index 0000000..583c95d --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sv_entityvars.lua @@ -0,0 +1,304 @@ +local meta = FindMetaTable("Player") + +DarkRP.ServerDarkRPVars = DarkRP.ServerDarkRPVars or {} +DarkRP.ServerPrivateDarkRPVars = DarkRP.ServerPrivateDarkRPVars or {} + +--[[--------------------------------------------------------------------------- +Pooled networking strings +---------------------------------------------------------------------------]] +util.AddNetworkString("DarkRP_InitializeVars") +util.AddNetworkString("DarkRP_PlayerVar") +util.AddNetworkString("DarkRP_PlayerVarRemoval") +util.AddNetworkString("DarkRP_DarkRPVarDisconnect") + +--[[--------------------------------------------------------------------------- +Player vars +---------------------------------------------------------------------------]] + +local warningsShown = {} +local function checkDarkRPVarRegistration(name) + local DarkRPVar = DarkRP.RegisteredDarkRPVars[name] + if DarkRPVar then return end + + if warningsShown[name] then return end + warningsShown[name] = true + + DarkRP.errorNoHalt(string.format([[Warning! DarkRPVar '%s' wasn't registered! + Please contact the author of the DarkRP Addon to fix this. + Until this is fixed you don't need to worry about anything. Everything will keep working. + It's just that registering DarkRPVars would make DarkRP faster.]], name), 4) +end + +--[[--------------------------------------------------------------------------- +Remove a player's DarkRPVar +---------------------------------------------------------------------------]] +function meta:removeDarkRPVar(var, target) + local vars = DarkRP.ServerDarkRPVars[self] + hook.Call("DarkRPVarChanged", nil, self, var, vars and vars[var], nil) + target = target or player.GetAll() + + DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {} + DarkRP.ServerDarkRPVars[self][var] = nil + + checkDarkRPVarRegistration(var) + + net.Start("DarkRP_PlayerVarRemoval") + net.WriteUInt(self:UserID(), 16) + DarkRP.writeNetDarkRPVarRemoval(var) + net.Send(target) +end + +--[[--------------------------------------------------------------------------- +Set a player's DarkRPVar +---------------------------------------------------------------------------]] +function meta:setDarkRPVar(var, value, target) + target = target or player.GetAll() + + if value == nil then return self:removeDarkRPVar(var, target) end + + local vars = DarkRP.ServerDarkRPVars[self] + hook.Call("DarkRPVarChanged", nil, self, var, vars and vars[var], value) + + DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {} + DarkRP.ServerDarkRPVars[self][var] = value + + checkDarkRPVarRegistration(var) + + net.Start("DarkRP_PlayerVar") + net.WriteUInt(self:UserID(), 16) + DarkRP.writeNetDarkRPVar(var, value) + net.Send(target) +end + +--[[--------------------------------------------------------------------------- +Set a private DarkRPVar +---------------------------------------------------------------------------]] +function meta:setSelfDarkRPVar(var, value) + DarkRP.ServerPrivateDarkRPVars[self] = DarkRP.ServerPrivateDarkRPVars[self] or {} + DarkRP.ServerPrivateDarkRPVars[self][var] = true + + self:setDarkRPVar(var, value, self) +end + +--[[--------------------------------------------------------------------------- +Get a DarkRPVar +---------------------------------------------------------------------------]] +function meta:getDarkRPVar(var, fallback) + local vars = DarkRP.ServerDarkRPVars[self] + if vars == nil then return fallback end + + local results = vars[var] + if results == nil then return fallback end + + return results +end + +--[[--------------------------------------------------------------------------- +Backwards compatibility: Set ply.DarkRPVars attribute +---------------------------------------------------------------------------]] +function meta:setDarkRPVarsAttribute() + DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {} + -- With a reference to the table, ply.DarkRPVars should always remain + -- up-to-date. One needs only be careful that DarkRP.ServerDarkRPVars[ply] + -- is never replaced by a different table. + self.DarkRPVars = DarkRP.ServerDarkRPVars[self] +end + + +--[[--------------------------------------------------------------------------- +Send the DarkRPVars to a client +---------------------------------------------------------------------------]] +function meta:sendDarkRPVars() + if self:EntIndex() == 0 then return end + + local plys = player.GetAll() + + net.Start("DarkRP_InitializeVars") + net.WriteUInt(#plys, 8) + for _, target in ipairs(plys) do + net.WriteUInt(target:UserID(), 16) + + local vars = {} + for var, value in pairs(DarkRP.ServerDarkRPVars[target] or {}) do + if self ~= target and (DarkRP.ServerPrivateDarkRPVars[target] or {})[var] then continue end + table.insert(vars, var) + end + + local vars_cnt = #vars + net.WriteUInt(vars_cnt, DarkRP.DARKRP_ID_BITS + 2) -- Allow for three times as many unknown DarkRPVars than the limit + for i = 1, vars_cnt, 1 do + DarkRP.writeNetDarkRPVar(vars[i], DarkRP.ServerDarkRPVars[target][vars[i]]) + end + end + net.Send(self) +end +concommand.Add("_sendDarkRPvars", function(ply) + if ply.DarkRPVarsSent and ply.DarkRPVarsSent > (CurTime() - 3) then return end -- prevent spammers + ply.DarkRPVarsSent = CurTime() + ply:sendDarkRPVars() +end) + +--[[--------------------------------------------------------------------------- +Admin DarkRPVar commands +---------------------------------------------------------------------------]] +local function setRPName(ply, args) + if not args[2] or string.len(args[2]) < 2 or string.len(args[2]) > 30 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "<2/>30")) + return + end + + local name = table.concat(args, " ", 2) + + local target = DarkRP.findPlayer(args[1]) + + if not target then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args[1])) + return + end + + local oldname = target:Nick() + + DarkRP.retrieveRPNames(name, function(taken) + if not IsValid(target) then return end + + if taken then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("unable", "RPname", DarkRP.getPhrase("already_taken"))) + return + end + + DarkRP.storeRPName(target, name) + target:setDarkRPVar("rpname", name) + + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_set_x_name", oldname, name)) + + local nick = "" + if ply:EntIndex() == 0 then + nick = "Console" + else + nick = ply:Nick() + end + DarkRP.notify(target, 0, 4, DarkRP.getPhrase("x_set_your_name", nick, name)) + if ply:EntIndex() == 0 then + DarkRP.log("Console set " .. target:SteamName() .. "'s name to " .. name, Color(30, 30, 30)) + else + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") set " .. target:SteamName() .. "'s name to " .. name, Color(30, 30, 30)) + end + end) +end +DarkRP.definePrivilegedChatCommand("forcerpname", "DarkRP_AdminCommands", setRPName) + +local function freerpname(ply, args) + local name = args ~= "" and args or IsValid(ply) and ply:Nick() or "" + + MySQLite.query(("UPDATE darkrp_player SET rpname = NULL WHERE rpname = %s"):format(MySQLite.SQLStr(name))) + + local nick = IsValid(ply) and ply:Nick() or "Console" + DarkRP.log(("%s has freed the rp name '%s'"):format(nick, name), Color(30, 30, 30)) + DarkRP.notify(ply, 0, 4, ("'%s' has been freed"):format(name)) +end +DarkRP.definePrivilegedChatCommand("freerpname", "DarkRP_AdminCommands", freerpname) + +local function RPName(ply, args) + if ply.LastNameChange and ply.LastNameChange > (CurTime() - 5) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(5 - (CurTime() - ply.LastNameChange)), "/rpname")) + return "" + end + + if not GAMEMODE.Config.allowrpnames then + DarkRP.notify(ply, 1, 6, DarkRP.getPhrase("disabled", "/rpname", "")) + return "" + end + + args = args:find"^%s*$" and '' or args:match"^%s*(.*%S)" + + local canChangeName, reason = hook.Call("CanChangeRPName", GAMEMODE, ply, args) + if canChangeName == false then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/rpname", reason or "")) + return "" + end + + ply:setRPName(args) + ply.LastNameChange = CurTime() + return "" +end +DarkRP.defineChatCommand("rpname", RPName) +DarkRP.defineChatCommand("name", RPName) +DarkRP.defineChatCommand("nick", RPName) + +--[[--------------------------------------------------------------------------- +Setting the RP name +---------------------------------------------------------------------------]] +function meta:setRPName(name, firstRun) + -- Make sure nobody on this server already has this RP name + local lowername = string.lower(tostring(name)) + DarkRP.retrieveRPNames(name, function(taken) + if not IsValid(self) or string.len(lowername) < 2 and not firstrun then return end + -- If we found that this name exists for another player + if taken then + if firstRun then + -- If we just connected and another player happens to be using our steam name as their RP name + -- Put a 1 after our steam name + DarkRP.storeRPName(self, name .. " 1") + DarkRP.notify(self, 0, 12, DarkRP.getPhrase("someone_stole_steam_name")) + else + DarkRP.notify(self, 1, 5, DarkRP.getPhrase("unable", "/rpname", DarkRP.getPhrase("already_taken"))) + return "" + end + else + if not firstRun then -- Don't save the steam name in the database + DarkRP.notifyAll(2, 6, DarkRP.getPhrase("rpname_changed", self:SteamName(), name)) + DarkRP.storeRPName(self, name) + end + end + end) +end + +--[[--------------------------------------------------------------------------- +Maximum entity values +---------------------------------------------------------------------------]] +local maxEntities = {} +function meta:addCustomEntity(entTable) + maxEntities[self] = maxEntities[self] or {} + maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0 + maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] + 1 +end + +function meta:removeCustomEntity(entTable) + maxEntities[self] = maxEntities[self] or {} + maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0 + maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] - 1 +end + +function meta:customEntityLimitReached(entTable) + maxEntities[self] = maxEntities[self] or {} + maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0 + local max = entTable.getMax and entTable.getMax(self) or entTable.max + + return max ~= 0 and maxEntities[self][entTable.cmd] >= max +end + +function meta:customEntityCount(entTable) + local entities = maxEntities[self] + if entities == nil then return 0 end + + entities = entities[entTable.cmd] + if entities == nil then return 0 end + + return entities +end + +-- We use EntityRemoved to clear players of tables, because it is always called +-- after the PlayerDisconnected hook. This is called _after_ the GAMEMODE +-- function, to make sure that all regular hooks can still use DarkRPVars until +-- the very end. See https://github.com/FPtje/DarkRP/pull/3270 +(GAMEMODE or GM).DarkRPPostEntityRemoved = function(_gm, ent) + if not ent:IsPlayer() then return end + + maxEntities[ent] = nil + DarkRP.ServerDarkRPVars[ent] = nil + DarkRP.ServerPrivateDarkRPVars[ent] = nil + + net.Start("DarkRP_DarkRPVarDisconnect") + net.WriteUInt(ent:UserID(), 16) + net.Broadcast() +end diff --git a/gamemodes/darkrp/gamemode/modules/base/sv_gamemode_functions.lua b/gamemodes/darkrp/gamemode/modules/base/sv_gamemode_functions.lua new file mode 100644 index 0000000..f3f74c8 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sv_gamemode_functions.lua @@ -0,0 +1,1146 @@ +local entMeta = FindMetaTable("Entity") + +-- Maintains entities that are to be removed after disconnect +local queuedForRemoval = {} + +--[[--------------------------------------------------------------------------- +DarkRP hooks +---------------------------------------------------------------------------]] +function GM:Initialize() + self.Sandbox.Initialize(self) +end + +function GM:playerBuyDoor(ply, ent) + if ply:getJobTable().hobo then + return false, DarkRP.getPhrase("door_hobo_unable") + end + + return true +end + +function GM:getDoorCost(ply, ent) + return GAMEMODE.Config.doorcost ~= 0 and GAMEMODE.Config.doorcost or 30 +end + +function GM:getVehicleCost(ply, ent) + return GAMEMODE.Config.vehiclecost ~= 0 and GAMEMODE.Config.vehiclecost or 40 +end + +local disallowedNames = {["ooc"] = true, ["shared"] = true, ["world"] = true, ["world prop"] = true} +function GM:CanChangeRPName(ply, RPname) + if disallowedNames[string.lower(RPname)] then return false, DarkRP.getPhrase("forbidden_name") end + if not string.match(RPname, "^[a-zA-ZЀ-џ0-9 ]+$") then return false, DarkRP.getPhrase("illegal_characters") end + + local len = string.len(RPname) + if len > 30 then return false, DarkRP.getPhrase("too_long") end + if len < 3 then return false, DarkRP.getPhrase("too_short") end +end + +function GM:canDemote(ply, target, reason) + +end + +function GM:canVote(ply, vote) + +end + +function GM:playerWalletChanged(ply, amount) + +end + +function GM:playerGetSalary(ply, amount) + +end + +function GM:DarkRPVarChanged(ply, var, oldvar, newvalue) + +end + +function GM:playerBoughtVehicle(ply, ent, cost) + +end + +function GM:playerBoughtDoor(ply, ent, cost) + +end + +function GM:canDropWeapon(ply, weapon) + if not IsValid(weapon) then return false end + local class = string.lower(weapon:GetClass()) + + if not GAMEMODE.Config.dropspawnedweapons then + local jobTable = ply:getJobTable() + if jobTable.weapons and table.HasValue(jobTable.weapons, class) then return false end + end + + if self.Config.DisallowDrop[class] then return false end + + if not GAMEMODE.Config.restrictdrop then return true end + + for _, v in pairs(CustomShipments) do + if v.entity ~= class then continue end + + return true + end + + return false +end + +function GM:DatabaseInitialized() + DarkRP.initDatabase() +end + +function GM:canSeeLogMessage(ply, message, colour) + return true +end + +function GM:canEarnNPCKillPay(ply, npc) + return GAMEMODE.Config.npckillpay > 0 +end + +function GM:calculateNPCKillPay(ply, npc) + -- A NPC spawned by an addon might be worth more money than the default + if npc.KillValue then + return npc.KillValue + end + return GAMEMODE.Config.npckillpay +end + +--[[--------------------------------------------------------- + Gamemode functions + ---------------------------------------------------------]] + +function GM:PlayerSpawnProp(ply, model) + -- No prop spawning means no prop spawning. + local allowed = GAMEMODE.Config.propspawning + + if not allowed then return false end + if ply:isArrested() then return false end + + model = string.gsub(tostring(model), "\\", "/") + model = string.gsub(tostring(model), "//", "/") + + local jobTable = ply:getJobTable() + if jobTable.PlayerSpawnProp then + jobTable.PlayerSpawnProp(ply, model) + end + + return self.Sandbox.PlayerSpawnProp(self, ply, model) +end + +function GM:PlayerSpawnedProp(ply, model, ent) + self.Sandbox.PlayerSpawnedProp(self, ply, model, ent) + ent.SID = ply.SID + ent:CPPISetOwner(ply) + + local phys = ent:GetPhysicsObject() + if IsValid(phys) then + ent.RPOriginalMass = phys:GetMass() + end + + if GAMEMODE.Config.proppaying then + if ply:canAfford(GAMEMODE.Config.propcost) then + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("deducted_money", DarkRP.formatMoney(GAMEMODE.Config.propcost))) + ply:addMoney(-GAMEMODE.Config.propcost) + else + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_money", DarkRP.formatMoney(GAMEMODE.Config.propcost))) + SafeRemoveEntity(ent) + return false + end + end +end + + +local function checkAdminSpawn(ply, configVar, errorStr) + local config = GAMEMODE.Config[configVar] + + if (config == true or config == 1) and ply:EntIndex() ~= 0 and not ply:IsAdmin() then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("need_admin", DarkRP.getPhrase(errorStr) or errorStr)) + return false + elseif config == 2 and ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("need_sadmin", DarkRP.getPhrase(errorStr) or errorStr)) + return false + elseif config == 3 and ply:EntIndex() ~= 0 then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("disabled", DarkRP.getPhrase(errorStr) or errorStr, DarkRP.getPhrase("see_settings"))) + return false + end + + return true +end + +function GM:PlayerSpawnSENT(ply, class) + return checkAdminSpawn(ply, "adminsents", "gm_spawnsent") and self.Sandbox.PlayerSpawnSENT(self, ply, class) and not ply:isArrested() +end + +function GM:PlayerSpawnedSENT(ply, ent) + self.Sandbox.PlayerSpawnedSENT(self, ply, ent) + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned SENT " .. ent:GetClass(), Color(255, 255, 0)) +end + +local function canSpawnWeapon(ply) + if (GAMEMODE.Config.adminweapons == 0 and ply:IsAdmin()) or + (GAMEMODE.Config.adminweapons == 1 and ply:IsSuperAdmin()) or + -- Can't use 2 to maintain compatibility + (GAMEMODE.Config.adminweapons == 3) then + return true + end + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cant_spawn_weapons")) + + return false +end + +function GM:PlayerSpawnSWEP(ply, class, info) + return canSpawnWeapon(ply) and self.Sandbox.PlayerSpawnSWEP(self, ply, class, info) and not ply:isArrested() +end + +function GM:PlayerGiveSWEP(ply, class, info) + return canSpawnWeapon(ply) and self.Sandbox.PlayerGiveSWEP(self, ply, class, info) and not ply:isArrested() +end + +function GM:PlayerSpawnEffect(ply, model) + return self.Sandbox.PlayerSpawnEffect(self, ply, model) and not ply:isArrested() +end + +function GM:PlayerSpawnVehicle(ply, model, class, info) + return checkAdminSpawn(ply, "adminvehicles", "gm_spawnvehicle") and self.Sandbox.PlayerSpawnVehicle(self, ply, model, class, info) and not ply:isArrested() +end + +function GM:PlayerSpawnedVehicle(ply, ent) + self.Sandbox.PlayerSpawnedVehicle(self, ply, ent) + local vehicleClass = ent.GetVehicleClass and " (" .. ent:GetVehicleClass() .. ")" or "" + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned Vehicle " .. ent:GetClass() .. vehicleClass, Color(255, 255, 0)) +end + +function GM:PlayerSpawnNPC(ply, type, weapon) + return checkAdminSpawn(ply, "adminnpcs", "gm_spawnnpc") and self.Sandbox.PlayerSpawnNPC(self, ply, type, weapon) and not ply:isArrested() +end + +function GM:PlayerSpawnedNPC(ply, ent) + self.Sandbox.PlayerSpawnedNPC(self, ply, ent) + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned NPC " .. ent:GetClass(), Color(255, 255, 0)) +end + +function GM:PlayerSpawnRagdoll(ply, model) + return self.Sandbox.PlayerSpawnRagdoll(self, ply, model) and not ply:isArrested() +end + +function GM:PlayerSpawnedRagdoll(ply, model, ent) + self.Sandbox.PlayerSpawnedRagdoll(self, ply, model, ent) + ent.SID = ply.SID +end + +function GM:EntityRemoved(ent) + self.Sandbox.EntityRemoved(self, ent) + if ent:IsVehicle() then + local found = ent:CPPIGetOwner() + if IsValid(found) then + found.Vehicles = found.Vehicles or 1 + found.Vehicles = found.Vehicles - 1 + end + end + + local owner = ent.Getowning_ent and ent:Getowning_ent() or Player(ent.SID or 0) + if ent.DarkRPItem and IsValid(owner) and not ent.IsPocketing then owner:removeCustomEntity(ent.DarkRPItem) end + if ent.isKeysOwnable and ent:isKeysOwnable() then ent:removeDoorData() end + + -- Quick workaround for the fact that we don't have a hook ordering system + -- built into gmod. + if self.DarkRPPostEntityRemoved then + self.DarkRPPostEntityRemoved(self, ent) + end +end + +function GM:ShowSpare1(ply) + local jobTable = ply:getJobTable() + if jobTable.ShowSpare1 then + return jobTable.ShowSpare1(ply) + end +end + +function GM:ShowSpare2(ply) + local jobTable = ply:getJobTable() + if jobTable.ShowSpare2 then + return jobTable.ShowSpare2(ply) + end +end + +function GM:ShowTeam(ply) +end + +function GM:ShowHelp(ply) +end + +function GM:OnNPCKilled(victim, ent, weapon) + -- If something killed the npc + if not ent then return end + + if ent:IsVehicle() and ent:GetDriver():IsPlayer() then ent = ent:GetDriver() end + + -- If it wasn't a player directly, find out who owns the prop that did the killing + if not ent:IsPlayer() then + ent = Player(tonumber(ent.SID) or 0) + end + + -- If we know by now who killed the NPC, pay them. + if IsValid(ent) and hook.Call("canEarnNPCKillPay", GAMEMODE, ent, victim) then + local amount = hook.Call("calculateNPCKillPay", GAMEMODE, ent, victim) + ent:addMoney(amount) + DarkRP.notify(ent, 0, 4, DarkRP.getPhrase("npc_killpay", DarkRP.formatMoney(amount))) + end +end + +function GM:KeyPress(ply, code) + self.Sandbox.KeyPress(self, ply, code) +end + +-- IsInRoom function to see if the player is in the same room. +local roomTrResult = {} +local roomTr = {output = roomTrResult} +local function IsInRoom(listenerShootPos, talkerShootPos, talker) + roomTr.start = talkerShootPos + roomTr.endpos = listenerShootPos + -- Listener needs not be ignored as that's the end of the trace + roomTr.filter = talker + roomTr.collisiongroup = COLLISION_GROUP_WORLD + roomTr.mask = MASK_SOLID_BRUSHONLY + util.TraceLine(roomTr) + + return not roomTrResult.HitWorld +end + +local threed = GM.Config.voice3D +local vrad = GM.Config.voiceradius +local dynv = GM.Config.dynamicvoice +local deadv = GM.Config.deadvoice +local voiceDistance = GM.Config.voiceDistance * GM.Config.voiceDistance +local DrpCanHear = {} + +-- Recreate DrpCanHear after Lua Refresh +-- This prevents an indexing nil error in PlayerCanHearPlayersVoice +for _, ply in ipairs(player.GetAll()) do + DrpCanHear[ply] = {} +end + +local gridSize = GM.Config.voiceDistance -- Grid cell size is equal to the size of the radius of player talking +local floor = math.floor -- Caching floor as we will need to use it a lot + +-- Grid based position check +local grid +-- Translate player to grid coordinates. The first table maps players to x +-- coordinates, the second table maps players to y coordinates. +local plyToGrid = { + {}, + {} +} + +-- Set DarkRP.voiceCheckTimeDelay before DarkRP is loaded to set the time +-- between player voice radius checks. +DarkRP.voiceCheckTimeDelay = DarkRP.voiceCheckTimeDelay or 0.3 +timer.Create("DarkRPCanHearPlayersVoice", DarkRP.voiceCheckTimeDelay, 0, function() + -- Voiceradius is off, everyone can hear everyone + if not vrad then + return + end + + local players = player.GetHumans() + + -- Clear old values + plyToGrid[1] = {} + plyToGrid[2] = {} + grid = {} + + local plyPos = {} + local eyePos = {} + + -- Get the grid position of every player O(N) + for _, ply in ipairs(players) do + local pos = ply:GetPos() + plyPos[ply] = pos + eyePos[ply] = ply:EyePos() + local x = floor(pos.x / gridSize) + local y = floor(pos.y / gridSize) + + local row = grid[x] or {} + local cell = row[y] or {} + + table.insert(cell, ply) + row[y] = cell + grid[x] = row + + plyToGrid[1][ply] = x + plyToGrid[2][ply] = y + + DrpCanHear[ply] = {} -- Initialize output variable + end + + -- Check all neighbouring cells for every player. + -- We are only checking in 1 direction to avoid duplicate check of cells + for _, ply1 in ipairs(players) do + local gridX = plyToGrid[1][ply1] + local gridY = plyToGrid[2][ply1] + local ply1Pos = plyPos[ply1] + local ply1EyePos = eyePos[ply1] + + for i = 0, 3 do + local vOffset = 1 - ((i >= 3) and 1 or 0) + local hOffset = -(i % 3-1) + local x = gridX + hOffset + local y = gridY + vOffset + + local row = grid[x] + if not row then continue end + + local cell = row[y] + if not cell then continue end + + for _, ply2 in ipairs(cell) do + local canTalk = + ply1Pos:DistToSqr(plyPos[ply2]) < voiceDistance and -- voiceradius is on and the two are within hearing distance + (not dynv or IsInRoom(ply1EyePos, eyePos[ply2], ply2)) -- Dynamic voice is on and players are in the same room + + DrpCanHear[ply1][ply2] = canTalk and (deadv or ply2:Alive()) + DrpCanHear[ply2][ply1] = canTalk and (deadv or ply1:Alive()) -- Take advantage of the symmetry + end + end + end + + -- Doing a pass-through inside every cell to compute the interactions inside of the cells. + -- Each grid check is O(N(N+1)/2) where N is the number of players inside the cell. + for _, row in pairs(grid) do + for _, cell in pairs(row) do + local count = #cell + for i = 1, count do + local ply1 = cell[i] + for j = i + 1, count do + local ply2 = cell[j] + local canTalk = + plyPos[ply1]:DistToSqr(plyPos[ply2]) < voiceDistance and -- voiceradius is on and the two are within hearing distance + (not dynv or IsInRoom(eyePos[ply1], eyePos[ply2], ply2)) -- Dynamic voice is on and players are in the same room + + DrpCanHear[ply1][ply2] = canTalk and (deadv or ply2:Alive()) + DrpCanHear[ply2][ply1] = canTalk and (deadv or ply1:Alive()) -- Take advantage of the symmetry + end + end + end + end +end) + +hook.Add("PlayerDisconnected", "DarkRPCanHear", function(ply) + DrpCanHear[ply] = nil -- Clear to avoid memory leaks +end) + +function GM:PlayerCanHearPlayersVoice(listener, talker) + if not deadv and not talker:Alive() then return false end + + return not vrad or DrpCanHear[listener][talker] == true, threed +end + +function GM:CanTool(ply, trace, mode) + if not self.Sandbox.CanTool(self, ply, trace, mode) then return false end + + local ent = trace.Entity + if IsValid(ent) then + if ent.onlyremover then + if mode == "remover" then + return ply:IsAdmin() or ply:IsSuperAdmin() + else + return false + end + end + + if ent.nodupe and (mode == "weld" or + mode == "weld_ez" or + mode == "spawner" or + mode == "duplicator" or + mode == "adv_duplicator") then + return false + end + + if ent:IsVehicle() and mode == "nocollide" and not GAMEMODE.Config.allowvnocollide then + return false + end + end + return true +end + +function GM:CanPlayerSuicide(ply) + if ply.IsSleeping then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "suicide", "")) + return false + end + if ply:isArrested() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "suicide", "")) + return false + end + if GAMEMODE.Config.wantedsuicide and ply:getDarkRPVar("wanted") then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "suicide", "")) + return false + end + + local jobTable = ply:getJobTable() + if jobTable.CanPlayerSuicide then + return jobTable.CanPlayerSuicide(ply) + end + return true +end + +function GM:CanDrive(ply, ent) + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("drive_disabled")) + return false -- Disabled until people can't minge with it anymore +end + +function GM:CanProperty(ply, property, ent) + if self.Config.allowedProperties[property] and ent:CPPICanTool(ply, "remover") then + return true + end + + if property == "persist" and ply:IsSuperAdmin() then + return true + end + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("property_disabled")) + return false -- Disabled until antiminge measure is found +end + +function GM:PlayerShouldTaunt(ply, actid) + return GAMEMODE.Config.allowActs +end + +function GM:DoPlayerDeath(ply, attacker, dmginfo, ...) + local weapon = ply:GetActiveWeapon() + local canDrop = hook.Call("canDropWeapon", self, ply, weapon) + + if (GAMEMODE.Config.dropweapondeath or ply.dropWeaponOnDeath) and weapon:IsValid() and canDrop then + ply:dropDRPWeapon(weapon) + end + self.Sandbox.DoPlayerDeath(self, ply, attacker, dmginfo, ...) +end + +function GM:PlayerDeath(ply, weapon, killer) + local jobTable = ply:getJobTable() + if jobTable.PlayerDeath then + jobTable.PlayerDeath(ply, weapon, killer) + end + + if GAMEMODE.Config.deathblack and not ply.blackScreen then + ply.blackScreen = true + SendUserMessage("blackScreen", ply, true) + end + + if weapon:IsVehicle() and weapon:GetDriver():IsPlayer() then killer = weapon:GetDriver() end + + if GAMEMODE.Config.showdeaths then + self.Sandbox.PlayerDeath(self, ply, weapon, killer) + end + + ply:Extinguish() + + ply:ExitVehicle() + + if ply:isArrested() and not GAMEMODE.Config.respawninjail then + -- If the player died in jail, make sure they can't respawn until their jail sentence is over + -- NextSpawnTime is set to CurTime() on unarrest + ply.NextSpawnTime = math.huge + DarkRP.printMessageAll(HUD_PRINTCENTER, DarkRP.getPhrase("died_in_jail", ply:Nick())) + DarkRP.notify(ply, 4, 4, DarkRP.getPhrase("dead_in_jail")) + else + -- Normal death, respawning. + ply.NextSpawnTime = CurTime() + math.Clamp(GAMEMODE.Config.respawntime, 0, 10) + end + ply.DeathPos = ply:GetPos() + + if GAMEMODE.Config.dropmoneyondeath then + local amount = GAMEMODE.Config.deathfee + if not ply:canAfford(GAMEMODE.Config.deathfee) then + amount = ply:getDarkRPVar("money") + end + + if amount > 0 then + ply:addMoney(-amount) + DarkRP.createMoneyBag(ply:GetPos(), amount) + end + end + + if IsValid(ply) and (ply ~= killer or ply.Slayed) and not ply:isArrested() then + if not GAMEMODE.Config.wantedrespawn then + ply:setDarkRPVar("wanted", nil) + end + ply.DeathPos = nil + ply.Slayed = false + end + + ply.ConfiscatedWeapons = nil + + local KillerName = (killer:IsPlayer() and killer:Nick()) or tostring(killer) + local WeaponName = IsValid(weapon) and ((weapon:IsPlayer() and weapon:GetActiveWeapon():IsValid() and weapon:GetActiveWeapon():GetClass()) or weapon:GetClass()) or "unknown" + + if killer == ply then + KillerName = "Themself" + WeaponName = "suicide trick" + end + + DarkRP.log(ply:Nick() .. " was killed by " .. KillerName .. " with a " .. WeaponName, Color(255, 190, 0)) +end + +local adminCopWeapons = { + ["door_ram"] = true, + ["arrest_stick"] = true, + ["unarrest_stick"] = true, + ["stunstick"] = true, + ["weaponchecker"] = true, +} +function GM:PlayerCanPickupWeapon(ply, weapon) + if ply:isArrested() then return false end + if weapon.PlayerUse == false then return false end + local weaponClass = weapon:GetClass() + if ply:IsAdmin() and GAMEMODE.Config.AdminsCopWeapons and adminCopWeapons[weaponClass] then return true end + + local jobTable = ply:getJobTable() + if jobTable.PlayerCanPickupWeapon then + local val = jobTable.PlayerCanPickupWeapon(ply, weapon) + + return val == nil or val + end + + if GAMEMODE.Config.license and not ply:getDarkRPVar("HasGunlicense") and not ply.RPLicenseSpawn then + if GAMEMODE.NoLicense[string.lower(weaponClass)] or not weapon:IsWeapon() then + return true + end + return false + end + + return true +end + +function GM:PlayerSetModel(ply) + local jobTable = ply:getJobTable() + + -- Invalid job, return to Sandbox behaviour + if not jobTable then return self.Sandbox.PlayerSetModel(ply) end + + if jobTable.PlayerSetModel then + local model = jobTable.PlayerSetModel(ply) + if model then ply:SetModel(model) return end + end + + local EndModel = "" + if GAMEMODE.Config.enforceplayermodel then + if istable(jobTable.model) then + local ChosenModel = string.lower(ply:getPreferredModel(ply:Team()) or "") + + local found + for _, Models in pairs(jobTable.model) do + if ChosenModel == string.lower(Models) then + EndModel = Models + found = true + break + end + end + + if not found then + EndModel = jobTable.model[math.random(#jobTable.model)] + end + else + EndModel = jobTable.model + end + + ply:SetModel(EndModel) + else + local cl_playermodel = ply:GetInfo("cl_playermodel") + local modelname = player_manager.TranslatePlayerModel(cl_playermodel) + ply:SetModel(ply:getPreferredModel(ply:Team()) or modelname) + end + + self.Sandbox.PlayerSetModel(self, ply) + + ply:SetupHands() +end + +local function initPlayer(ply) + timer.Simple(5, function() + if not IsValid(ply) then return end + + if GetGlobalBool("DarkRP_Lockdown") then + SetGlobalBool("DarkRP_Lockdown", true) -- so new players who join know there's a lockdown, is this bug still there? + end + end) + + ply:initiateTax() + + ply:updateJob(team.GetName(GAMEMODE.DefaultTeam)) + ply:setSelfDarkRPVar("salary", DarkRP.retrieveSalary(ply)) + ply.LastJob = nil -- so players don't have to wait to get a job after joining + + ply.Ownedz = {} + + ply.LastLetterMade = CurTime() - 61 + ply.LastVoteCop = CurTime() - 61 + + ply:SetTeam(GAMEMODE.DefaultTeam) + ply.DarkRPInitialised = true + + -- Whether or not a player is being prevented from joining + -- a specific team for a certain length of time + if GAMEMODE.Config.restrictallteams then + for i = 1, #RPExtraTeams do + ply:teamBan(i, 0) + end + end +end + +local function restoreReconnectedEnts(ply) + local sid = ply:SteamID64() + if not queuedForRemoval[sid] then return end + + timer.Remove("DarkRP_removeDisconnected_" .. sid) + + for _, e in pairs(queuedForRemoval[sid]) do + if not IsValid(e) then continue end + + e.SID = ply.SID + + if e.Setowning_ent then + e:Setowning_ent(ply) + end + + -- Some entities (e.g. vehicles) have an SID, but do not have a DarkRPItem + if e.DarkRPItem then + ply:addCustomEntity(e.DarkRPItem) + end + end + + queuedForRemoval[sid] = nil +end + +function GM:PlayerInitialSpawn(ply) + self.Sandbox.PlayerInitialSpawn(self, ply) + -- Initialize DrpCanHear for player (used for voice radius check) + DrpCanHear[ply] = {} + + local sid = ply:SteamID() + DarkRP.log(ply:Nick() .. " (" .. sid .. ") has joined the game", Color(0, 130, 255)) + ply:setDarkRPVarsAttribute() + ply:restorePlayerData() + initPlayer(ply) + ply.SID = ply:UserID() + + timer.Simple(1, function() + if not IsValid(ply) then return end + local group = GAMEMODE.Config.DefaultPlayerGroups[sid] + if group then + ply:SetUserGroup(group) + end + end) + + restoreReconnectedEnts(ply) +end + +function GM:PlayerSelectSpawn(ply) + local spawn = self.Sandbox.PlayerSelectSpawn(self, ply) + + local jobTable = ply:getJobTable() + if jobTable.PlayerSelectSpawn then + jobTable.PlayerSelectSpawn(ply, spawn) + end + + local POS + if spawn and spawn.GetPos then + POS = spawn:GetPos() + else + POS = ply:GetPos() + end + + local CustomSpawnPos = DarkRP.retrieveTeamSpawnPos(ply:Team()) + if GAMEMODE.Config.customspawns and not ply:isArrested() and CustomSpawnPos and next(CustomSpawnPos) ~= nil then + POS = CustomSpawnPos[math.random(1, #CustomSpawnPos)] + end + + -- Spawn where died in certain cases + if GAMEMODE.Config.strictsuicide and ply.DeathPos then + POS = ply.DeathPos + end + + if ply:isArrested() then + POS = DarkRP.retrieveJailPos() or ply.DeathPos -- If we can't find a jail pos then we'll use where they died as a last resort + end + + -- Make sure the player doesn't get stuck in something + + local _, hull = ply:GetHull() + + POS = DarkRP.findEmptyPos(POS, {ply}, 600, 30, hull) + + return spawn, POS +end + +local oldPlyColor +local function disableBabyGod(ply) + if not IsValid(ply) or not ply.Babygod then return end + + ply.Babygod = nil + ply:SetRenderMode(RENDERMODE_NORMAL) + ply:GodDisable() + + -- Don't reinstate the SetColor function + -- if there are still players who are babygodded + local reinstateOldColor = true + + for _, p in ipairs(player.GetAll()) do + reinstateOldColor = reinstateOldColor and p.Babygod == nil + end + + if reinstateOldColor then + entMeta.SetColor = oldPlyColor + oldPlyColor = nil + end + + ply:SetColor(ply.babyGodColor or color_white) + + ply.babyGodColor = nil +end + +local function enableBabyGod(ply) + timer.Remove(ply:EntIndex() .. "babygod") + + ply.Babygod = true + ply:GodEnable() + ply.babyGodColor = ply:GetColor() + ply:SetRenderMode(RENDERMODE_TRANSALPHA) + + if not oldPlyColor then + oldPlyColor = entMeta.SetColor + entMeta.SetColor = function(p, c, ...) + if not p.Babygod then return oldPlyColor(p, c, ...) end + + p.babyGodColor = c + oldPlyColor(p, Color(c.r, c.g, c.b, 100)) + end + end + + ply:SetColor(ply.babyGodColor) + timer.Create(ply:EntIndex() .. "babygod", GAMEMODE.Config.babygodtime or 0, 1, fp{disableBabyGod, ply}) +end + +function GM:PlayerSpawn(ply) + if not ply.DarkRPInitialised then + DarkRP.errorNoHalt( + string.format("DarkRP was unable to introduce player \"%s\" to the game. Expect further errors and shit generally being fucked!", + IsValid(ply) and ply:Nick() or "unknown"), + 1, + { + "This error most likely does not stand on its own, and previous serverside errors have a very good chance of telling you the cause.", + "Note that errors from another addon could cause this. Specifically when they're thrown during 'PlayerInitialSpawn'.", + "This error can also be caused by some other addon returning a value in 'PlayerInitialSpawn', though that is less likely.", + "Errors in your DarkRP configuration (jobs, shipments, etc.) could also cause this. Earlier errors should tell you when this is the case." + } + ) + end + + ply:CrosshairEnable() + ply:UnSpectate() + + -- Kill any colormod + if ply.blackScreen then + ply.blackScreen = false + SendUserMessage("blackScreen", ply, false) + end + + if GAMEMODE.Config.babygod and not ply.IsSleeping and not ply.Babygod then + enableBabyGod(ply) + end + ply.IsSleeping = false + + ply:Extinguish() + + for i = 0, 2 do + local vm = ply:GetViewModel(i) + + if IsValid(vm) then + vm:Extinguish() + end + end + + if ply.demotedWhileDead then + ply.demotedWhileDead = nil + + local demoteTeam = hook.Call("demoteTeam", nil, ply) or GAMEMODE.DefaultTeam + ply:changeTeam(demoteTeam, true) + ply:setDarkRPVar("job", team.GetName(demoteTeam)) + end + + local jobTable = ply:getJobTable() + + player_manager.SetPlayerClass(ply, jobTable.playerClass or "player_darkrp") + + ply:applyPlayerClassVars(true) + + player_manager.RunClass(ply, "Spawn") + + hook.Call("PlayerLoadout", self, ply) + hook.Call("PlayerSetModel", self, ply) + + local ent, pos = hook.Call("PlayerSelectSpawn", self, ply) + ply:SetPos(pos or ent:GetPos()) + + if jobTable.PlayerSpawn then + jobTable.PlayerSpawn(ply) + end + + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned") +end + +function GM:PlayerLoadout(ply) + self.Sandbox.PlayerLoadout(self, ply) + + if ply:isArrested() then return end + + ply.RPLicenseSpawn = true + timer.Simple(1, function() + if not IsValid(ply) then return end + ply.RPLicenseSpawn = false + end) + + local jobTable = ply:getJobTable() + + for _, v in pairs(jobTable.weapons or {}) do + ply:Give(v) + end + + if jobTable.PlayerLoadout then + local val = jobTable.PlayerLoadout(ply) + if val == true then + ply:SwitchToDefaultWeapon() + return + end + end + + if jobTable.ammo then + for k, v in pairs(jobTable.ammo) do + ply:SetAmmo(v, k) + end + end + + for _, v in pairs(self.Config.DefaultWeapons) do + ply:Give(v) + end + + CAMI.PlayerHasAccess(ply, "DarkRP_GetAdminWeapons", function(access) + if not access or not IsValid(ply) then return end + + for _, v in pairs(GAMEMODE.Config.AdminWeapons) do + ply:Give(v) + end + + if not GAMEMODE.Config.AdminsCopWeapons then return end + + ply:Give("door_ram") + ply:Give("arrest_stick") + ply:Give("unarrest_stick") + ply:Give("stunstick") + ply:Give("weaponchecker") + end) + + ply:SwitchToDefaultWeapon() +end + +--[[--------------------------------------------------------------------------- +Remove with a delay if the player doesn't rejoin before the timer has run out +---------------------------------------------------------------------------]] +local function removeDelayed(entList, ply) + local removedelay = GAMEMODE.Config.entremovedelay + + if removedelay <= 0 then + for _, e in pairs(entList) do + SafeRemoveEntity(e) + end + + return + end + + local sid = ply:SteamID64() + queuedForRemoval[sid] = entList + + timer.Create("DarkRP_removeDisconnected_" .. sid, removedelay, 1, function() + for _, e in pairs(queuedForRemoval[sid] or {}) do + SafeRemoveEntity(e) + end + + queuedForRemoval[sid] = nil + end) +end + +-- Collect entities that are to be removed +local function collectRemoveEntities(ply) + if not GAMEMODE.Config.removeondisconnect then return {} end + + local collect = {} + -- Get the classes of entities to remove + local remClasses = {} + for _, customEnt in pairs(DarkRPEntities) do + remClasses[string.lower(customEnt.ent)] = true + end + + local sid = ply.SID + for _, v in ipairs(ents.GetAll()) do + if v.SID ~= sid or not v:IsVehicle() and not remClasses[string.lower(v:GetClass() or "")] then continue end + + table.insert(collect, v) + end + + if not ply:isMayor() then return collect end + + for _, ent in pairs(ply.lawboards or {}) do + if not IsValid(ent) then continue end + table.insert(collect, ent) + end + + return collect +end + +function GM:PlayerDisconnected(ply) + self.Sandbox.PlayerDisconnected(self, ply) + timer.Remove(ply:SteamID64() .. "jobtimer") + timer.Remove(ply:SteamID64() .. "propertytax") + + local isMayor = ply:isMayor() + + local remList = collectRemoveEntities(ply) + removeDelayed(remList, ply) + + DarkRP.destroyQuestionsWithEnt(ply) + DarkRP.destroyVotesWithEnt(ply) + + if isMayor and GetGlobalBool("DarkRP_LockDown") then -- Stop the lockdown + DarkRP.unLockdown(ply) + end + + if isMayor and GAMEMODE.Config.shouldResetLaws then + DarkRP.resetLaws() + end + + if IsValid(ply.SleepRagdoll) then + ply.SleepRagdoll:Remove() + end + + ply:keysUnOwnAll() + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") disconnected", Color(0, 130, 255)) + + local agenda = ply:getAgendaTable() + + -- Clear agenda + if agenda and ply:Team() == agenda.Manager and team.NumPlayers(ply:Team()) <= 1 then + agenda.text = nil + for _, v in ipairs(player.GetAll()) do + if v:getAgendaTable() ~= agenda then continue end + v:setSelfDarkRPVar("agenda", agenda.text) + end + end + + local jobTable = ply:getJobTable() + if jobTable.PlayerDisconnected then + jobTable.PlayerDisconnected(ply) + end +end + +function GM:GetFallDamage(ply, flFallSpeed) + if GetConVar("mp_falldamage"):GetBool() or GAMEMODE.Config.realisticfalldamage then + if GAMEMODE.Config.falldamagedamper then return flFallSpeed / GAMEMODE.Config.falldamagedamper else return flFallSpeed / 15 end + else + if GAMEMODE.Config.falldamageamount then return GAMEMODE.Config.falldamageamount else return 10 end + end +end + +local function fuckQAC() + local netRecs = {"Debug1", "Debug2", "checksaum", "gcontrol_vars", "control_vars", "QUACK_QUACK_MOTHER_FUCKER"} + for _, v in ipairs(netRecs) do + net.Receivers[v] = fn.Id + end +end + +function GM:InitPostEntity() + self.InitPostEntityCalled = true + + local physData = physenv.GetPerformanceSettings() + physData.MaxVelocity = 2000 + physData.MaxAngularVelocity = 3636 + + physenv.SetPerformanceSettings(physData) + + -- Scriptenforcer enabled by default? Fuck you, not gonna happen. + if not GAMEMODE.Config.disallowClientsideScripts then + game.ConsoleCommand("sv_allowcslua 1\n") + timer.Simple(1, fuckQAC) -- Also, fuck QAC which bans innocent people when allowcslua = 1 + end + game.ConsoleCommand("physgun_DampingFactor 0.9\n") + game.ConsoleCommand("sv_sticktoground 0\n") + game.ConsoleCommand("sv_airaccelerate 1000\n") + -- sv_alltalk must be 0 + -- Note, everyone will STILL hear everyone UNLESS GM.Config.voiceradius is set to true + -- This will fix the GM.Config.voiceradius not working + game.ConsoleCommand("sv_alltalk 0\n") + + if GAMEMODE.Config.unlockdoorsonstart then + for _, v in ipairs(ents.GetAll()) do + if not v:isDoor() then continue end + v:Fire("unlock", "", 0) + end + end +end +timer.Simple(0.1, function() + if not GAMEMODE.InitPostEntityCalled then + GAMEMODE:InitPostEntity() + end +end) + +function GM:loadCustomDarkRPItems() + -- Error when the default team isn't set + if not GAMEMODE.DefaultTeam or not RPExtraTeams[GAMEMODE.DefaultTeam] then + -- Re-set to first available team to hopefully prevent further errors. + -- Because this error is more important than any that follow because of it. + GAMEMODE.DefaultTeam = next(RPExtraTeams) + + local hints = { + "This may happen when you disable the default citizen job. Make sure you update GAMEMODE.DefaultTeam to the new default team.", + "GAMEMODE.DefaultTeam may be set to a job that does not exist anymore. Did you remove the job you had set to default?", + "The error being in jobs.lua is a guess. This is usually right, but the problem might lie somewhere else." + } + + -- Gotta be totally clear here + local stack = "\tjobs.lua, settings.lua, disabled_defaults.lua or any of your other custom files." + DarkRP.error("GAMEMODE.DefaultTeam is not set to an existing job.", 1, hints, "lua/darkrp_customthings/jobs.lua", -1, stack) + end +end + +function GM:PlayerLeaveVehicle(ply, vehicle) + if GAMEMODE.Config.autovehiclelock and vehicle:isKeysOwnedBy(ply) then + vehicle:keysLock() + end + self.Sandbox.PlayerLeaveVehicle(self, ply, vehicle) +end + +local function ClearDecals() + if GAMEMODE.Config.decalcleaner then + for _, p in ipairs(player.GetAll()) do + p:ConCommand("r_cleardecals") + end + end +end +timer.Create("RP_DecalCleaner", GM.Config.decaltimer, 0, ClearDecals) + +function GM:PlayerSpray() + return not GAMEMODE.Config.allowsprays +end + +function GM:GravGunOnPickedUp(ply, ent, ...) + self.Sandbox.GravGunOnPickedUp(self, ply, ent, ...) + -- Keeping track of who is holding an entity is done to make sure the entity + -- cannot be pocketed. This is because holding an entity with the gravgun + -- changes some properties. One such property is setting the mass to 1, + -- causing the mass check of the pocket to always succeed. + ent.DarkRPBeingGravGunHeldBy = ply +end + +function GM:GravGunOnDropped(ply, ent, ...) + self.Sandbox.GravGunOnDropped(self, ply, ent, ...) + -- See comment at GravGunOnPickedUp + ent.DarkRPBeingGravGunHeldBy = nil +end diff --git a/gamemodes/darkrp/gamemode/modules/base/sv_interface.lua b/gamemodes/darkrp/gamemode/modules/base/sv_interface.lua new file mode 100644 index 0000000..e5aafad --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sv_interface.lua @@ -0,0 +1,1325 @@ +DarkRP.initDatabase = DarkRP.stub{ + name = "initDatabase", + description = "Initialize the DarkRP database.", + parameters = { + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.storeRPName = DarkRP.stub{ + name = "storeRPName", + description = "Store an RP name in the database.", + parameters = { + { + name = "ply", + description = "The player that gets the RP name.", + type = "Player", + optional = false + }, + { + name = "name", + description = "The new name of the player.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.retrieveRPNames = DarkRP.stub{ + name = "retrieveRPNames", + description = "Whether a given RP name is taken by someone else.", + parameters = { + { + name = "name", + description = "The RP name.", + type = "string", + optional = false + }, + { + name = "callback", + description = "The function that receives the boolean answer in its first parameter.", + type = "function", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.retrievePlayerData = DarkRP.stub{ + name = "retrievePlayerData", + description = "Get a player's information from the database.", + parameters = { + { + name = "ply", + description = "The player to get the data for.", + type = "Player", + optional = false + }, + { + name = "callback", + description = "The function that receives the information.", + type = "function", + optional = false + }, + { + name = "failure", + description = "The function that is called when the information cannot be retrieved.", + type = "function", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.offlinePlayerData = DarkRP.stub{ + name = "offlinePlayerData", + description = "Get a player's information from the database using a SteamID for use when the player is offline.", + parameters = { + { + name = "steamid", + description = "The SteamID of the player to get the data for.", + type = "string", + optional = false + }, + { + name = "callback", + description = "The function that receives the information.", + type = "function", + optional = false + }, + { + name = "failure", + description = "The function that is called when the information cannot be retrieved.", + type = "function", + optional = false + } + }, + returns = {}, + metatable = DarkRP +} + +DarkRP.storeOfflineMoney = DarkRP.stub{ + name = "storeOfflineMoney", + description = "Store the wallet amount of an offline player. Use DarkRP.offlinePlayerData to fetch the current wallet amount.", + parameters = { + { + name = "sid64", + description = "The SteamID64 of the player to set the wallet of. NOTE: THIS USED TO BE THE UNIQUEID, BUT THIS CHANGED!", + type = "number", + optional = false + }, + { + name = "amount", + description = "The amount of money.", + type = "number", + optional = false + } + }, + returns = {}, + metatable = DarkRP +} + +DarkRP.createPlayerData = DarkRP.stub{ + name = "createPlayerData", + description = "Internal function: creates an entry in the database for a player who has joined for the first time.", + parameters = { + { + name = "ply", + description = "The player to create the data for.", + type = "Player", + optional = false + }, + { + name = "name", + description = "The name of the player.", + type = "string", + optional = false + }, + { + name = "wallet", + description = "The amount of money the player has.", + type = "number", + optional = false + }, + { + name = "salary", + description = "The salary of the player.", + type = "number", + optional = false + }, + { + name = "onError", + description = "What to do when creating the player data fails. This function will get an argument holding the SQL error", + type = "function", + optional = true + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.storeMoney = DarkRP.stub{ + name = "storeMoney", + description = "Internal function. Store a player's money in the database. Do not call this if you just want to set someone's money, the player will not see the change!", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player", + optional = false + }, + { + name = "amount", + description = "The new contents of the player's wallet.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.storeSalary = DarkRP.stub{ + name = "storeSalary", + description = "Internal and deprecated function. Used to store a player's salary in the database.", + deprecated = "Use the ply:setSelfDarkRPVar(\"salary\", value) function instead.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player", + optional = false + }, + { + name = "amount", + description = "The new contents of the player's wallet.", + type = "number", + optional = false + } + }, + returns = { + { + name = "amount", + description = "The new contents of the player's wallet.", + type = "number" + } + }, + metatable = DarkRP +} + +DarkRP.retrieveSalary = DarkRP.stub{ + name = "retrieveSalary", + description = "Get a player's salary from the database.", + parameters = { + { + name = "ply", + description = "The player to get the data for.", + type = "Player", + optional = false + }, + { + name = "callback", + description = "The function that receives the salary. Deprecated, use the return value.", + type = "function", + optional = false + } + }, + returns = { + { + name = "salary", + description = "The salary.", + type = "number" + } + }, + metatable = DarkRP +} + +DarkRP.restorePlayerData = DarkRP.stub{ + name = "restorePlayerData", + description = "Internal function that restores a player's DarkRP information when they join.", + parameters = { + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.storeDoorData = DarkRP.stub{ + name = "storeDoorData", + description = "Store the information about a door in the database.", + parameters = { + { + name = "ent", + description = "The door.", + type = "Entity", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.storeTeamDoorOwnability = DarkRP.stub{ + name = "storeTeamDoorOwnability", + description = "Store the ownability information of a door in the database.", + parameters = { + { + name = "ent", + description = "The door.", + type = "Entity", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.storeDoorGroup = DarkRP.stub{ + name = "storeDoorGroup", + description = "Store the group of a door in the database.", + parameters = { + { + name = "ent", + description = "The door.", + type = "Entity", + optional = false + }, + { + name = "group", + description = "The group of the door.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.notify = DarkRP.stub{ + name = "notify", + description = "Make a notification pop up on the player's screen.", + parameters = { + { + name = "ply", + description = "The receiver(s) of the message.", + type = "Player, table", + optional = true + }, + { + name = "msgType", + description = "The type of the message.", + type = "number", + optional = false + }, + { + name = "time", + description = "For how long the notification should stay on the screen.", + type = "number", + optional = false + }, + { + name = "message", + description = "The actual message.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.notifyAll = DarkRP.stub{ + name = "notifyAll", + description = "Make a notification pop up on the everyone's screen.", + parameters = { + { + name = "msgType", + description = "The type of the message.", + type = "number", + optional = false + }, + { + name = "time", + description = "For how long the notification should stay on the screen.", + type = "number", + optional = false + }, + { + name = "message", + description = "The actual message.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.printMessageAll = DarkRP.stub{ + name = "printMessageAll", + description = "Make a notification pop up in the middle of everyone's screen.", + parameters = { + { + name = "msgType", + description = "The type of the message.", + type = "number", + optional = false + }, + { + name = "message", + description = "The actual message.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.printConsoleMessage = DarkRP.stub{ + name = "printConsoleMessage", + description = "Prints a message to a given player's console. This function also handles server consoles too (EntIndex = 0).", + parameters = { + { + name = "ply", + description = "The player to send the console message to.", + type = "Player", + optional = false + }, + { + name = "msg", + description = "The actual message.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.talkToRange = DarkRP.stub{ + name = "talkToRange", + description = "Send a chat message to people close to a player.", + parameters = { + { + name = "ply", + description = "The sender of the message.", + type = "Player", + optional = false + }, + { + name = "playerName", + description = "The name of the sender of the message.", + type = "string", + optional = false + }, + { + name = "message", + description = "The actual message.", + type = "string", + optional = false + }, + { + name = "size", + description = "The radius of the circle in which players can see the message in chat.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.talkToPerson = DarkRP.stub{ + name = "talkToPerson", + description = "Send a chat message to a player.", + parameters = { + { + name = "receiver", + description = "The receiver of the message.", + type = "Player", + optional = false + }, + { + name = "col1", + description = "The color of the first part of the message.", + type = "Color", + optional = false + }, + { + name = "text1", + description = "The first part of the message.", + type = "string", + optional = false + }, + { + name = "col2", + description = "The color of the second part of the message.", + type = "Color", + optional = false + }, + { + name = "text2", + description = "The secpnd part of the message.", + type = "string", + optional = false + }, + { + name = "sender", + description = "The sender of the message.", + type = "Player", + optional = true + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.isEmpty = DarkRP.stub{ + name = "isEmpty", + description = "Check whether the given position is empty. If you want the function not to ignore your entity, set the variable NotEmptyPos (ex. ENT.NotEmptyPos = true)", + parameters = { + { + name = "pos", + description = "The position to check for emptiness.", + type = "Vector", + optional = false + }, + { + name = "ignore", + description = "Table of things the algorithm can ignore.", + type = "table", + optional = true + } + }, + returns = { + { + name = "empty", + description = "Whether the given position is empty.", + type = "boolean" + } + }, + metatable = DarkRP +} +-- findEmptyPos(pos, ignore, distance, step, area) -- returns pos +DarkRP.findEmptyPos = DarkRP.stub{ + name = "findEmptyPos", + description = "Find an empty position as close as possible to the given position (Note: this algorithm is slow!).", + parameters = { + { + name = "pos", + description = "The position to check for emptiness.", + type = "Vector", + optional = false + }, + { + name = "ignore", + description = "Table of things the algorithm can ignore.", + type = "table", + optional = true + }, + { + name = "distance", + description = "The maximum distance to look for empty positions.", + type = "number", + optional = false + }, + { + name = "step", + description = "The size of the steps to check (it places it will look are STEP units removed from one another).", + type = "number", + optional = false + }, + { + name = "area", + description = "The hull to check, this is Vector(16, 16, 72) for players.", + type = "Vector", + optional = false + } + }, + returns = { + { + name = "pos", + description = "A found position. When no position was found, the parameter is returned", + type = "Vector" + } + }, + metatable = DarkRP +} + +DarkRP.PLAYER.applyPlayerClassVars = DarkRP.stub{ + name = "applyPlayerClassVars", + description = "Applies all variables in a player's associated GMod player class to the player.", + parameters = { + { + name = "applyHealth", + description = "Whether the player's health should be set to the starting health.", + type = "boolean", + optional = true + } + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.removeDarkRPVar = DarkRP.stub{ + name = "removeDarkRPVar", + description = "Remove a shared variable. Exactly the same as ply:setDarkRPVar(nil).", + parameters = { + { + name = "variable", + description = "The name of the variable.", + type = "string", + optional = false + }, + { + name = "target", + description = "The clients to whom this variable is sent. Defaults to all players.", + type = "table", + optional = true + } + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.setDarkRPVar = DarkRP.stub{ + name = "setDarkRPVar", + description = "Set a shared variable. Make sure the variable is registered with DarkRP.registerDarkRPVar!", + parameters = { + { + name = "variable", + description = "The name of the variable.", + type = "string", + optional = false + }, + { + name = "value", + description = "The value of the variable.", + type = "any", + optional = false + }, + { + name = "target", + description = "The clients to whom this variable is sent. Defaults to all players.", + type = "table", + optional = true + } + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.setSelfDarkRPVar = DarkRP.stub{ + name = "setSelfDarkRPVar", + description = "Set a shared variable that is only seen by the player to whom this variable applies.", + parameters = { + { + name = "variable", + description = "The name of the variable.", + type = "string", + optional = false + }, + { + name = "value", + description = "The value of the variable.", + type = "any", + optional = false + } + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.sendDarkRPVars = DarkRP.stub{ + name = "sendDarkRPVars", + description = "Internal function. Sends all visibleDarkRPVars of all players to this player.", + parameters = { + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.setRPName = DarkRP.stub{ + name = "setRPName", + description = "Set the RPName of a player.", + parameters = { + { + name = "name", + description = "The new name of the player.", + type = "string", + optional = false + }, + { + name = "firstrun", + description = "Whether to play nice and find a different name if it has been taken (true) or to refuse the name change when taken (false).", + type = "boolean", + optional = true + } + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.addCustomEntity = DarkRP.stub{ + name = "addCustomEntity", + description = "Add a custom entity to the player's limit.", + parameters = { + { + name = "tblEnt", + description = "The entity table (from the DarkRPEntities table).", + type = "table", + optional = false + } + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.removeCustomEntity = DarkRP.stub{ + name = "removeCustomEntity", + description = "Remove a custom entity to the player's limit.", + parameters = { + { + name = "tblEnt", + description = "The entity table (from the DarkRPEntities table).", + type = "table", + optional = false + } + }, + returns = {}, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.customEntityLimitReached = DarkRP.stub{ + name = "customEntityLimitReached", + description = "Set a shared variable.", + parameters = { + { + name = "tblEnt", + description = "The entity table (from the DarkRPEntities table).", + type = "table", + optional = false + } + }, + returns = { + { + name = "limitReached", + description = "Whether the limit has been reached.", + type = "boolean" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.customEntityCount = DarkRP.stub{ + name = "customEntityCount", + description = "Get the count of a custom entity.", + parameters = { + { + name = "tblEnt", + description = "The entity table (from the DarkRPEntities table).", + type = "table", + optional = false + } + }, + returns = { + { + name = "count", + description = "The current count of the custom entity.", + type = "number" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.getPreferredModel = DarkRP.stub{ + name = "getPreferredModel", + description = "Get the preferred model of a player for a job.", + parameters = { + { + name = "TeamNr", + description = "The job number.", + type = "number", + optional = false + } + }, + returns = { + { + name = "model", + description = "The preferred model for the job.", + type = "string" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.hookStub{ + name = "DarkRPDBInitialized", + description = "Called when DarkRP is done initializing the database.", + parameters = { + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "CanChangeRPName", + description = "Whether a player can change their RP name.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "name", + description = "The name.", + type = "string" + } + }, + returns = { + { + name = "answer", + description = "Whether the player can change their RP names.", + type = "boolean" + }, + { + name = "reason", + description = "When answer is false, this return value is the reason why.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "onNotify", + description = "Called when a notification is sent.", + parameters = { + { + name = "plys", + description = "The table recipients of the notification.", + type = "table" + }, + { + name = "msgType", + description = "The notification type (NOTIFY_ enum)", + type = "number" + }, + { + name = "duration", + description = "How long the notification should stay on screen.", + type = "number" + }, + { + name = "message", + description = "The message of the notification.", + type = "string" + } + }, + returns = { + { + name = "suppress", + description = "Whether to suppress the notification. Return true to suppress.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "onPlayerChangedName", + description = "Called when a player's DarkRP name has been changed.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "oldName", + description = "The old name.", + type = "string" + }, + { + name = "newName", + description = "The new name.", + type = "string" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "onPlayerFirstJoined", + description = "Called when a player joins this server for the first time (i.e. no entry for this player exists in the DarkRP database)", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "data", + description = "Contains the default wallet, salary and RPName that will be put in the database", + type = "table" + } + }, + returns = { + { + name = "data", + description = "Override the data that is to be put in the database.", + type = "table" + } + } +} + +DarkRP.hookStub{ + name = "playerBoughtPistol", + description = "Called when a player bought a pistol.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "weaponTable", + description = "The table (from the CustomShipments table).", + type = "table" + }, + { + name = "ent", + description = "The spawned weapon.", + type = "Weapon" + }, + { + name = "price", + description = "The eventual price.", + type = "number" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "playerBoughtShipment", + description = "Called when a player bought a shipment.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "shipmentTable", + description = "The table (from the CustomShipments table).", + type = "table" + }, + { + name = "ent", + description = "The spawned entity.", + type = "Entity" + }, + { + name = "price", + description = "The eventual price.", + type = "number" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "playerBoughtCustomVehicle", + description = "Called when a player bought a vehicle.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "vehicleTable", + description = "The table (from the CustomVehicles table).", + type = "table" + }, + { + name = "ent", + description = "The spawned vehicle.", + type = "Entity" + }, + { + name = "price", + description = "The eventual price.", + type = "number" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "playerBoughtCustomEntity", + description = "Called when a player bought an entity (like a money printer or a gun lab).", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "entityTable", + description = "The table of the custom entity (from the DarkRPEntities table).", + type = "table" + }, + { + name = "ent", + description = "The spawned entity.", + type = "Entity" + }, + { + name = "price", + description = "The eventual price.", + type = "number" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "playerBoughtAmmo", + description = "Called when a player buys some ammo.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "ammoTable", + description = "The table (from the AmmoTypes table).", + type = "table" + }, + { + name = "ent", + description = "The spawned ammo entity.", + type = "Weapon" + }, + { + name = "price", + description = "The eventual price.", + type = "number" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "canDemote", + description = "Whether a player can demote another player.", + parameters = { + { + name = "ply", + description = "The player who wants to demote.", + type = "Player" + }, + { + name = "target", + description = "The player whom is to be demoted.", + type = "Player" + }, + { + name = "reason", + description = "The reason provided for the demote.", + type = "string" + } + }, + returns = { + { + name = "canDemote", + description = "Whether the player can change demote the target.", + type = "boolean" + }, + { + name = "message", + description = "The message to show when the player cannot demote the other player. Only useful when canDemote is false.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "demoteTeam", + description = "The team the player is to be demoted to instead of the default team.", + parameters = { + { + name = "target", + description = "The player whom is to be demoted.", + type = "Player" + }, + }, + returns = { + { + name = "demoteTeam", + description = "The team the player is to be demoted to.", + type = "number" + }, + } +} + +DarkRP.hookStub{ + name = "onPlayerDemoted", + description = "Called when a player is demoted.", + parameters = { + { + name = "source", + description = "The player who demoted the target.", + type = "Player" + }, + { + name = "target", + description = "The player who has been demoted.", + type = "Player" + }, + { + name = "reason", + description = "The reason provided for the demote.", + type = "string" + } + }, + returns = { + } +} + +DarkRP.hookStub{ + name = "canDropWeapon", + description = "Whether a player can drop a certain weapon.", + parameters = { + { + name = "ply", + description = "The player who wants to drop the weapon.", + type = "Player" + }, + { + name = "weapon", + description = "The weapon the player wants to drop.", + type = "Weapon" + } + }, + returns = { + { + name = "canDrop", + description = "Whether the player can drop the weapon.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "canSeeLogMessage", + description = "Whether a player can see a DarkRP log message in the console.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "message", + description = "The log message.", + type = "string" + }, + { + name = "color", + description = "The color of the message.", + type = "Color" + } + }, + returns = { + { + name = "canHear", + description = "Whether the player can see the log message.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "canVote", + description = "Whether a player can cast a vote.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player" + }, + { + name = "vote", + description = "Table containing all information about the vote.", + type = "table" + } + }, + returns = { + { + name = "canVote", + description = "Whether the player can vote.", + type = "boolean" + }, + { + name = "message", + description = "The message to show when the player cannot vote. Only useful when canVote is false.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "playerGetSalary", + description = "When a player receives salary.", + parameters = { + { + name = "ply", + description = "The player who is receiving salary.", + type = "Player" + }, + { + name = "amount", + description = "The amount of money given to the player.", + type = "number" + } + }, + returns = { + { + name = "suppress", + description = "Suppress the salary message.", + type = "boolean" + }, + { + name = "message", + description = "Override the default message (suppress must be false).", + type = "string" + }, + { + name = "amount", + description = "Override the salary.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "playerWalletChanged", + description = "When a player receives money.", + parameters = { + { + name = "ply", + description = "The player who is getting money.", + type = "Player" + }, + { + name = "amount", + description = "The amount of money given to the player.", + type = "number" + }, + { + name = "wallet", + description = "How much money the player had before receiving the money.", + type = "number" + } + }, + returns = { + { + name = "total", + description = "Override the total amount of money (optional).", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "playerClassVarsApplied", + description = "When a player has had GMod player class variables applied to them through ply:applyPlayerClassVars().", + parameters = { + { + name = "ply", + description = "The player in question.", + type = "Player" + } + }, + returns = {} +} + + +DarkRP.hookStub{ + name = "canEarnNPCKillPay", + description = "If a player should profit from killing a NPC", + parameters = { + { + name = "ply", + description = "The player who killed the NPC.", + type = "Player" + }, + { + name = "npc", + description = "The NPC that they killed.", + type = "Entity" + } + }, + returns = { + { + name = "answer", + description = "Whether the player can earn money.", + type = "boolean" + } + } +} + + +DarkRP.hookStub{ + name = "calculateNPCKillPay", + description = "How much a player should profit from killing a NPC", + parameters = { + { + name = "ply", + description = "The player who killed the NPC.", + type = "Player" + }, + { + name = "npc", + description = "The NPC that they killed.", + type = "Entity" + } + }, + returns = { + { + name = "total", + description = "How much money they should earn.", + type = "number" + } + } +} diff --git a/gamemodes/darkrp/gamemode/modules/base/sv_jobmodels.lua b/gamemodes/darkrp/gamemode/modules/base/sv_jobmodels.lua new file mode 100644 index 0000000..fb7775d --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sv_jobmodels.lua @@ -0,0 +1,38 @@ +util.AddNetworkString("DarkRP_preferredjobmodels") +util.AddNetworkString("DarkRP_preferredjobmodel") + +local preferredJobModels = {} +local plyMeta = FindMetaTable("Player") + +local received = {} +net.Receive("DarkRP_preferredjobmodels", function(len, ply) + preferredJobModels[ply] = {} + + for i in pairs(RPExtraTeams) do + if net.ReadBit() == 0 then continue end + + preferredJobModels[ply][i] = net.ReadString() + end + + if not received[ply] and preferredJobModels[ply][ply:Team()] then + gamemode.Call("PlayerSetModel", ply) + end + + received[ply] = true +end) + +net.Receive("DarkRP_preferredjobmodel", function(len, ply) + local teamNr = net.ReadUInt(8) + local model = net.ReadString() + + if not RPExtraTeams[teamNr] then return end + + preferredJobModels[ply] = preferredJobModels[ply] or {} + preferredJobModels[ply][teamNr] = model +end) + +function plyMeta:getPreferredModel(TeamNr) + preferredJobModels[self] = preferredJobModels[self] or {} + + return preferredJobModels[self][TeamNr] +end diff --git a/gamemodes/darkrp/gamemode/modules/base/sv_purchasing.lua b/gamemodes/darkrp/gamemode/modules/base/sv_purchasing.lua new file mode 100644 index 0000000..e2e13d9 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sv_purchasing.lua @@ -0,0 +1,466 @@ +function DarkRP.hooks:canBuyPistol(ply, shipment) + local price = shipment.getPrice and shipment.getPrice(ply, shipment.pricesep) or shipment.pricesep or 0 + + if not GAMEMODE:CustomObjFitsMap(shipment) then + return false, false, "Custom object does not fit map" + end + + if ply:isArrested() then + return false, false, DarkRP.getPhrase("unable", "/buy", "") + end + + if shipment.customCheck and not shipment.customCheck(ply) then + local message = isfunction(shipment.CustomCheckFailMsg) and shipment.CustomCheckFailMsg(ply, shipment) or + shipment.CustomCheckFailMsg or + DarkRP.getPhrase("not_allowed_to_purchase") + return false, false, message + end + + if not ply:canAfford(price) then + return false, false, DarkRP.getPhrase("cant_afford", "/buy") + end + + if not GAMEMODE.Config.restrictbuypistol or + (GAMEMODE.Config.restrictbuypistol and (not shipment.allowed[1] or table.HasValue(shipment.allowed, ply:Team()))) then + return true + end + + return false +end + +local function BuyPistol(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + if not GAMEMODE.Config.enablebuypistol then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", "/buy", "")) + return "" + end + + if GAMEMODE.Config.noguns then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", "/buy", "")) + return "" + end + + local shipment = DarkRP.getShipmentByName(args) + if not shipment or not shipment.separate then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("weapon_"))) + return "" + end + + local canbuy, suppress, message, price = hook.Call("canBuyPistol", DarkRP.hooks, ply, shipment) + + if not canbuy then + message = message or DarkRP.getPhrase("incorrect_job", "/buy") + if not suppress then DarkRP.notify(ply, 1, 4, message) end + return "" + end + + local cost = price or shipment.getPrice and shipment.getPrice(ply, shipment.pricesep) or shipment.pricesep or 0 + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + local defaultClip, clipSize + local wep_tbl = weapons.Get(shipment.entity) + if wep_tbl and wep_tbl.Primary then + defaultClip = wep_tbl.Primary.DefaultClip + clipSize = wep_tbl.Primary.ClipSize + end + + local weapon = ents.Create("spawned_weapon") + weapon:SetModel(shipment.model) + weapon:SetWeaponClass(shipment.entity) + weapon:SetPos(tr.HitPos) + weapon.ammoadd = shipment.spareammo or defaultClip + weapon.clip1 = shipment.clip1 or clipSize + weapon.clip2 = shipment.clip2 + weapon.nodupe = true + weapon:Spawn() + + DarkRP.placeEntity(weapon, tr, ply) + + if shipment.onBought then + shipment.onBought(ply, shipment, weapon) + end + hook.Call("playerBoughtPistol", nil, ply, shipment, weapon, cost) + + if IsValid(weapon) then + ply:addMoney(-cost) + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", args, DarkRP.formatMoney(cost))) + else + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/buy", args)) + end + + return "" +end +DarkRP.defineChatCommand("buy", BuyPistol, 0.2) + +function DarkRP.hooks:canBuyShipment(ply, shipment) + if not GAMEMODE:CustomObjFitsMap(shipment) then + return false, false, "Custom object does not fit map" + end + + if ply.LastShipmentSpawn and ply.LastShipmentSpawn > (CurTime() - GAMEMODE.Config.ShipmentSpamTime) then + return false, false, DarkRP.getPhrase("shipment_antispam_wait") + end + + if ply:isArrested() then + return false, false, DarkRP.getPhrase("unable", "/buyshipment", "") + end + + if shipment.customCheck and not shipment.customCheck(ply) then + local message = isfunction(shipment.CustomCheckFailMsg) and shipment.CustomCheckFailMsg(ply, shipment) or + shipment.CustomCheckFailMsg or + DarkRP.getPhrase("not_allowed_to_purchase") + return false, false, message + end + + local canbecome = false + for _, b in pairs(shipment.allowed) do + if ply:Team() == b then + canbecome = true + break + end + end + + if not canbecome then + return false, false, DarkRP.getPhrase("incorrect_job", "/buyshipment") + end + + local cost = shipment.getPrice and shipment.getPrice(ply, shipment.price) or shipment.price + + if not ply:canAfford(cost) then + return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("shipment")) + end + + if not shipment.allowPurchaseWhileDead and not ply:Alive() then + return false, false, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", DarkRP.getPhrase("shipments"))) + end + + return true +end + +local function BuyShipment(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + local found, foundKey = DarkRP.getShipmentByName(args) + if not found or found.noship or not GAMEMODE:CustomObjFitsMap(found) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("shipment"))) + return "" + end + + local canbuy, suppress, message, price = hook.Call("canBuyShipment", DarkRP.hooks, ply, found) + + if not canbuy then + message = message or DarkRP.getPhrase("incorrect_job", "/buy") + if not suppress then DarkRP.notify(ply, 1, 4, message) end + return "" + end + + local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + local crate = ents.Create(found.shipmentClass or "spawned_shipment") + crate.SID = ply.SID + crate:Setowning_ent(ply) + crate:SetContents(foundKey, found.amount) + + crate:SetPos(Vector(tr.HitPos.x, tr.HitPos.y, tr.HitPos.z)) + crate.nodupe = true + crate.ammoadd = found.spareammo + crate.clip1 = found.clip1 + crate.clip2 = found.clip2 + crate:Spawn() + crate:SetPlayer(ply) + + DarkRP.placeEntity(crate, tr, ply) + + local phys = crate:GetPhysicsObject() + phys:Wake() + if found.weight then + phys:SetMass(found.weight) + end + + if CustomShipments[foundKey].onBought then + CustomShipments[foundKey].onBought(ply, CustomShipments[foundKey], crate) + end + hook.Call("playerBoughtShipment", nil, ply, CustomShipments[foundKey], crate, cost) + + if IsValid(crate) then + ply:addMoney(-cost) + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", args, DarkRP.formatMoney(cost))) + else + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/buyshipment", arg)) + end + + ply.LastShipmentSpawn = CurTime() + + return "" +end +DarkRP.defineChatCommand("buyshipment", BuyShipment) + +function DarkRP.hooks:canBuyVehicle(ply, vehicle) + if not vehicle.allowPurchaseWhileDead and not ply:Alive() then + return false, false, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", vehicle.name)) + end + if not GAMEMODE:CustomObjFitsMap(vehicle) then + return false, false, "Custom object does not fit map" + end + + if ply:isArrested() then + return false, false, DarkRP.getPhrase("unable", "/buyvehicle", "") + end + + if vehicle.allowed and not table.HasValue(vehicle.allowed, ply:Team()) then + return false, false, DarkRP.getPhrase("incorrect_job", "/buyvehicle") + end + + if vehicle.customCheck and not vehicle.customCheck(ply) then + local message = isfunction(vehicle.CustomCheckFailMsg) and vehicle.CustomCheckFailMsg(ply, vehicle) or + vehicle.CustomCheckFailMsg or + DarkRP.getPhrase("not_allowed_to_purchase") + return false, false, message + end + + ply.Vehicles = ply.Vehicles or 0 + if GAMEMODE.Config.maxvehicles and ply.Vehicles >= GAMEMODE.Config.maxvehicles then + return false, false, DarkRP.getPhrase("limit", DarkRP.getPhrase("vehicle")) + end + + local cost = vehicle.getPrice and vehicle.getPrice(ply, vehicle.price) or vehicle.price + if not ply:canAfford(cost) then + return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("vehicle")) + end + + return true +end + +local function BuyVehicle(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + local found = false + -- Allow people to have multiple vehicles with the same name + -- vehicles are bought through the command + for k, v in pairs(CustomVehicles) do + if v.command and string.lower(v.command) == string.lower(args) then + found = CustomVehicles[k] + break + end + end + + if not found then + for k,v in pairs(CustomVehicles) do + if string.lower(v.name) == string.lower(args) then found = CustomVehicles[k] break end + end + end + + if not found then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("vehicle"))) + return "" + end + + local Vehicle = DarkRP.getAvailableVehicles()[found.name] + if not Vehicle then DarkRP.notify(ply, 1, 4, "Incorrect vehicle, fix your vehicles.") return "" end + + local canbuy, suppress, message, price = hook.Call("canBuyVehicle", DarkRP.hooks, ply, found) + + if not canbuy then + message = message or DarkRP.getPhrase("incorrect_job", "/buy") + if not suppress then DarkRP.notify(ply, 1, 4, message) end + return "" + end + + local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price + + ply:addMoney(-cost) + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", found.label or found.name, DarkRP.formatMoney(cost))) + + local trace = {} + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * (found.distance or 85) + trace.filter = ply + local tr = util.TraceLine(trace) + + local ent = ents.Create(Vehicle.Class) + if not ent:IsValid() then error("Vehicle '" .. Vehicle.Class .. "' does not exist or is not valid.") end + + ent:SetModel(Vehicle.Model) + if Vehicle.KeyValues then + for k, v in pairs(Vehicle.KeyValues) do + ent:SetKeyValue(k, v) + end + end + + ent:SetPos(tr.HitPos) + ent.VehicleName = found.name + ent.VehicleTable = Vehicle + ent:Spawn() + ent:Activate() + ent.SID = ply.SID + ent.ClassOverride = Vehicle.Class + if Vehicle.Members then + table.Merge(ent, Vehicle.Members) + end + ent:CPPISetOwner(ply) + ent:keysOwn(ply) + + DarkRP.placeEntity(ent, tr, ply) + + local angOff = found.angle or Angle(0, 0, 0) + ent:SetAngles(ent:GetAngles() + angOff) + + hook.Call("PlayerSpawnedVehicle", GAMEMODE, ply, ent) -- VUMod compatability + hook.Call("playerBoughtCustomVehicle", nil, ply, found, ent, cost) + + if found.onBought then + found.onBought(ply, found, ent) + end + + return "" +end +DarkRP.defineChatCommand("buyvehicle", BuyVehicle) + +function DarkRP.hooks:canBuyAmmo(ply, ammo) + if not GAMEMODE:CustomObjFitsMap(ammo) then + return false, false, "Custom object does not fit map" + end + + if ply:isArrested() then + return false, false, DarkRP.getPhrase("unable", "/buyammo", "") + end + + if ammo.allowed and not table.HasValue(ammo.allowed, ply:Team()) then + return false, false, DarkRP.getPhrase("incorrect_job", "/buyammo") + end + + if ammo.customCheck and not ammo.customCheck(ply) then + local message = isfunction(ammo.CustomCheckFailMsg) and ammo.CustomCheckFailMsg(ply, ammo) or + ammo.CustomCheckFailMsg or + DarkRP.getPhrase("not_allowed_to_purchase") + return false, false, message + end + + local cost = ammo.getPrice and ammo.getPrice(ply, ammo.price) or ammo.price + if not ply:canAfford(cost) then + return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("ammo")) + end + + return true +end + +local function BuyAmmo(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + if GAMEMODE.Config.noguns then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", DarkRP.getPhrase("ammo"), "")) + return "" + end + + local found + local num = tonumber(args) + if num and GAMEMODE.AmmoTypes[num] then + found = GAMEMODE.AmmoTypes[num] + else + for _, v in pairs(GAMEMODE.AmmoTypes) do + if v.ammoType ~= args then continue end + + found = v + break + end + end + + if not found then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("ammo"))) + return "" + end + + local canbuy, suppress, message, price = hook.Call("canBuyAmmo", DarkRP.hooks, ply, found) + + if not canbuy then + message = message or DarkRP.getPhrase("incorrect_job", "/buy") + if not suppress then DarkRP.notify(ply, 1, 4, message) end + return "" + end + + local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price + + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", found.name, DarkRP.formatMoney(cost))) + ply:addMoney(-cost) + + -- local trace = {} + -- trace.start = ply:EyePos() + -- trace.endpos = trace.start + ply:GetAimVector() * 85 + -- trace.filter = ply + + -- local tr = util.TraceLine(trace) + + -- local ammo = ents.Create("spawned_ammo") + -- ammo:SetModel(found.model) + -- ammo:SetPos(tr.HitPos) + -- ammo.nodupe = true + -- ammo.amountGiven, ammo.ammoType = found.amountGiven, found.ammoType + -- ammo:Spawn() + + -- DarkRP.placeEntity(ammo, tr, ply) + + -- hook.Call("playerBoughtAmmo", nil, ply, found, ammo, cost) + -- + ply:GiveAmmo(found.amountGiven, found.ammoType) + + return "" +end +DarkRP.defineChatCommand("buyammo", BuyAmmo, 1) + +local function SetPrice(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + local price = DarkRP.toInt(args) + if not price then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + price = math.Clamp(price, GAMEMODE.Config.pricemin, (GAMEMODE.Config.pricecap ~= 0 and GAMEMODE.Config.pricecap) or 500) + local trace = {} + + trace.start = ply:EyePos() + trace.endpos = trace.start + ply:GetAimVector() * 85 + trace.filter = ply + + local tr = util.TraceLine(trace) + + local ent = tr.Entity + + if IsValid(ent) and ent.CanSetPrice and ent.SID == ply.SID then + ent:Setprice(price) + else + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("any_lab"))) + end + return "" +end +DarkRP.defineChatCommand("price", SetPrice) +DarkRP.defineChatCommand("setprice", SetPrice) diff --git a/gamemodes/darkrp/gamemode/modules/base/sv_util.lua b/gamemodes/darkrp/gamemode/modules/base/sv_util.lua new file mode 100644 index 0000000..64510b5 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/base/sv_util.lua @@ -0,0 +1,235 @@ +function DarkRP.notify(ply, msgtype, len, msg) + if not istable(ply) then + if not IsValid(ply) then + -- Dedicated server console + print(msg) + return + end + + ply = {ply} + end + + local rcp = RecipientFilter() + for _, v in pairs(ply) do + rcp:AddPlayer(v) + end + + if hook.Run("onNotify", rcp:GetPlayers(), msgtype, len, msg) == true then return end + + umsg.Start("_Notify", rcp) + umsg.String(msg) + umsg.Short(msgtype) + umsg.Long(len) + umsg.End() +end + +function DarkRP.notifyAll(msgtype, len, msg) + if hook.Run("onNotify", player.GetAll(), msgtype, len, msg) == true then return end + + umsg.Start("_Notify") + umsg.String(msg) + umsg.Short(msgtype) + umsg.Long(len) + umsg.End() +end + +function DarkRP.printMessageAll(msgtype, msg) + for _, v in ipairs(player.GetAll()) do + v:PrintMessage(msgtype, msg) + end +end + +function DarkRP.printConsoleMessage(ply, msg) + if ply:EntIndex() == 0 then + print(msg) + else + ply:PrintMessage(HUD_PRINTCONSOLE, msg) + end +end + +util.AddNetworkString("DarkRP_Chat") + +function DarkRP.talkToRange(ply, PlayerName, Message, size) + local ents = player.GetHumans() + local col = team.GetColor(ply:Team()) + local filter = {} + + local plyPos = ply:EyePos() + local sizeSqr = size * size + + for _, v in ipairs(ents) do + if (v:EyePos():DistToSqr(plyPos) <= sizeSqr) and (v == ply or hook.Run("PlayerCanSeePlayersChat", PlayerName .. ": " .. Message, false, v, ply) ~= false) then + table.insert(filter, v) + end + end + + if PlayerName == ply:Nick() then PlayerName = "" end -- If it's just normal chat, why not cut down on networking and get the name on the client + + net.Start("DarkRP_Chat") + net.WriteUInt(col.r, 8) + net.WriteUInt(col.g, 8) + net.WriteUInt(col.b, 8) + net.WriteString(PlayerName) + net.WriteEntity(ply) + net.WriteUInt(255, 8) + net.WriteUInt(255, 8) + net.WriteUInt(255, 8) + net.WriteString(Message) + net.Send(filter) +end + +function DarkRP.talkToPerson(receiver, col1, text1, col2, text2, sender) + if not IsValid(receiver) then return end + if receiver:IsBot() then return end + local concatenatedText = (text1 or "") .. ": " .. (text2 or "") + + if sender == receiver or hook.Run("PlayerCanSeePlayersChat", concatenatedText, false, receiver, sender) ~= false then + net.Start("DarkRP_Chat") + net.WriteUInt(col1.r, 8) + net.WriteUInt(col1.g, 8) + net.WriteUInt(col1.b, 8) + net.WriteString(text1) + + sender = sender or Entity(0) + net.WriteEntity(sender) + + col2 = col2 or color_black + net.WriteUInt(col2.r, 8) + net.WriteUInt(col2.g, 8) + net.WriteUInt(col2.b, 8) + net.WriteString(text2 or "") + net.Send(receiver) + end +end + +function DarkRP.isEmpty(vector, ignore) + ignore = ignore or {} + + local point = util.PointContents(vector) + local a = point ~= CONTENTS_SOLID + and point ~= CONTENTS_MOVEABLE + and point ~= CONTENTS_LADDER + and point ~= CONTENTS_PLAYERCLIP + and point ~= CONTENTS_MONSTERCLIP + if not a then return false end + + local b = true + + for _, v in ipairs(ents.FindInSphere(vector, 35)) do + if (v:IsNPC() or v:IsPlayer() or v:GetClass() == "prop_physics" or v.NotEmptyPos) and not table.HasValue(ignore, v) then + b = false + break + end + end + + return a and b +end + +function DarkRP.placeEntity(ent, tr, ply) + if IsValid(ply) then + local ang = ply:EyeAngles() + ang.pitch = 0 + ang.yaw = ang.yaw + 180 + ang.roll = 0 + ent:SetAngles(ang) + end + + local vFlushPoint = tr.HitPos - (tr.HitNormal * 512) + vFlushPoint = ent:NearestPoint(vFlushPoint) + vFlushPoint = ent:GetPos() - vFlushPoint + vFlushPoint = tr.HitPos + vFlushPoint + ent:SetPos(vFlushPoint) +end + +--[[--------------------------------------------------------------------------- +Find an empty position near the position given in the first parameter +pos - The position to use as a center for looking around +ignore - what entities to ignore when looking for the position (the position can be within the entity) +distance - how far to look +step - how big the steps are +area - the position relative to pos that should also be free + +Performance: O(N^2) (The Lua part, that is, I don't know about the C++ counterpart) +Don't call this function too often or with big inputs. +---------------------------------------------------------------------------]] +function DarkRP.findEmptyPos(pos, ignore, distance, step, area) + if DarkRP.isEmpty(pos, ignore) and DarkRP.isEmpty(pos + area, ignore) then + return pos + end + + for j = step, distance, step do + for i = -1, 1, 2 do -- alternate in direction + local k = j * i + + -- Look North/South + if DarkRP.isEmpty(pos + Vector(k, 0, 0), ignore) and DarkRP.isEmpty(pos + Vector(k, 0, 0) + area, ignore) then + return pos + Vector(k, 0, 0) + end + + -- Look East/West + if DarkRP.isEmpty(pos + Vector(0, k, 0), ignore) and DarkRP.isEmpty(pos + Vector(0, k, 0) + area, ignore) then + return pos + Vector(0, k, 0) + end + + -- Look Up/Down + if DarkRP.isEmpty(pos + Vector(0, 0, k), ignore) and DarkRP.isEmpty(pos + Vector(0, 0, k) + area, ignore) then + return pos + Vector(0, 0, k) + end + end + end + + return pos +end + +local meta = FindMetaTable("Player") +function meta:applyPlayerClassVars(applyHealth) + local playerClass = baseclass.Get(player_manager.GetPlayerClass(self)) + + self:SetWalkSpeed(playerClass.WalkSpeed >= 0 and playerClass.WalkSpeed or GAMEMODE.Config.walkspeed) + self:SetRunSpeed(playerClass.RunSpeed >= 0 and playerClass.RunSpeed or (self:isCP() and GAMEMODE.Config.runspeedcp or GAMEMODE.Config.runspeed)) + + hook.Call("UpdatePlayerSpeed", GAMEMODE, self) -- Backwards compatitibly, do not use + + self:SetCrouchedWalkSpeed(playerClass.CrouchedWalkSpeed) + self:SetDuckSpeed(playerClass.DuckSpeed) + self:SetUnDuckSpeed(playerClass.UnDuckSpeed) + self:SetJumpPower(playerClass.JumpPower) + self:AllowFlashlight(playerClass.CanUseFlashlight) + + self:SetMaxHealth(playerClass.MaxHealth >= 0 and playerClass.MaxHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100)) + if applyHealth then + self:SetHealth(playerClass.StartHealth >= 0 and playerClass.StartHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100)) + end + self:SetArmor(playerClass.StartArmor) + + self.dropWeaponOnDeath = playerClass.DropWeaponOnDie + self:SetNoCollideWithTeammates(playerClass.TeammateNoCollide) + self:SetAvoidPlayers(playerClass.AvoidPlayers) + + hook.Call("playerClassVarsApplied", nil, self) +end + +local function LookPersonUp(ply, cmd, args) + if not args[1] then + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return + end + local P = DarkRP.findPlayer(args[1]) + if not IsValid(P) then + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("could_not_find", tostring(args[1]))) + return + end + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("name", P:Nick())) + DarkRP.printConsoleMessage(ply, "Steam " .. DarkRP.getPhrase("name", P:SteamName())) + DarkRP.printConsoleMessage(ply, "Steam ID: " .. P:SteamID()) + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("job", team.GetName(P:Team()))) + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("kills", P:Frags())) + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("deaths", P:Deaths())) + + CAMI.PlayerHasAccess(ply, "DarkRP_AdminCommands", function(access) + if not access then return end + + DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("wallet", DarkRP.formatMoney(P:getDarkRPVar("money")), "")) + end) +end +concommand.Add("rp_lookup", LookPersonUp) diff --git a/gamemodes/darkrp/gamemode/modules/chat/cl_chat.lua b/gamemodes/darkrp/gamemode/modules/chat/cl_chat.lua new file mode 100644 index 0000000..6a3ecc3 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/cl_chat.lua @@ -0,0 +1,90 @@ +--[[--------------------------------------------------------------------------- +Gamemode function +---------------------------------------------------------------------------]] +function GM:OnPlayerChat() +end + +--[[--------------------------------------------------------------------------- +Add a message to chat +---------------------------------------------------------------------------]] +local function AddToChat(bits) + local col1 = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)) + + local prefixText = net.ReadString() + local ply = net.ReadEntity() + ply = IsValid(ply) and ply or LocalPlayer() + + if not IsValid(ply) then return end + + if prefixText == "" or not prefixText then + prefixText = ply:Nick() + prefixText = prefixText ~= "" and prefixText or ply:SteamName() + end + + local col2 = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8)) + + local text = net.ReadString() + local shouldShow + if text and text ~= "" then + if IsValid(ply) then + shouldShow = hook.Call("OnPlayerChat", GAMEMODE, ply, text, false, not ply:Alive(), prefixText, col1, col2) + end + + if shouldShow ~= true then + chat.AddNonParsedText(col1, prefixText, col2, ": " .. text) + end + else + shouldShow = hook.Call("ChatText", GAMEMODE, "0", prefixText, prefixText, "darkrp") + + if shouldShow ~= true then + chat.AddNonParsedText(col1, prefixText) + end + end + chat.PlaySound() +end +net.Receive("DarkRP_Chat", AddToChat) + +--[[--------------------------------------------------------------------------- +Credits + +Please only ADD to the credits. +---------------------------------------------------------------------------]] +local creds = +[[ + +LightRP was created by Rick darkalonio. LightRP was sandbox with some added RP elements. +LightRP was released at the end of January 2007 + +DarkRP was created as a spoof of LightRP by Rickster, somewhere during the summer of 2007. +Note: There was a DarkRP in 2006, but that was an entirely different gamemode. + +Rickster went to serve his country and went to Afghanistan. During that time, the following people updated DarkRP: +Picwizdan +Sibre +[GNC] Matt +PhilXYZ +Chromebolt A.K.A. Unib5 (STEAM_0:1:19045957) + +In 2008, Unib5 was administrator on a DarkRP server called EuroRP, owned by Jiggu. FPtje frequently joined this server to prop kill en masse. While Jiggu loved watching the chaos unfold, Unib5 hated it and banned FPtje on sight. Since Jiggu kept unbanning FPtje, Unib5 felt powerless. In an attempt to stop FPtje, Unib5 put FPtje's favourite prop killing props (the locker and the sawblade) in the default blacklist of DarkRP in an update. This in turn enraged FPtje, as he swore to make an update in secret that would suddenly pop up and overthrow the established version. As a result, DarkRP 2.3.1 was released in December 2008. After a bit of a fight, FPtje became the official updater of DarkRP. + +Current developer: + Falco A.K.A. FPtje Atheos (STEAM_0:0:8944068) + +People who have contributed (ordered by commits, with at least two commits) + Bo98 + Drakehawke (STEAM_0:0:22342869) (64 commits on old SVN) + FiG-Scorn + Noiwex + KoZ + Eusion (STEAM_0:0:20450406) (3 commits on old SVN) + Gangleider + MattWalton12 + TypicalRookie +]] + +local function credits(um) + chat.AddNonParsedText(Color(255, 0, 0, 255), "[", Color(50,50,50,255), GAMEMODE.Name, Color(255, 0, 0, 255), "] ", color_white, DarkRP.getPhrase("credits_see_console")) + + MsgC(Color(255, 0, 0, 255), DarkRP.getPhrase("credits_for", GAMEMODE.Name), color_white, creds) +end +usermessage.Hook("DarkRP_Credits", credits) diff --git a/gamemodes/darkrp/gamemode/modules/chat/cl_chatlisteners.lua b/gamemodes/darkrp/gamemode/modules/chat/cl_chatlisteners.lua new file mode 100644 index 0000000..3753907 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/cl_chatlisteners.lua @@ -0,0 +1,207 @@ +--[[--------------------------------------------------------------------------- +This module finds out for you who can see you talk or speak through the microphone +---------------------------------------------------------------------------]] + +--[[--------------------------------------------------------------------------- +Variables +---------------------------------------------------------------------------]] +local receivers +local currentChatText = {} +local receiverConfigs = {} +local currentConfig = {text = "", hearFunc = fn.Id} -- Default config is not loaded yet + +--[[--------------------------------------------------------------------------- +addChatReceiver +Add a chat command with specific receivers + +prefix: the chat command itself ("/pm", "/ooc", "/me" are some examples) +text: the text that shows up when it says "Some people can hear you X" +hearFunc: a function(ply, splitText) that decides whether this player can or cannot hear you. + return true if the player can hear you + false if the player cannot + nil if you want to prevent the text from showing up temporarily +---------------------------------------------------------------------------]] +function DarkRP.addChatReceiver(prefix, text, hearFunc) + receiverConfigs[prefix] = { + text = text, + hearFunc = hearFunc + } +end + +--[[--------------------------------------------------------------------------- +removeChatReceiver +Remove a chat command. + +prefix: the command, like in addChatReceiver +---------------------------------------------------------------------------]] +function DarkRP.removeChatReceiver(prefix) + receiverConfigs[prefix] = nil +end + +--[[--------------------------------------------------------------------------- +Draw the results to the screen +---------------------------------------------------------------------------]] +local function drawChatReceivers() + if not receivers then return end + + local fontHeight = draw.GetFontHeight("DarkRPHUD1") + local x, y = chat.GetChatBoxPos() + y = y - fontHeight - 4 + + local receiversCount = #receivers + -- No one hears you + if receiversCount == 0 then + draw.WordBox(2, x, y, DarkRP.getPhrase("hear_noone", currentConfig.text), "DarkRPHUD1", Color(0,0,0,160), Color(255,0,0,255)) + return + -- Everyone hears you + elseif receiversCount == player.GetCount() - 1 then + draw.WordBox(2, x, y, DarkRP.getPhrase("hear_everyone"), "DarkRPHUD1", Color(0,0,0,160), Color(0,255,0,255)) + return + end + + draw.WordBox(2, x, y - (receiversCount * (fontHeight + 4)), DarkRP.getPhrase("hear_certain_persons", currentConfig.text), "DarkRPHUD1", Color(0,0,0,160), Color(0,255,0,255)) + for i = 1, receiversCount, 1 do + if not IsValid(receivers[i]) then + receivers[i] = receivers[#receivers] + receivers[#receivers] = nil + continue + end + + draw.WordBox(2, x, y - (i - 1) * (fontHeight + 4), receivers[i]:Nick(), "DarkRPHUD1", Color(0, 0, 0, 160), color_white) + end +end + +--[[--------------------------------------------------------------------------- +Find out who could hear the player if they were to speak now +---------------------------------------------------------------------------]] +local function chatGetRecipients() + if not currentConfig then return end + + receivers = {} + for _, ply in ipairs(player.GetAll()) do + local hidePly = hook.Run("chatHideRecipient", ply) + if not IsValid(ply) or ply == LocalPlayer() or ply:GetNoDraw() or hidePly then continue end + + local val = currentConfig.hearFunc(ply, currentChatText) + + -- Return nil to disable the chat recipients temporarily. + if val == nil then + receivers = nil + return + elseif val == true then + table.insert(receivers, ply) + end + end +end + +--[[--------------------------------------------------------------------------- +Called when the player starts typing +---------------------------------------------------------------------------]] +local function startFind() + local shouldDraw = hook.Call("HUDShouldDraw", GAMEMODE, "DarkRP_ChatReceivers") + if shouldDraw == false then return end + + currentConfig = receiverConfigs[""] + hook.Add("Think", "DarkRP_chatRecipients", chatGetRecipients) + hook.Add("HUDPaint", "DarkRP_DrawChatReceivers", drawChatReceivers) +end +hook.Add("StartChat", "DarkRP_StartFindChatReceivers", startFind) + +--[[--------------------------------------------------------------------------- +Called when the player stops typing +---------------------------------------------------------------------------]] +local function stopFind() + hook.Remove("Think", "DarkRP_chatRecipients") + hook.Remove("HUDPaint", "DarkRP_DrawChatReceivers") +end +hook.Add("FinishChat", "DarkRP_StopFindChatReceivers", stopFind) + +--[[--------------------------------------------------------------------------- +Find out which chat command the user is typing +---------------------------------------------------------------------------]] +local function findConfig(text) + local split = string.Explode(' ', text) + local prefix = string.lower(split[1]) + + currentChatText = split + + currentConfig = receiverConfigs[prefix] or receiverConfigs[""] +end +hook.Add("ChatTextChanged", "DarkRP_FindChatRecipients", findConfig) + + +--[[--------------------------------------------------------------------------- +Default chat receievers. If you want to add your own ones, don't add them to this file. Add them to a clientside module file instead. +---------------------------------------------------------------------------]] +-- Load after the custom languages have been loaded +local function loadChatReceivers() + -- Default talk chat receiver has no prefix + DarkRP.addChatReceiver("", DarkRP.getPhrase("talk"), function(ply) + if GAMEMODE.Config.alltalk then return nil end + + return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < + GAMEMODE.Config.talkDistance * GAMEMODE.Config.talkDistance + end) + + DarkRP.addChatReceiver("/ooc", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end) + DarkRP.addChatReceiver("//", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end) + DarkRP.addChatReceiver("/a", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end) + DarkRP.addChatReceiver("/w", DarkRP.getPhrase("whisper"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.whisperDistance * GAMEMODE.Config.whisperDistance end) + DarkRP.addChatReceiver("/y", DarkRP.getPhrase("yell"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.yellDistance * GAMEMODE.Config.yellDistance end) + DarkRP.addChatReceiver("/me", DarkRP.getPhrase("perform_your_action"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.meDistance * GAMEMODE.Config.meDistance end) + DarkRP.addChatReceiver("/g", DarkRP.getPhrase("talk_to_your_group"), function(ply) + for _, func in pairs(GAMEMODE.DarkRPGroupChats) do + if func(LocalPlayer()) and func(ply) then + return true + end + end + return false + end) + + + DarkRP.addChatReceiver("/pm", "PM", function(ply, text) + if not isstring(text[2]) then return false end + text[2] = string.lower(tostring(text[2])) + + return string.find(string.lower(ply:Nick()), text[2], 1, true) ~= nil or + string.find(string.lower(ply:SteamName()), text[2], 1, true) ~= nil or + string.lower(ply:SteamID()) == text[2] + end) + + --[[--------------------------------------------------------------------------- + Voice chat receivers + ---------------------------------------------------------------------------]] + local voiceDistance = GM.Config.voiceDistance * GM.Config.voiceDistance + DarkRP.addChatReceiver("speak", DarkRP.getPhrase("speak"), function(ply) + if not LocalPlayer().DRPIsTalking then return nil end + if LocalPlayer():GetPos():DistToSqr(ply:GetPos()) > voiceDistance then return false end + + return not GAMEMODE.Config.dynamicvoice or ply:isInRoom() + end) +end +hook.Add("loadCustomDarkRPItems", "loadChatListeners", loadChatReceivers) + +--[[--------------------------------------------------------------------------- +Called when the player starts using their voice +---------------------------------------------------------------------------]] +local function startFindVoice(ply) + if ply ~= LocalPlayer() then return end + + local shouldDraw = hook.Call("HUDShouldDraw", GAMEMODE, "DarkRP_ChatReceivers") + if shouldDraw == false then return end + + currentConfig = receiverConfigs["speak"] + hook.Add("Think", "DarkRP_chatRecipients", chatGetRecipients) + hook.Add("HUDPaint", "DarkRP_DrawChatReceivers", drawChatReceivers) +end +hook.Add("PlayerStartVoice", "DarkRP_VoiceChatReceiverFinder", startFindVoice) + +--[[--------------------------------------------------------------------------- +Called when the player stops using their voice +---------------------------------------------------------------------------]] +local function stopFindVoice(ply) + if ply ~= LocalPlayer() then return end + + stopFind() +end +hook.Add("PlayerEndVoice", "DarkRP_VoiceChatReceiverFinder", stopFindVoice) diff --git a/gamemodes/darkrp/gamemode/modules/chat/cl_interface.lua b/gamemodes/darkrp/gamemode/modules/chat/cl_interface.lua new file mode 100644 index 0000000..8583c74 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/cl_interface.lua @@ -0,0 +1,56 @@ +DarkRP.addChatReceiver = DarkRP.stub{ + name = "addChatReceiver", + description = "Add a chat command with specific receivers", + parameters = { + { + name = "prefix", + description = "The chat command itself (\"/pm\", \"/ooc\", \"/me\" are some examples)", + type = "string", + optional = false + }, + { + name = "text", + description = "The text that shows up when it says \"Some people can hear you X\"", + type = "string", + optional = false + }, + { + name = "hearFunc", + description = "A function(ply, splitText) that decides whether this player can or cannot hear you.", + type = "function", + optional = false + } + }, + returns = {}, + metatable = DarkRP +} + +DarkRP.removeChatReceiver = DarkRP.stub{ + name = "removeChatReceiver", + description = "Remove a chat command receiver", + parameters = { + { + name = "prefix", + description = "The chat command itself (\"/pm\", \"/ooc\", \"/me\" are some examples)", + type = "string", + optional = false + } + }, + returns = {}, + metatable = DarkRP +} + +DarkRP.hookStub{ + name = "chatHideRecipient", + description = "Hide a receipent from who can hear/see your text GUI.", + parameters = { + { + name = "ply", + description = "The player who spoke.", + type = "Player" + } + }, + returns = { + + } +} diff --git a/gamemodes/darkrp/gamemode/modules/chat/sh_chatcommands.lua b/gamemodes/darkrp/gamemode/modules/chat/sh_chatcommands.lua new file mode 100644 index 0000000..22c32d3 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/sh_chatcommands.lua @@ -0,0 +1,216 @@ +local plyMeta = FindMetaTable("Player") +DarkRP.chatCommands = DarkRP.chatCommands or {} + +local validChatCommand = { + command = isstring, + description = isstring, + condition = fn.FOr{fn.Curry(fn.Eq, 2)(nil), isfunction}, + delay = isnumber, + tableArgs = fn.FOr{fn.Curry(fn.Eq, 2)(nil), isbool}, +} + +local checkChatCommand = function(tbl) + for k in pairs(validChatCommand) do + if not validChatCommand[k](tbl[k]) then + return false, k + end + end + return true +end + +function DarkRP.declareChatCommand(tbl) + local valid, element = checkChatCommand(tbl) + if not valid then + DarkRP.error("Incorrect chat command! " .. element .. " is invalid!", 2) + end + + tbl.command = string.lower(tbl.command) + DarkRP.chatCommands[tbl.command] = DarkRP.chatCommands[tbl.command] or tbl + for k, v in pairs(tbl) do + DarkRP.chatCommands[tbl.command][k] = v + end +end + +function DarkRP.removeChatCommand(command) + DarkRP.chatCommands[string.lower(command)] = nil +end + +function DarkRP.chatCommandAlias(command, ...) + local name + for k, v in ipairs{...} do + name = string.lower(v) + + DarkRP.chatCommands[name] = {command = name} + setmetatable(DarkRP.chatCommands[name], { + __index = DarkRP.chatCommands[command] + }) + end +end + +function DarkRP.getChatCommand(command) + return DarkRP.chatCommands[string.lower(command)] +end + +function DarkRP.getChatCommands() + return DarkRP.chatCommands +end + +function DarkRP.getSortedChatCommands() + local tbl = fn.Compose{table.ClearKeys, table.Copy, DarkRP.getChatCommands}() + table.SortByMember(tbl, "command", true) + + return tbl +end + +-- chat commands that have been defined, but not declared +DarkRP.getIncompleteChatCommands = fn.Curry(fn.Filter, 3)(fn.Compose{fn.Not, checkChatCommand})(DarkRP.chatCommands) + +--[[--------------------------------------------------------------------------- +Chat commands +---------------------------------------------------------------------------]] +DarkRP.declareChatCommand{ + command = "pm", + description = "Send a private message to someone.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "w", + description = "Say something in whisper voice.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "y", + description = "Yell something out loud.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "me", + description = "Chat roleplay to say you're doing things that you can't show otherwise.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "/", + description = "Global server chat.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "a", + description = "Global server chat.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "ooc", + description = "Global server chat.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "broadcast", + description = "Broadcast something as a mayor.", + delay = 1.5, + condition = plyMeta.isMayor +} + +DarkRP.declareChatCommand{ + command = "channel", + description = "Tune into a radio channel.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "radio", + description = "Say something through the radio.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "g", + description = "Group chat.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "credits", + description = "Send the DarkRP credits to someone.", + delay = 1.5 +} + + +local function init() + if not DarkRP then + MsgC(Color(255,0,0), "DarkRP Classic Advert tried to run, but DarkRP wasn't declared!\n") + return + end + + DarkRP.removeChatCommand("advert") + DarkRP.declareChatCommand({ + command = "advert", + description = "Displays an advertisement to everyone in chat.", + delay = 1.5 + }) + + if SERVER then + DarkRP.defineChatCommand("advert",function(ply,args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", "argument", "")) + return "" + end + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", "argument", "")) + return + end + for k,v in pairs(player.GetAll()) do + local col = team.GetColor(ply:Team()) + DarkRP.talkToPerson(v, col, "[Реклама] " .. ply:Nick(), Color(255, 255, 0, 255), text, ply) + end + end + hook.Call("playerAdverted", nil, ply, args) + return args, DoSay + end, 1.5) + else + DarkRP.addChatReceiver("/advert", "advertise", function(ply) return true end) + end +end + +if SERVER then + if #player.GetAll() > 0 then + init() + else + hook.Add("PlayerInitialSpawn", "dfca-load", init) + end +else + hook.Add("InitPostEntity", "dfca-load", init) +end + + +if SERVER then + +local function Roll(ply, args) + local DoSay = function() + if GAMEMODE.Config.alltalk then + for _, target in pairs(player.GetAll()) do + DarkRP.talkToPerson(target, team.GetColor(ply:Team()), ply:Nick().. " выпало " ..math.random(1,100).." из 100.") + end + else + DarkRP.talkToRange(ply, ply:Nick().. " выпало " ..math.random(1,100).." из 100.", "", 250) + end + end + return args, DoSay +end +DarkRP.defineChatCommand("roll", Roll, 1.5) + + + +DarkRP.declareChatCommand{ + command = "roll", + description = "write an roll", + delay = 1.5 +} +end diff --git a/gamemodes/darkrp/gamemode/modules/chat/sh_interface.lua b/gamemodes/darkrp/gamemode/modules/chat/sh_interface.lua new file mode 100644 index 0000000..f7b6bd0 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/sh_interface.lua @@ -0,0 +1,121 @@ +DarkRP.declareChatCommand = DarkRP.stub{ + name = "declareChatCommand", + description = "Declare a chat command (describe it)", + parameters = { + { + name = "table", + description = "The description of the chat command. Has to contain a string: command, string: description, number: delay, optional function: condition", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.removeChatCommand = DarkRP.stub{ + name = "removeChatCommand", + description = "Remove a chat command", + parameters = { + { + name = "command", + description = "The chat command to remove", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.chatCommandAlias = DarkRP.stub{ + name = "chatCommandAlias", + description = "Create an alias for a chat command", + parameters = { + { + name = "command", + description = "An already existing chat command.", + type = "string", + optional = false + }, + { + name = "alias", + description = "One or more aliases for the chat command.", + type = "vararg", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.getChatCommand = DarkRP.stub{ + name = "getChatCommand", + description = "Get the information on a chat command.", + parameters = { + { + name = "command", + description = "The chat command", + type = "string", + optional = false + } + }, + returns = { + { + name = "chatTable", + description = "A table containing the information of the chat command.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.getChatCommands = DarkRP.stub{ + name = "getChatCommands", + description = "Get every chat command.", + parameters = { + + }, + returns = { + { + name = "commands", + description = "A table containing every command. Table indices are the command strings.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.getSortedChatCommands = DarkRP.stub{ + name = "getSortedChatCommands", + description = "Get every chat command, sorted by their name.", + parameters = { + + }, + returns = { + { + name = "commands", + description = "A table containing every command.", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.getIncompleteChatCommands = DarkRP.stub{ + name = "getIncompleteChatCommands", + description = "chat commands that have been defined, but not declared. Information about these chat commands is missing.", + parameters = { + }, + returns = { + { + name = "commands", + description = "A table containing the undeclared chat commands.", + type = "table" + } + }, + metatable = DarkRP +} diff --git a/gamemodes/darkrp/gamemode/modules/chat/sv_chat.lua b/gamemodes/darkrp/gamemode/modules/chat/sv_chat.lua new file mode 100644 index 0000000..c370df8 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/sv_chat.lua @@ -0,0 +1,183 @@ +local function registerCommandDefinition(cmd, callback) + local chatcommands = DarkRP.getChatCommands() + + chatcommands[cmd] = chatcommands[cmd] or {} + chatcommands[cmd].callback = callback + chatcommands[cmd].command = chatcommands[cmd].command or cmd +end + +function DarkRP.defineChatCommand(cmd, callback) + cmd = string.lower(cmd) + local detour = function(ply, arg, ...) + local canChatCommand = gamemode.Call("canChatCommand", ply, cmd, arg, ...) + if not canChatCommand then + return "" + end + + local ret = {callback(ply, arg, ...)} + local overrideTxt, overrideDoSayFunc = hook.Run("onChatCommand", ply, cmd, arg, ret, ...) + + if overrideTxt then return overrideTxt, overrideDoSayFunc end + return unpack(ret) + end + + registerCommandDefinition(cmd, detour) +end + +function DarkRP.definePrivilegedChatCommand(cmd, priv, callback, extraInfoTbl) + cmd = string.lower(cmd) + + local function onCAMIResult(ply, arg, hasAccess, reason) + if hasAccess then return callback(ply, arg) end + + local notify = ply:EntIndex() == 0 and print or fp{DarkRP.notify, ply, 1, 4} + notify(DarkRP.getPhrase("no_privilege")) + end + + local function callbackdetour(ply, arg, ...) + local canChatCommand = gamemode.Call("canChatCommand", ply, cmd, arg) + if not canChatCommand then + return "" + end + + CAMI.PlayerHasAccess(ply, priv, fp{onCAMIResult, ply, arg}, nil, extraInfoTbl) + + local overrideTxt, overrideDoSayFunc = hook.Run("onChatCommand", ply, cmd, arg, {""}) + + if overrideTxt then return overrideTxt, overrideDoSayFunc end + return "" + end + + registerCommandDefinition(cmd, callbackdetour) +end + +local function RP_PlayerChat(ply, text, teamonly) + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. "): " .. text) + local callback = "" + local DoSayFunc + local groupSay = DarkRP.getChatCommand("g") + + -- Extract the chat command + local tblCmd = fn.Compose{ + DarkRP.getChatCommand, + string.lower, + fn.Curry(fn.Flip(string.sub), 2)(2), -- extract prefix + fn.Curry(fn.GetValue, 2)(1), -- Get the first word + fn.Curry(string.Explode, 2)(' ') -- split by spaces + }(text) + + if string.sub(text, 1, 1) == GAMEMODE.Config.chatCommandPrefix and tblCmd then + local args = string.sub(text, string.len(tblCmd.command) + 3, string.len(text)) + args = tblCmd.tableArgs and DarkRP.explodeArg(args) or args + + ply.DrpCommandDelays = ply.DrpCommandDelays or {} + if tblCmd.delay and ply.DrpCommandDelays[tblCmd.command] and ply.DrpCommandDelays[tblCmd.command] > CurTime() - tblCmd.delay then + return "" + end + + ply.DrpCommandDelays[tblCmd.command] = CurTime() + + callback, DoSayFunc = tblCmd.callback(ply, args) + if callback == "" then + return "", "", DoSayFunc + end + text = string.sub(text, string.len(tblCmd.command) + 3, string.len(text)) + elseif teamonly and groupSay then + callback, DoSayFunc = groupSay.callback(ply, text) + return text, "", DoSayFunc + end + + if callback ~= "" then + callback = callback or "" .. " " + end + + return text, callback, DoSayFunc; +end + +local function RP_ActualDoSay(ply, text, callback) + callback = callback or "" + if text == "" then return "" end + local col = team.GetColor(ply:Team()) + local col2 = color_white + if not ply:Alive() then + col2 = Color(255, 200, 200, 255) + col = col2 + end + + if GAMEMODE.Config.alltalk then + local name = ply:Nick() + for _, v in ipairs(player.GetAll()) do + DarkRP.talkToPerson(v, col, callback .. name, col2, text, ply) + end + else + DarkRP.talkToRange(ply, callback .. ply:Nick(), text, GAMEMODE.Config.talkDistance) + end + return "" +end + +function GM:canChatCommand(ply, cmd, ...) + if not ply.DarkRPUnInitialized then return true end + + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("data_not_loaded_one")) + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("data_not_loaded_two")) + + return false +end + +g_DarkRPOldHookCall = g_DarkRPOldHookCall or hook.Call + +local GM = GM +function hook.Call(name, gm, ply, text, teamonly, ...) + if name == "PlayerSay" then + local dead = not ply:Alive() + + local text2 = text + local callback + local DoSayFunc + + text2 = g_DarkRPOldHookCall(name, gm, ply, text, teamonly, dead) or text2 + + text2, callback, DoSayFunc = RP_PlayerChat(ply, text2, teamonly) + if tostring(text2) == " " then text2, callback = callback, text2 end + if not GM.Config.deadtalk and dead then return "" end + + if game.IsDedicated() then + ServerLog("\"" .. ply:Nick() .. "<" .. ply:UserID() .. ">" .. "<" .. ply:SteamID() .. ">" .. "<" .. team.GetName(ply:Team()) .. ">\" say \"" .. text .. "\"\n" .. "\n") + end + + if DoSayFunc then DoSayFunc(text2) return "" end + RP_ActualDoSay(ply, text2, callback) + + hook.Call("PostPlayerSay", nil, ply, text2, teamonly, dead) + return "" + end + + return g_DarkRPOldHookCall(name, gm, ply, text, teamonly, ...) +end + +local function ConCommand(ply, _, args) + if not args[1] then return end + local cmd = string.lower(args[1]) + local tbl = DarkRP.getChatCommand(cmd) + + if not tbl then return end + + table.remove(args, 1) -- Remove subcommand + local arg = tbl.tableArgs and args or table.concat(args, ' ') + local time = CurTime() + + if not tbl then return end + + ply.DrpCommandDelays = ply.DrpCommandDelays or {} + + if IsValid(ply) then -- Server console isn't valid + if tbl.delay and ply.DrpCommandDelays[cmd] and ply.DrpCommandDelays[cmd] > time - tbl.delay then + return + end + + ply.DrpCommandDelays[cmd] = time + end + + tbl.callback(ply, arg) +end +concommand.Add("darkrp", ConCommand) diff --git a/gamemodes/darkrp/gamemode/modules/chat/sv_chatcommands.lua b/gamemodes/darkrp/gamemode/modules/chat/sv_chatcommands.lua new file mode 100644 index 0000000..a5231a8 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/sv_chatcommands.lua @@ -0,0 +1,241 @@ +--[[--------------------------------------------------------- +Talking + ---------------------------------------------------------]] +local function PM(ply, args) + local namepos = string.find(args, " ") + if not namepos then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + local name = string.sub(args, 1, namepos - 1) + local msg = string.sub(args, namepos + 1) + + if msg == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + local target = DarkRP.findPlayer(name) + if target == ply then return "" end + + if target then + local col = team.GetColor(ply:Team()) + local pname = ply:Nick() + local col2 = color_white + DarkRP.talkToPerson(target, col, "(PM) " .. pname, col2, msg, ply) + DarkRP.talkToPerson(ply, col, "(PM) " .. pname, col2, msg, ply) + else + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(name))) + end + + return "" +end +DarkRP.defineChatCommand("pm", PM, 1.5) + +local function Whisper(ply, args) + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + DarkRP.talkToRange(ply, "(" .. DarkRP.getPhrase("whisper") .. ") " .. ply:Nick(), text, GAMEMODE.Config.whisperDistance) + end + return args, DoSay +end +DarkRP.defineChatCommand("w", Whisper, 1.5) + +local function Yell(ply, args) + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + DarkRP.talkToRange(ply, "(" .. DarkRP.getPhrase("yell") .. ") " .. ply:Nick(), text, GAMEMODE.Config.yellDistance) + end + return args, DoSay +end +DarkRP.defineChatCommand("y", Yell, 1.5) + +local function Me(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + if GAMEMODE.Config.alltalk then + local col = team.GetColor(ply:Team()) + local name = ply:Nick() + for _, target in ipairs(player.GetAll()) do + DarkRP.talkToPerson(target, col, name .. " " .. text) + end + else + DarkRP.talkToRange(ply, ply:Nick() .. " " .. text, "", GAMEMODE.Config.meDistance) + end + end + return args, DoSay +end +DarkRP.defineChatCommand("me", Me, 1.5) + +local function OOC(ply, args) + if not GAMEMODE.Config.ooc then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", DarkRP.getPhrase("ooc"), "")) + return "" + end + + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + local col = team.GetColor(ply:Team()) + local col2 = color_white + if not ply:Alive() then + col2 = Color(255, 200, 200, 255) + col = col2 + end + + local phrase = DarkRP.getPhrase("ooc") + local name = ply:Nick() + for _, v in ipairs(player.GetAll()) do + DarkRP.talkToPerson(v, col, "(" .. phrase .. ") " .. name, col2, text, ply) + end + end + return args, DoSay +end +DarkRP.defineChatCommand("/", OOC, true, 1.5) +DarkRP.defineChatCommand("a", OOC, true, 1.5) +DarkRP.defineChatCommand("ooc", OOC, true, 1.5) + +local function MayorBroadcast(ply, args) + if args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + local Team = ply:Team() + if not RPExtraTeams[Team] or not RPExtraTeams[Team].mayor then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("incorrect_job", DarkRP.getPhrase("broadcast"))) + return "" + end + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return + end + + local col = team.GetColor(ply:Team()) + local col2 = Color(170, 0, 0, 255) + local phrase = DarkRP.getPhrase("broadcast") + local name = ply:Nick() + for _, v in ipairs(player.GetAll()) do + DarkRP.talkToPerson(v, col, phrase .. " " .. name, col2, text, ply) + end + end + return args, DoSay +end +DarkRP.defineChatCommand("broadcast", MayorBroadcast, 1.5) + +local function SetRadioChannel(ply,args) + local channel = DarkRP.toInt(args) + if channel == nil or channel < 0 or channel > 100 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "0<" .. DarkRP.getPhrase("channel") .. "<100")) + return "" + end + DarkRP.notify(ply, 2, 4, DarkRP.getPhrase("channel_set_to_x", args)) + ply.RadioChannel = channel + return "" +end +DarkRP.defineChatCommand("channel", SetRadioChannel) + +local function SayThroughRadio(ply,args) + if not ply.RadioChannel then ply.RadioChannel = 1 end + local radioChannel = ply.RadioChannel + if not args or args == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return "" + end + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return + end + local col = Color(180, 180, 180, 255) + local phrase = DarkRP.getPhrase("radio_x", radioChannel) + for _, v in ipairs(player.GetAll()) do + if v.RadioChannel == radioChannel then + DarkRP.talkToPerson(v, col, phrase, col, text, ply) + end + end + end + return args, DoSay +end +DarkRP.defineChatCommand("radio", SayThroughRadio, 1.5) + +local function GroupMsg(ply, args) + local DoSay = function(text) + if text == "" then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "")) + return + end + + local col = team.GetColor(ply:Team()) + + local groupChats = {} + for _, func in pairs(GAMEMODE.DarkRPGroupChats) do + -- not the group of the player + if not func(ply) then continue end + + table.insert(groupChats, func) + end + + if table.IsEmpty(groupChats) then return "" end + + local phrase = DarkRP.getPhrase("group") + local name = ply:Nick() + local color = color_white + for _, target in ipairs(player.GetAll()) do + -- The target is in any of the group chats + for _, func in ipairs(groupChats) do + if not func(target, ply) then continue end + + DarkRP.talkToPerson(target, col, phrase .. " " .. name, color, text, ply) + break + end + end + end + return args, DoSay +end +DarkRP.defineChatCommand("g", GroupMsg, 0) + +-- here's the new easter egg. Easier to find, more subtle, doesn't only credit FPtje and unib5 +-- WARNING: DO NOT EDIT THIS +-- You can edit DarkRP but you HAVE to credit the original authors! +-- You even have to credit all the previous authors when you rename the gamemode. +-- local CreditsWait = true +local function GetDarkRPAuthors(ply, args) + local target = DarkRP.findPlayer(args) -- Only send to one player. Prevents spamming + target = IsValid(target) and target or ply + + if target ~= ply then + if ply.CreditsWait then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("wait_with_that")) return "" end + ply.CreditsWait = true + timer.Simple(60, function() if IsValid(ply) then ply.CreditsWait = nil end end) -- so people don't spam it + end + + local rf = RecipientFilter() + rf:AddPlayer(target) + if ply ~= target then + rf:AddPlayer(ply) + end + + umsg.Start("DarkRP_Credits", rf) + umsg.End() + + return "" +end +DarkRP.defineChatCommand("credits", GetDarkRPAuthors, 50) diff --git a/gamemodes/darkrp/gamemode/modules/chat/sv_interface.lua b/gamemodes/darkrp/gamemode/modules/chat/sv_interface.lua new file mode 100644 index 0000000..f55b0a9 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chat/sv_interface.lua @@ -0,0 +1,145 @@ +DarkRP.defineChatCommand = DarkRP.stub{ + name = "defineChatCommand", + description = "Create a chat command that calls the function", + parameters = { + { + name = "chat command", + description = "The registered chat command", + type = "string", + optional = false + }, + { + name = "callback", + description = "The function that is called when the chat command is executed", + type = "function", + optional = false + } + }, + returns = {}, + metatable = DarkRP +} + +DarkRP.definePrivilegedChatCommand = DarkRP.stub{ + name = "definePrivilegedChatCommand", + description = "Create a chat command that calls the function if the player has the right CAMI privilege. Will automatically notify the user when they don't have access. Note that chat command functions registered with this function can NOT override the chat that will appear after the command has been executed.", + parameters = { + { + name = "chat command", + description = "The registered chat command", + type = "string", + optional = false + }, + { + name = "privilege", + description = "The name of the CAMI privilege", + type = "string", + optional = false + }, + { + name = "callback", + description = "The function that is called when the chat command is executed", + type = "function", + optional = false + } + }, + returns = {}, + metatable = DarkRP +} + +DarkRP.hookStub{ + name = "PostPlayerSay", + description = "Called after a player has said something.", + parameters = { + { + name = "ply", + description = "The player who spoke.", + type = "Player" + }, + { + name = "text", + description = "The thing they said.", + type = "string" + }, + { + name = "teamonly", + description = "Whether they said it to their team only.", + type = "boolean" + }, + { + name = "dead", + description = "Whether they are dead.", + type = "boolean" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "canChatCommand", + description = "Called when a player tries to run any chat command or uses the DarkRP console command. ", + parameters = { + { + name = "ply", + description = "The player who spoke.", + type = "Player" + }, + { + name = "command", + description = "The thing they said.", + type = "string" + }, + { + name = "arguments", + description = "The arguments of the chat command, given as one string.", + type = "string" + } + }, + returns = { + { + name = "canChatCommand", + description = "Whether the player is allowed to run the chat command.", + type = "boolean" + }, + } +} + +DarkRP.hookStub{ + name = "onChatCommand", + description = "Called after a player has run any chat command or uses the DarkRP console command. Note: the chat command has already been run. Use canChatCommand if you want to stop chat commands from being run.", + parameters = { + { + name = "ply", + description = "The player who spoke.", + type = "Player" + }, + { + name = "command", + description = "The thing they said.", + type = "string" + }, + { + name = "arguments", + description = "The arguments of the chat command, given either as one string or a table of strings. That depends on whether the command is declared to use table arguments.", + type = "string" + }, + { + name = "return", + description = "The return value of the chat command function. Should contain a chat text override and/or a say function. See the return values of this hook for a description", + type = "table" + } + }, + returns = { + { + name = "overrideText", + description = "Overrides the text a chat command will put in everyone's chat box. Return nil to not change behaviour.", + type = "string" + }, + { + name = "overrideSayFunc", + description = "Say functions handle what needs to be said to whom. The say function for PMs for example make sure only the sender and receiver see the message. You can override this behaviour by returning a different say function in this hook. Return nil to not change behaviour.", + type = "function" + }, + } +} diff --git a/gamemodes/darkrp/gamemode/modules/chatindicator/cl_init.lua b/gamemodes/darkrp/gamemode/modules/chatindicator/cl_init.lua new file mode 100644 index 0000000..2b45fbb --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chatindicator/cl_init.lua @@ -0,0 +1,70 @@ +local function drawIndicator(ply) + if not ply:IsTyping() then + if ply.indicator then + ply.indicator:Remove() + ply.indicator = nil + end + return + end + + local chatIndicator = hook.Call("DrawChatIndicator", nil, ply) + if chatIndicator == true then return end + + local indicator = ply.indicator + if not IsValid(indicator) then + indicator = ClientsideModel("models/extras/info_speech.mdl", RENDERGROUP_OPAQUE) + if not IsValid(indicator) then return end -- In case the non networked entity limit is hit (still prints a red error message, but doesn't spam client console with lua errors) + ply.indicator = indicator + end + indicator:SetNoDraw(true) + indicator:SetModelScale(0.6) + + local ragdoll = ply:GetRagdollEntity() + if IsValid(ragdoll) then + local maxs = ragdoll:OBBMaxs() + indicator:SetPos(ragdoll:GetPos() + Vector(0, 0, maxs.z) + Vector(0, 0, 12)) + else + indicator:SetPos(ply:GetPos() + Vector(0, 0, 72 * ply:GetModelScale()) + Vector(0, 0, 12)) + end + + local curTime = CurTime() + local angle = indicator:GetAngles() + angle.y = (angle.y + (360 * (curTime - (indicator.lastDraw or 0)))) % 360 + indicator:SetAngles(angle) + indicator.lastDraw = curTime + + indicator:SetupBones() + indicator:DrawModel() +end + +hook.Add("PostPlayerDraw", "DarkRP_ChatIndicator", drawIndicator) +hook.Add("CreateClientsideRagdoll", "DarkRP_ChatIndicator", function(ent, ragdoll) + if not ent:IsPlayer() then return end + + local oldRenderOverride = ragdoll.RenderOverride -- Just in case - best be safe + ragdoll.RenderOverride = function(self) + if ent:IsValid() then + drawIndicator(ent) + end + + if oldRenderOverride then + oldRenderOverride(self) + else + self:DrawModel() + end + end +end) + +-- CSEnts aren't GC'd. +-- https://github.com/Facepunch/garrysmod-issues/issues/1387 +gameevent.Listen("player_disconnect") +hook.Add("player_disconnect", "DarkRP_ChatIndicator", function(data) + local ply = Player(data.userid) + + if not IsValid(ply) then return end -- disconnected while joining + + if ply.indicator then + ply.indicator:Remove() + ply.indicator = nil + end +end) diff --git a/gamemodes/darkrp/gamemode/modules/chatindicator/cl_interface.lua b/gamemodes/darkrp/gamemode/modules/chatindicator/cl_interface.lua new file mode 100644 index 0000000..640345d --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chatindicator/cl_interface.lua @@ -0,0 +1,18 @@ +DarkRP.hookStub{ + name = "DrawChatIndicator", + description = "Call when the Chat Indicator is drawn. Return to overwrite.", + parameters = { + { + name = "ply", + description = "The player the indicator should be drawn for.", + type = "Player" + } + }, + returns = { + { + name = "override", + description = "Return true in your hook to disable the default drawing.", + type = "boolean" + } + } +} diff --git a/gamemodes/darkrp/gamemode/modules/chatsounds.lua b/gamemodes/darkrp/gamemode/modules/chatsounds.lua new file mode 100644 index 0000000..487ae41 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/chatsounds.lua @@ -0,0 +1,377 @@ +-- This module will make voice sounds play when certain words are typed in the chat +-- You can add/remove sounds as you wish using DarkRP.setChatSound, just follow the format used here +-- To disable them completely, set GM.Config.chatsounds to false +-- TODO: Add female sounds & detect gender of model, and use combine sounds for CPs + +local sounds = {} +sounds["ammo"] = {"vo/npc/male01/ammo03.wav", "vo/npc/male01/ammo04.wav", "vo/npc/male01/ammo05.wav"} + +sounds["behind you"] = {"vo/npc/male01/behindyou01.wav", "vo/npc/male01/behindyou02.wav"} + +sounds["better reload"] = {"vo/npc/male01/youdbetterreload01.wav"} + +sounds["bullshit"] = {"vo/npc/male01/question26.wav"} + +sounds["bull shit"] = sounds["bullshit"] + +sounds["cheese"] = {"vo/npc/male01/question06.wav"} + +sounds["combine"] = {"vo/npc/male01/combine01.wav", "vo/npc/male01/combine02.wav"} + +sounds["coming"] = {"vo/npc/male01/squad_approach04.wav"} + +sounds["cops"] = {"vo/npc/male01/civilprotection01.wav", "vo/npc/male01/civilprotection02.wav", "vo/npc/male01/cps01.wav", "vo/npc/male01/cps02.wav"} + +sounds["cp"] = sounds["cops"] +sounds["cps"] = sounds["cops"] + +sounds["cut it"] = {"vo/trainyard/male01/cit_hit01.wav", "vo/trainyard/male01/cit_hit02.wav", "vo/trainyard/male01/cit_hit03.wav", "vo/trainyard/male01/cit_hit04.wav", "vo/trainyard/male01/cit_hit05.wav"} + +sounds["dont tell me"] = {"vo/npc/male01/gordead_ans03.wav"} + +sounds["de ja vu"] = {"vo/npc/male01/question05.wav"} + +sounds["dejavu"] = sounds["de ja vu"] + +sounds["excuse me"] = {"vo/npc/male01/excuseme01.wav", "vo/npc/male01/excuseme02.wav"} + +sounds["fantastic"] = {"vo/npc/male01/fantastic01.wav", "vo/npc/male01/fantastic02.wav"} + +sounds["figures"] = {"vo/npc/male01/answer03.wav"} + +sounds["finally"] = {"vo/npc/male01/finally.wav"} + +sounds["follow"] = {"vo/coast/odessa/male01/stairman_follow01.wav", "vo/npc/male01/squad_away03.wav", "vo/coast/cardock/le_followme.wav"} + +sounds["focus"] = {"vo/npc/male01/answer18.wav", "vo/npc/male01/answer19.wav"} + +sounds["freeman"] = {"vo/npc/male01/freeman.wav", "vo/npc/male01/docfreeman01.wav", "vo/npc/male01/docfreeman02.wav"} + +sounds["get down"] = {"vo/npc/male01/getdown02.wav"} + +sounds["get in"] = {"vo/canals/gunboat_getin.wav"} + +sounds["get out"] = {"vo/npc/male01/gethellout.wav"} + +sounds["good god"] = {"vo/npc/male01/goodgod.wav", "vo/npc/male01/gordead_ans04.wav"} + +sounds["gosh"] = sounds["good god"] + +sounds["got one"] = {"vo/npc/male01/gotone01.wav", "vo/npc/male01/gotone01.wav"} + +sounds["gotta reload"] = {"vo/npc/male01/gottareload01.wav"} + +sounds["gtfo"] = sounds["get out"] + +sounds["hacks"] = {"vo/npc/male01/hacks01.wav", "vo/npc/male01/hacks02.wav", "vo/npc/male01/thehacks01.wav", "vo/npc/male01/thehacks02.wav"} + +sounds["hax"] = sounds["hacks"] +sounds["haxx"] = sounds["hacks"] + +sounds["help"] = {"vo/npc/male01/help01.wav"} + +sounds["here they come"] = {"vo/npc/male01/heretheycome01.wav", "vo/npc/male01/incoming02.wav"} + +sounds["hello"] = {"vo/npc/male01/hi01.wav", "vo/npc/male01/hi02.wav"} + +sounds["hey"] = sounds["hello"] +sounds["hi"] = sounds["hello"] + +sounds["heads up"] = {"vo/npc/male01/headsup01.wav", "vo/npc/male01/headsup02.wav"} + +sounds["he's dead"] = {"vo/npc/male01/gordead_ques01.wav", "vo/npc/male01/gordead_ques07.wav"} + +sounds["he is dead"] = sounds["he's dead"] + +sounds["how about that"] = {"vo/npc/male01/answer25.wav"} + +sounds["i know"] = {"vo/npc/male01/answer08.wav"} + +sounds["ill stay here"] = {"vo/npc/male01/illstayhere01.wav", "vo/npc/male01/holddownspot01.wav", "vo/npc/male01/holddownspot02.wav", "vo/npc/male01/imstickinghere01.wav", "vo/npc/male01/littlecorner01.wav"} + +sounds["i'll stay here"] = sounds["ill stay here"] +sounds["i will stay here"] = sounds["ill stay here"] + +sounds["im busy"] = {"vo/npc/male01/busy02.wav"} + +sounds["i'm busy"] = sounds["im busy"] + +sounds["im with you"] = {"vo/npc/male01/answer13.wav"} + +sounds["i'm with you"] = sounds["im with you"] + +sounds["isnt good"] = {"vo/trainyard/male01/cit_window_use01.wav"} + +sounds["isn't good"] = sounds["isnt good"] +sounds["incoming"] = sounds["here they come"] + +sounds["it cant be"] = {"vo/npc/male01/gordead_ques06.wav"} + +sounds["it can't be"] = sounds["it cant be"] + +sounds["it is okay"] = {"vo/npc/male01/answer02.wav"} + +sounds["it's okay"] = sounds["it is okay"] + +sounds["kay"] = {"vo/npc/male01/ok01.wav", "vo/npc/male01/ok02.wav"} + +sounds["kk"] = sounds["kay"] + +sounds["lead the way"] = {"vo/npc/male01/leadtheway01.wav", "vo/npc/male01/leadtheway02.wav"} + +sounds["lead on"] = sounds["lead the way"] + +sounds["lets go"] = {"vo/npc/male01/letsgo01.wav", "vo/npc/male01/letsgo02.wav"} + +sounds["let's go"] = sounds["lets go"] + +sounds["never"] = {"vo/Citadel/eli_nonever.wav"} + +sounds["never can tell"] = {"vo/npc/male01/answer23.wav"} + +sounds["nice"] = {"vo/npc/male01/nice.wav"} + +sounds["no"] = {"vo/Citadel/br_no.wav", "vo/Citadel/eli_notobreen.wav"} + +sounds["not good"] = sounds["isnt good"] + +sounds["not sure"] = {"vo/npc/male01/answer21.wav"} + +sounds["now what"] = {"vo/npc/male01/gordead_ans01.wav", "vo/npc/male01/gordead_ans15.wav"} + +sounds["oh no"] = {"vo/npc/male01/gordead_ans05.wav", "vo/npc/male01/ohno.wav"} + +sounds["oh my god"] = sounds["good god"] +sounds["omg"] = sounds["good god"] +sounds["omfg"] = sounds["good god"] +sounds["ok"] = sounds["kay"] +sounds["okay"] = sounds["kay"] + +sounds["oops"] = {"vo/npc/male01/whoops01.wav"} + +sounds["over here"] = {"vo/npc/male01/overhere01.wav", "vo/npc/male01/squad_away02.wav"} + +sounds["over there"] = {"vo/npc/male01/overthere01.wav", "vo/npc/male01/overthere02.wav"} + +sounds["pardon me"] = {"vo/npc/male01/pardonme01.wav", "vo/npc/male01/pardonme02.wav"} + +sounds["please no"] = {"vo/npc/male01/gordead_ans06.wav"} + +sounds["right on"] = {"vo/npc/male01/answer18.wav"} + +sounds["run"] = {"vo/npc/male01/strider_run.wav"} + +sounds["same here"] = {"vo/npc/male01/answer07.wav"} + +sounds["shut up"] = {"vo/npc/male01/answer17.wav"} + +sounds["spread the word"] = {"vo/npc/male01/gordead_ans10.wav"} + +sounds["stop it"] = sounds["cut it"] +sounds["stop that"] = sounds["cut it"] + +sounds["stop looking at me"] = {"vo/npc/male01/vquestion01.wav"} + +sounds["sorry"] = {"vo/npc/male01/sorry01.wav", "vo/npc/male01/sorry02.wav", "vo/npc/male01/sorry03.wav"} + +sounds["sry"] = sounds["sorry"] + +sounds["take cover"] = {"vo/npc/male01/takecover02.wav"} + +sounds["take this medkit"] = {"vo/npc/male01/health01.wav", "vo/npc/male01/health02.wav", "vo/npc/male01/health03.wav", "vo/npc/male01/health04.wav"} + +sounds["task at hand"] = {"vo/npc/male01/answer18.wav"} + +sounds["talking to me"] = {"vo/npc/male01/answer30.wav"} + +sounds["thats you"] = {"vo/npc/male01/answer01.wav"} + +sounds["this cant be"] = sounds["it cant be"] +sounds["this can't be"] = sounds["it cant be"] + +sounds["this is bad"] = {"vo/npc/male01/gordead_ques10.wav"} + +sounds["too much info"] = {"vo/npc/male01/answer26.wav"} + +sounds["too much information"] = sounds["too much info"] + +sounds["uhoh"] = {"vo/npc/male01/uhoh.wav"} + +sounds["uh oh"] = sounds["uhoh"] + +sounds["wait"] = {"vo/trainyard/man_waitaminute.wav"} + +sounds["wait for me"] = {"vo/npc/male01/squad_reinforce_single04.wav"} + +sounds["wait for us"] = {"vo/npc/male01/squad_reinforce_group04.wav"} + +sounds["wanna bet"] = {"vo/npc/male01/answer27.wav"} + +sounds["watch out"] = {"vo/npc/male01/watchout.wav"} + +sounds["we are done for"] = {"vo/npc/male01/gordead_ans14.wav"} + +sounds["we're done for"] = sounds["we are done for"] + +sounds["what now"] = {"vo/npc/male01/gordead_ques16.wav"} + +sounds["whatever you say"] = {"vo/npc/male01/squad_affirm03.wav"} + +sounds["whats the use"] = {"vo/npc/male01/gordead_ans11.wav"} + +sounds["what's the use"] = sounds["whats the use"] + +sounds["whats the point"] = {"vo/npc/male01/gordead_ans12.wav"} + +sounds["what's the point"] = sounds["whats the point"] +sounds["whoops"] = sounds["oops"] + +sounds["why go on"] = {"vo/npc/male01/gordead_ans13.wav"} + +sounds["why telling me"] = {"vo/npc/male01/answer24.wav"} + +sounds["yeah"] = {"vo/npc/male01/yeah02.wav"} + +sounds["yes"] = sounds["yeah"] + +sounds["you and me both"] = {"vo/npc/male01/answer14.wav"} + +sounds["you never know"] = {"vo/npc/male01/answer22.wav"} + +sounds["you sure"] = {"vo/npc/male01/answer37.wav"} + +DarkRP.hookStub{ + name = "canChatSound", + description = "Whether a chat sound can be played.", + parameters = { + { + name = "ply", + description = "The player who triggered the chat sound.", + type = "Player" + }, + { + name = "chatPhrase", + description = "The chat sound phrase that has been detected.", + type = "string" + }, + { + name = "chatText", + description = "The whole chat text the player sent that contains the chat sound phrase.", + type = "string" + } + }, + returns = { + { + name = "canChatSound", + description = "False if the chat sound should not be played.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "onChatSound", + description = "When a chat sound is played.", + parameters = { + { + name = "ply", + description = "The player who triggered the chat sound.", + type = "Player" + }, + { + name = "chatPhrase", + description = "The chat sound phrase that was detected.", + type = "string" + }, + { + name = "chatText", + description = "The whole chat text the player sent that contains the chat sound phrase.", + type = "string" + } + }, + returns = { + } +} + +local function CheckChat(ply, text) + if not GAMEMODE.Config.chatsounds or ply.nextSpeechSound and ply.nextSpeechSound > CurTime() then return end + local prefix = string.sub(text, 0, 1) + if prefix == "/" or prefix == "!" or prefix == "@" then return end -- should cover most chat commands for various mods/addons + local longestMatch = nil + local longestMatchLength = 0 + for k, v in pairs(sounds) do + local res1, res2 = string.find(string.lower(text), k) + if not res1 then continue end + local charBefore = text[res1 - 1] + local charAfter = text[res2 + 1] + local length = res2 - res1 + -- Check whether the match is not part of a larger word (e.g. "no" should not match when "know" is said) + if charBefore and charBefore ~= "" and charBefore ~= " " then continue end + if charAfter and charAfter ~= "" and charAfter ~= " " then continue end + + if length > longestMatchLength then + longestMatch = k + longestMatchLength = length + end + end + + if not longestMatch then return end + + local canChatSound = hook.Call("canChatSound", nil, ply, longestMatch, text) + if canChatSound == false then return end + ply:EmitSound(table.Random(sounds[longestMatch]), 80, 100) + ply.nextSpeechSound = CurTime() + GAMEMODE.Config.chatsoundsdelay -- make sure they don't spam HAX HAX HAX, if the server owner so desires + hook.Call("onChatSound", nil, ply, longestMatch, text) +end +hook.Add("PostPlayerSay", "ChatSounds", CheckChat) + +DarkRP.getChatSound = DarkRP.stub{ + name = "getChatSound", + description = "Get a chat sound (play a noise when someone says something) associated with the given phrase.", + parameters = { + { + name = "text", + description = "The text that triggers the chat sound.", + type = "string", + optional = false + } + }, + returns = { + { + name = "soundPaths", + description = "A table of string sound paths associated with the given text.", + type = "table" + } + }, + metatable = DarkRP +} + +function DarkRP.getChatSound(text) + return sounds[string.lower(text or "")] +end + +DarkRP.setChatSound = DarkRP.stub{ + name = "setChatSound", + description = "Set a chat sound (play a noise when someone says something)", + parameters = { + { + name = "text", + description = "The text that should trigger the sound.", + type = "string", + optional = false + }, + { + name = "sounds", + description = "A table of string sound paths.", + type = "table", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +function DarkRP.setChatSound(text, sndTable) + sounds[string.lower(text or "")] = sndTable +end diff --git a/gamemodes/darkrp/gamemode/modules/cppi/sh_cppi.lua b/gamemodes/darkrp/gamemode/modules/cppi/sh_cppi.lua new file mode 100644 index 0000000..d0ee651 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/cppi/sh_cppi.lua @@ -0,0 +1,56 @@ +if CPPI then return end +CPPI = {} +CPPI.CPPI_DEFER = 100100 --\100\100 = dd +CPPI.CPPI_NOTIMPLEMENTED = 7080 + +function CPPI:GetName() + return "DarkRP" +end + +function CPPI:GetVersion() + return CPPI.CPPI_NOTIMPLEMENTED +end + +function CPPI:GetInterfaceVersion() + return CPPI.CPPI_NOTIMPLEMENTED +end + +function CPPI:GetNameFromUID(uid) + return CPPI.CPPI_NOTIMPLEMENTED +end + +local PLAYER = FindMetaTable("Player") +function PLAYER:CPPIGetFriends() + return CPPI.CPPI_NOTIMPLEMENTED +end + +local ENTITY = FindMetaTable("Entity") +function ENTITY:CPPIGetOwner() + return NULL, CPPI.CPPI_NOTIMPLEMENTED +end + +if SERVER then + function ENTITY:CPPISetOwner(ply) + return CPPI.CPPI_NOTIMPLEMENTED + end + + function ENTITY:CPPISetOwnerUID(UID) + return CPPI.CPPI_NOTIMPLEMENTED + end + + function ENTITY:CPPICanTool(ply, tool) + return CPPI.CPPI_NOTIMPLEMENTED + end + + function ENTITY:CPPICanPhysgun(ply) + return CPPI.CPPI_NOTIMPLEMENTED + end + + function ENTITY:CPPICanPickup(ply) + return CPPI.CPPI_NOTIMPLEMENTED + end + + function ENTITY:CPPICanPunt(ply) + return CPPI.CPPI_NOTIMPLEMENTED + end +end diff --git a/gamemodes/darkrp/gamemode/modules/darkrpmessages/cl_darkrpmessage.lua b/gamemodes/darkrp/gamemode/modules/darkrpmessages/cl_darkrpmessage.lua new file mode 100644 index 0000000..1a07618 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/darkrpmessages/cl_darkrpmessage.lua @@ -0,0 +1,26 @@ +local MotdMessage = +[[ + + +--------------------------------------------------------------------------- + DarkRP Message of the day! +--------------------------------------------------------------------------- +]] + +local endMOTD = "---------------------------------------------------------------------------\n" + +local function drawMOTD(text) + MsgC(Color(255, 20, 20, 255), MotdMessage, color_white, text, Color(255, 20, 20, 255), endMOTD) +end + +local function receiveMOTD(html, len, headers, code) + if not headers or headers.Status and string.sub(headers.Status, 1, 3) ~= "200" then return end + drawMOTD(html) +end + +local function showMOTD() + http.Fetch("https://raw.githubusercontent.com/FPtje/DarkRPMotd/master/motd.txt", receiveMOTD, fn.Id) +end +timer.Simple(5, showMOTD) + +concommand.Add("DarkRP_motd", showMOTD) diff --git a/gamemodes/darkrp/gamemode/modules/deathpov/cl_init.lua b/gamemodes/darkrp/gamemode/modules/deathpov/cl_init.lua new file mode 100644 index 0000000..ca7e7f4 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/deathpov/cl_init.lua @@ -0,0 +1,44 @@ +local view = { + origin = Vector(0, 0, 0), + angles = Angle(0, 0, 0), + fov = 90, + znear = 1 +} + +local deathpov = GM.Config.deathpov +hook.Add("CalcView", "rp_deathPOV", function(ply, origin, angles, fov) + -- Entity:Alive() is being slow as hell, we might actually see ourselves from third person for frame or two + if not deathpov or ply:Health() > 0 then return end + + local Ragdoll = ply:GetRagdollEntity() + if not IsValid(Ragdoll) then return end + + local head = Ragdoll:LookupAttachment("eyes") + head = Ragdoll:GetAttachment(head) + if not head or not head.Pos then return end + + if not Ragdoll.BonesRattled then + Ragdoll.BonesRattled = true + + Ragdoll:InvalidateBoneCache() + Ragdoll:SetupBones() + + local matrix + + for bone = 0, (Ragdoll:GetBoneCount() or 1) do + if Ragdoll:GetBoneName(bone):lower():find("head") then + matrix = Ragdoll:GetBoneMatrix(bone) + break + end + end + + if IsValid(matrix) then + matrix:SetScale(Vector(0, 0, 0)) + end + end + + view.origin = head.Pos + head.Ang:Up() * 8 + view.angles = head.Ang + + return view +end) diff --git a/gamemodes/darkrp/gamemode/modules/dermaskin/cl_dermaskin.lua b/gamemodes/darkrp/gamemode/modules/dermaskin/cl_dermaskin.lua new file mode 100644 index 0000000..922aa12 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/dermaskin/cl_dermaskin.lua @@ -0,0 +1,248 @@ +-- Skin for DarkRP gui's +SKIN = {} + +SKIN.PrintName = "DarkRP" +SKIN.Author = "FPtje Falco" +SKIN.DermaVersion = 1 +SKIN.GwenTexture = Material("darkrp/darkrpderma.png") + + +SKIN.colTextEntryText = color_white +SKIN.colTextEntryTextCursor = color_white + +SKIN.colTextEntryTextPlaceholder = Color(200, 200, 200, 200) -- Unofficial but will probably be named this + +SKIN.tex = {} + +SKIN.tex.Selection = GWEN.CreateTextureBorder(384, 32, 31, 31, 4, 4, 4, 4) + +SKIN.tex.Panels = {} +SKIN.tex.Panels.Normal = GWEN.CreateTextureBorder(256, 0, 63, 63, 16, 16, 16, 16) +SKIN.tex.Panels.Bright = GWEN.CreateTextureBorder(256 + 64, 0, 63, 63, 16, 16, 16, 16) +SKIN.tex.Panels.Dark = GWEN.CreateTextureBorder(256, 64, 63, 63, 16, 16, 16, 16) +SKIN.tex.Panels.Highlight = GWEN.CreateTextureBorder(256 + 64, 64, 63, 63, 16, 16, 16, 16) + +SKIN.tex.Button = GWEN.CreateTextureBorder(480, 0, 31, 31, 8, 8, 8, 8) +SKIN.tex.Button_Hovered = GWEN.CreateTextureBorder(480, 32, 31, 31, 8, 8, 8, 8) +SKIN.tex.Button_Dead = GWEN.CreateTextureBorder(480, 64, 31, 31, 8, 8, 8, 8) +SKIN.tex.Button_Down = GWEN.CreateTextureBorder(480, 96, 31, 31, 8, 8, 8, 8) +SKIN.tex.Shadow = GWEN.CreateTextureBorder(448, 0, 31, 31, 8, 8, 8, 8) + +SKIN.tex.Tree = GWEN.CreateTextureBorder(256, 128, 127, 127, 16, 16, 16, 16) +SKIN.tex.Checkbox_Checked = GWEN.CreateTextureNormal(448, 32, 15, 15) +SKIN.tex.Checkbox = GWEN.CreateTextureNormal(464, 32, 15, 15) +SKIN.tex.CheckboxD_Checked = GWEN.CreateTextureNormal(448, 48, 15, 15) +SKIN.tex.CheckboxD = GWEN.CreateTextureNormal(464, 48, 15, 15) +--SKIN.tex.RadioButton_Checked = GWEN.CreateTextureNormal(448, 64, 15, 15) +--SKIN.tex.RadioButton = GWEN.CreateTextureNormal(464, 64, 15, 15) +--SKIN.tex.RadioButtonD_Checked = GWEN.CreateTextureNormal(448, 80, 15, 15) +--SKIN.tex.RadioButtonD = GWEN.CreateTextureNormal(464, 80, 15, 15) +SKIN.tex.TreePlus = GWEN.CreateTextureNormal(448, 96, 15, 15) +SKIN.tex.TreeMinus = GWEN.CreateTextureNormal(464, 96, 15, 15) +--SKIN.tex.Menu_Strip = GWEN.CreateTextureBorder(0, 128, 127, 21, 1, 1, 1, 1) +SKIN.tex.TextBox = GWEN.CreateTextureBorder(0, 150, 127, 21, 4, 4, 4, 4) +SKIN.tex.TextBox_Focus = GWEN.CreateTextureBorder(0, 172, 127, 21, 4, 4, 4, 4) +SKIN.tex.TextBox_Disabled = GWEN.CreateTextureBorder(0, 193, 127, 21, 4, 4, 4, 4) +SKIN.tex.MenuBG_Margin = GWEN.CreateTextureBorder(128, 128, 127, 63, 24, 8, 8, 8) +SKIN.tex.MenuBG = GWEN.CreateTextureBorder(128, 192, 127, 63, 8, 8, 8, 8) +SKIN.tex.MenuBG_Hover = GWEN.CreateTextureBorder(128, 256, 127, 31, 8, 8, 8, 8) +SKIN.tex.MenuBG_Spacer = GWEN.CreateTextureNormal(128, 288, 127, 3) +SKIN.tex.Tab_Control = GWEN.CreateTextureBorder(0, 256, 127, 127, 8, 8, 8, 8) +SKIN.tex.TabB_Active = GWEN.CreateTextureBorder(0, 416, 63, 31, 8, 8, 8, 8) +SKIN.tex.TabB_Inactive = GWEN.CreateTextureBorder(0 + 128, 416, 63, 31, 8, 8, 8, 8) +SKIN.tex.TabT_Active = GWEN.CreateTextureBorder(0, 384, 63, 31, 8, 8, 8, 8) +SKIN.tex.TabT_Inactive = GWEN.CreateTextureBorder(0 + 128, 384, 63, 31, 8, 8, 8, 8) +SKIN.tex.TabL_Active = GWEN.CreateTextureBorder(64, 384, 31, 63, 8, 8, 8, 8) +SKIN.tex.TabL_Inactive = GWEN.CreateTextureBorder(64 + 128, 384, 31, 63, 8, 8, 8, 8) +SKIN.tex.TabR_Active = GWEN.CreateTextureBorder(96, 384, 31, 63, 8, 8, 8, 8) +SKIN.tex.TabR_Inactive = GWEN.CreateTextureBorder(96 + 128, 384, 31, 63, 8, 8, 8, 8) +SKIN.tex.Tab_Bar = GWEN.CreateTextureBorder(128, 352, 127, 31, 4, 4, 4, 4) + +SKIN.tex.Window = {} + +SKIN.tex.Window.Normal = GWEN.CreateTextureBorder(0, 0, 127, 127, 8, 32, 8, 8) +SKIN.tex.Window.Inactive = GWEN.CreateTextureBorder(128, 0, 127, 127, 8, 32, 8, 8) +SKIN.tex.Window.Close = GWEN.CreateTextureNormal(0, 224, 24, 24) +SKIN.tex.Window.Close_Hover = GWEN.CreateTextureNormal(32, 224, 24, 24) +SKIN.tex.Window.Close_Down = GWEN.CreateTextureNormal(64, 224, 24, 24) +SKIN.tex.Window.Close_Disabled = GWEN.CreateTextureNormal(96, 224, 24, 24) + +SKIN.tex.Window.Maxi = GWEN.CreateTextureNormal(32 + 96 * 2, 448, 31, 31) +SKIN.tex.Window.Maxi_Hover = GWEN.CreateTextureNormal(64 + 96 * 2, 448, 31, 31) +SKIN.tex.Window.Maxi_Down = GWEN.CreateTextureNormal(96 + 96 * 2, 448, 31, 31) + +SKIN.tex.Window.Restore = GWEN.CreateTextureNormal(32 + 96 * 2, 448 + 32, 31, 31) +SKIN.tex.Window.Restore_Hover = GWEN.CreateTextureNormal(64 + 96 * 2, 448 + 32, 31, 31) +SKIN.tex.Window.Restore_Down = GWEN.CreateTextureNormal(96 + 96 * 2, 448 + 32, 31, 31) + +SKIN.tex.Window.Mini = GWEN.CreateTextureNormal(32 + 96, 448, 31, 31) +SKIN.tex.Window.Mini_Hover = GWEN.CreateTextureNormal(64 + 96, 448, 31, 31) +SKIN.tex.Window.Mini_Down = GWEN.CreateTextureNormal(96 + 96, 448, 31, 31) + +SKIN.tex.Scroller = {} +SKIN.tex.Scroller.TrackV = GWEN.CreateTextureBorder(384, 208, 15, 127, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonV_Normal = GWEN.CreateTextureBorder(384 + 16, 208, 15, 127, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonV_Hover = GWEN.CreateTextureBorder(384 + 32, 208, 15, 127, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonV_Down = GWEN.CreateTextureBorder(384 + 48, 208, 15, 127, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonV_Disabled = GWEN.CreateTextureBorder(384 + 64, 208, 15, 127, 4, 4, 4, 4) + +SKIN.tex.Scroller.TrackH = GWEN.CreateTextureBorder(384, 128, 127, 15, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonH_Normal = GWEN.CreateTextureBorder(384, 128 + 16, 127, 15, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonH_Hover = GWEN.CreateTextureBorder(384, 128 + 32, 127, 15, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonH_Down = GWEN.CreateTextureBorder(384, 128 + 48, 127, 15, 4, 4, 4, 4) +SKIN.tex.Scroller.ButtonH_Disabled = GWEN.CreateTextureBorder(384, 128 + 64, 127, 15, 4, 4, 4, 4) + +SKIN.tex.Scroller.LeftButton_Normal = GWEN.CreateTextureBorder(464, 208, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.LeftButton_Hover = GWEN.CreateTextureBorder(480, 208, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.LeftButton_Down = GWEN.CreateTextureBorder(464, 272, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.LeftButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272, 15, 15, 2, 2, 2, 2) + +SKIN.tex.Scroller.UpButton_Normal = GWEN.CreateTextureBorder(464, 208 + 16, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.UpButton_Hover = GWEN.CreateTextureBorder(480, 208 + 16, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.UpButton_Down = GWEN.CreateTextureBorder(464, 272 + 16, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.UpButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 16, 15, 15, 2, 2, 2, 2) + +SKIN.tex.Scroller.RightButton_Normal = GWEN.CreateTextureBorder(464, 208 + 32, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.RightButton_Hover = GWEN.CreateTextureBorder(480, 208 + 32, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.RightButton_Down = GWEN.CreateTextureBorder(464, 272 + 32, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.RightButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 32, 15, 15, 2, 2, 2, 2) + +SKIN.tex.Scroller.DownButton_Normal = GWEN.CreateTextureBorder(464, 208 + 48, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.DownButton_Hover = GWEN.CreateTextureBorder(480, 208 + 48, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.DownButton_Down = GWEN.CreateTextureBorder(464, 272 + 48, 15, 15, 2, 2, 2, 2) +SKIN.tex.Scroller.DownButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 48, 15, 15, 2, 2, 2, 2) + +SKIN.tex.Menu = {} +SKIN.tex.Menu.RightArrow = GWEN.CreateTextureNormal(464, 112, 15, 15) + +SKIN.tex.Input = {} +SKIN.tex.Input.ListBox = GWEN.CreateTextureBorder(256, 256, 63, 127, 8, 8, 8, 8) + +SKIN.tex.Input.ComboBox = {} +SKIN.tex.Input.ComboBox.Normal = GWEN.CreateTextureBorder(384, 336, 127, 31, 8, 8, 32, 8) +SKIN.tex.Input.ComboBox.Hover = GWEN.CreateTextureBorder(384, 336 + 32, 127, 31, 8, 8, 32, 8) +SKIN.tex.Input.ComboBox.Down = GWEN.CreateTextureBorder(384, 336 + 64, 127, 31, 8, 8, 32, 8) +SKIN.tex.Input.ComboBox.Disabled = GWEN.CreateTextureBorder(384, 336 + 96, 127, 31, 8, 8, 32, 8) + +SKIN.tex.Input.ComboBox.Button = {} +SKIN.tex.Input.ComboBox.Button.Normal = GWEN.CreateTextureNormal(496, 272, 15, 15) +SKIN.tex.Input.ComboBox.Button.Hover = GWEN.CreateTextureNormal(496, 272 + 16, 15, 15) +SKIN.tex.Input.ComboBox.Button.Down = GWEN.CreateTextureNormal(496, 272 + 32, 15, 15) +SKIN.tex.Input.ComboBox.Button.Disabled = GWEN.CreateTextureNormal(496, 272 + 48, 15, 15) + +SKIN.tex.Input.UpDown = {} +SKIN.tex.Input.UpDown.Up = {} +SKIN.tex.Input.UpDown.Up.Normal = GWEN.CreateTextureCentered(384, 112, 7, 7) +SKIN.tex.Input.UpDown.Up.Hover = GWEN.CreateTextureCentered(384 + 8, 112, 7, 7) +SKIN.tex.Input.UpDown.Up.Down = GWEN.CreateTextureCentered(384 + 16, 112, 7, 7) +SKIN.tex.Input.UpDown.Up.Disabled = GWEN.CreateTextureCentered(384 + 24, 112, 7, 7) + +SKIN.tex.Input.UpDown.Down = {} +SKIN.tex.Input.UpDown.Down.Normal = GWEN.CreateTextureCentered(384, 120, 7, 7) +SKIN.tex.Input.UpDown.Down.Hover = GWEN.CreateTextureCentered(384 + 8, 120, 7, 7) +SKIN.tex.Input.UpDown.Down.Down = GWEN.CreateTextureCentered(384 + 16, 120, 7, 7) +SKIN.tex.Input.UpDown.Down.Disabled = GWEN.CreateTextureCentered(384 + 24, 120, 7, 7) + +SKIN.tex.Input.Slider = {} +SKIN.tex.Input.Slider.H = {} +SKIN.tex.Input.Slider.H.Normal = GWEN.CreateTextureNormal(416, 32, 15, 15) +SKIN.tex.Input.Slider.H.Hover = GWEN.CreateTextureNormal(416, 32 + 16, 15, 15) +SKIN.tex.Input.Slider.H.Down = GWEN.CreateTextureNormal(416, 32 + 32, 15, 15) +SKIN.tex.Input.Slider.H.Disabled = GWEN.CreateTextureNormal(416, 32 + 48, 15, 15) + +SKIN.tex.Input.Slider.V = {} +SKIN.tex.Input.Slider.V.Normal = GWEN.CreateTextureNormal(416 + 16, 32, 15, 15) +SKIN.tex.Input.Slider.V.Hover = GWEN.CreateTextureNormal(416 + 16, 32 + 16, 15, 15) +SKIN.tex.Input.Slider.V.Down = GWEN.CreateTextureNormal(416 + 16, 32 + 32, 15, 15) +SKIN.tex.Input.Slider.V.Disabled = GWEN.CreateTextureNormal(416 + 16, 32 + 48, 15, 15) + +SKIN.tex.Input.ListBox = {} +SKIN.tex.Input.ListBox.Background = GWEN.CreateTextureBorder(256, 256, 63, 127, 8, 8, 8, 8) +SKIN.tex.Input.ListBox.Hovered = GWEN.CreateTextureBorder(320, 320, 31, 31, 8, 8, 8, 8) +SKIN.tex.Input.ListBox.EvenLine = GWEN.CreateTextureBorder(352, 256, 31, 31, 8, 8, 8, 8) +SKIN.tex.Input.ListBox.OddLine = GWEN.CreateTextureBorder(352, 288, 31, 31, 8, 8, 8, 8) +SKIN.tex.Input.ListBox.EvenLineSelected = GWEN.CreateTextureBorder(320, 270, 31, 31, 8, 8, 8, 8) +SKIN.tex.Input.ListBox.OddLineSelected = GWEN.CreateTextureBorder(320, 288, 31, 31, 8, 8, 8, 8) + +SKIN.tex.ProgressBar = {} +SKIN.tex.ProgressBar.Back = GWEN.CreateTextureBorder(384, 0, 31, 31, 8, 8, 8, 8) +SKIN.tex.ProgressBar.Front = GWEN.CreateTextureBorder(384 + 32, 0, 31, 31, 8, 8, 8, 8) + + +SKIN.tex.CategoryList = {} +SKIN.tex.CategoryList.Outer = GWEN.CreateTextureBorder(256, 384, 63, 63, 8, 8, 8, 8) +SKIN.tex.CategoryList.Inner = GWEN.CreateTextureBorder(256 + 64, 384, 63, 63, 8, 21, 8, 8) +SKIN.tex.CategoryList.Header = GWEN.CreateTextureBorder(320, 352, 63, 31, 8, 8, 8, 8) + +SKIN.tex.Tooltip = GWEN.CreateTextureBorder(384, 64, 31, 31, 8, 8, 8, 8) + +SKIN.Colours = {} + +SKIN.Colours.Window = {} +SKIN.Colours.Window.TitleActive = GWEN.TextureColor(4 + 8 * 0, 508) +SKIN.Colours.Window.TitleInactive = GWEN.TextureColor(4 + 8 * 1, 508) + +SKIN.Colours.Button = {} +SKIN.Colours.Button.Normal = GWEN.TextureColor(4 + 8 * 2, 508) +SKIN.Colours.Button.Hover = GWEN.TextureColor(4 + 8 * 3, 508) +SKIN.Colours.Button.Down = GWEN.TextureColor(4 + 8 * 2, 500) +SKIN.Colours.Button.Disabled = GWEN.TextureColor(4 + 8 * 3, 500) + +SKIN.Colours.Tab = {} +SKIN.Colours.Tab.Active = {} +SKIN.Colours.Tab.Active.Normal = GWEN.TextureColor(4 + 8 * 4, 508) +SKIN.Colours.Tab.Active.Hover = GWEN.TextureColor(4 + 8 * 5, 508) +SKIN.Colours.Tab.Active.Down = GWEN.TextureColor(4 + 8 * 4, 500) +SKIN.Colours.Tab.Active.Disabled = GWEN.TextureColor(4 + 8 * 5, 500) + +SKIN.Colours.Tab.Inactive = {} +SKIN.Colours.Tab.Inactive.Normal = GWEN.TextureColor(4 + 8 * 6, 508) +SKIN.Colours.Tab.Inactive.Hover = GWEN.TextureColor(4 + 8 * 7, 508) +SKIN.Colours.Tab.Inactive.Down = GWEN.TextureColor(4 + 8 * 6, 500) +SKIN.Colours.Tab.Inactive.Disabled = GWEN.TextureColor(4 + 8 * 7, 500) + +SKIN.Colours.Label = {} +SKIN.Colours.Label.Default = GWEN.TextureColor(4 + 8 * 8, 508) +SKIN.Colours.Label.Bright = GWEN.TextureColor(4 + 8 * 9, 508) +SKIN.Colours.Label.Dark = GWEN.TextureColor(4 + 8 * 8, 500) +SKIN.Colours.Label.Highlight = GWEN.TextureColor(4 + 8 * 9, 500) + +SKIN.Colours.Tree = {} +SKIN.Colours.Tree.Lines = GWEN.TextureColor(4 + 8 * 10, 508) +---- !!! +SKIN.Colours.Tree.Normal = GWEN.TextureColor(4 + 8 * 11, 508) +SKIN.Colours.Tree.Hover = GWEN.TextureColor(4 + 8 * 10, 500) +SKIN.Colours.Tree.Selected = GWEN.TextureColor(4 + 8 * 11, 500) + +SKIN.Colours.Properties = {} +SKIN.Colours.Properties.Line_Normal = GWEN.TextureColor(4 + 8 * 12, 508) +SKIN.Colours.Properties.Line_Selected = GWEN.TextureColor(4 + 8 * 13, 508) +SKIN.Colours.Properties.Line_Hover = GWEN.TextureColor(4 + 8 * 12, 500) +SKIN.Colours.Properties.Title = GWEN.TextureColor(4 + 8 * 13, 500) +SKIN.Colours.Properties.Column_Normal = GWEN.TextureColor(4 + 8 * 14, 508) +SKIN.Colours.Properties.Column_Selected = GWEN.TextureColor(4 + 8 * 15, 508) +SKIN.Colours.Properties.Column_Hover = GWEN.TextureColor(4 + 8 * 14, 500) +SKIN.Colours.Properties.Border = GWEN.TextureColor(4 + 8 * 15, 500) +SKIN.Colours.Properties.Label_Normal = GWEN.TextureColor(4 + 8 * 16, 508) +SKIN.Colours.Properties.Label_Selected = GWEN.TextureColor(4 + 8 * 17, 508) +SKIN.Colours.Properties.Label_Hover = GWEN.TextureColor(4 + 8 * 16, 500) + +SKIN.Colours.Category = {} +SKIN.Colours.Category.Header = GWEN.TextureColor(4 + 8 * 18, 500) +SKIN.Colours.Category.Header_Closed = GWEN.TextureColor(4 + 8 * 19, 500) +SKIN.Colours.Category.Line = {} +SKIN.Colours.Category.Line.Text = GWEN.TextureColor(4 + 8 * 20, 508) +SKIN.Colours.Category.Line.Text_Hover = GWEN.TextureColor(4 + 8 * 21, 508) +SKIN.Colours.Category.Line.Text_Selected = GWEN.TextureColor(4 + 8 * 20, 500) +SKIN.Colours.Category.Line.Button = GWEN.TextureColor(4 + 8 * 21, 500) +SKIN.Colours.Category.Line.Button_Hover = GWEN.TextureColor(4 + 8 * 22, 508) +SKIN.Colours.Category.Line.Button_Selected = GWEN.TextureColor(4 + 8 * 23, 508) +SKIN.Colours.Category.LineAlt = {} +SKIN.Colours.Category.LineAlt.Text = GWEN.TextureColor(4 + 8 * 22, 500) +SKIN.Colours.Category.LineAlt.Text_Hover = GWEN.TextureColor(4 + 8 * 23, 500) +SKIN.Colours.Category.LineAlt.Text_Selected = GWEN.TextureColor(4 + 8 * 24, 508) +SKIN.Colours.Category.LineAlt.Button = GWEN.TextureColor(4 + 8 * 25, 508) +SKIN.Colours.Category.LineAlt.Button_Hover = GWEN.TextureColor(4 + 8 * 24, 500) +SKIN.Colours.Category.LineAlt.Button_Selected = GWEN.TextureColor(4 + 8 * 25, 500) + +derma.DefineSkin("DarkRP", "The official SKIN for DarkRP", SKIN) diff --git a/gamemodes/darkrp/gamemode/modules/dermaskin/sv_resource.lua b/gamemodes/darkrp/gamemode/modules/dermaskin/sv_resource.lua new file mode 100644 index 0000000..16e0333 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/dermaskin/sv_resource.lua @@ -0,0 +1 @@ +resource.AddFile("materials/darkrp/darkrpderma.png") diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/cl_doors.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/cl_doors.lua new file mode 100644 index 0000000..6dfebcf --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/cl_doors.lua @@ -0,0 +1,165 @@ +local meta = FindMetaTable("Entity") +local black = color_black +local white = Color(255, 255, 255, 200) +local red = Color(128, 30, 30, 255) +local changeDoorAccess = false + +local function updatePrivs() + CAMI.PlayerHasAccess(LocalPlayer(), "DarkRP_ChangeDoorSettings", function(b, _) + changeDoorAccess = b + end) +end +-- Timer due to lack of "on privilege changed" hook +hook.Add("InitPostEntity", "Load door privileges", function() + updatePrivs() + timer.Create("Door changeDoorAccess checker", 1, 0, updatePrivs) +end) + +function meta:drawOwnableInfo() + local ply = LocalPlayer() + if ply:InVehicle() and not ply:GetAllowWeaponsInVehicle() then return end + + -- Look, if you want to change the way door ownership is drawn, don't edit this file, use the hook instead! + local doorDrawing = hook.Call("HUDDrawDoorData", nil, self) + if doorDrawing == true then return end + + local blocked = self:getKeysNonOwnable() + local doorTeams = self:getKeysDoorTeams() + local doorGroup = self:getKeysDoorGroup() + local playerOwned = self:isKeysOwned() or table.GetFirstValue(self:getKeysCoOwners() or {}) ~= nil + local owned = playerOwned or doorGroup or doorTeams + + local doorInfo = {} + + local title = self:getKeysTitle() + if title then table.insert(doorInfo, title) end + + if owned then + table.insert(doorInfo, DarkRP.getPhrase("keys_owned_by")) + end + + if playerOwned then + if self:isKeysOwned() then table.insert(doorInfo, self:getDoorOwner():Nick()) end + for k in pairs(self:getKeysCoOwners() or {}) do + local ent = Player(k) + if not IsValid(ent) or not ent:IsPlayer() then continue end + table.insert(doorInfo, ent:Nick()) + end + + local allowedCoOwn = self:getKeysAllowedToOwn() + if allowedCoOwn and not fn.Null(allowedCoOwn) then + table.insert(doorInfo, DarkRP.getPhrase("keys_other_allowed")) + + for k in pairs(allowedCoOwn) do + local ent = Player(k) + if not IsValid(ent) or not ent:IsPlayer() then continue end + table.insert(doorInfo, ent:Nick()) + end + end + elseif doorGroup then + table.insert(doorInfo, doorGroup) + elseif doorTeams then + for k, v in pairs(doorTeams) do + if not v or not RPExtraTeams[k] then continue end + + table.insert(doorInfo, RPExtraTeams[k].name) + end + elseif blocked and changeDoorAccess then + table.insert(doorInfo, DarkRP.getPhrase("keys_allow_ownership")) + elseif not blocked then + table.insert(doorInfo, DarkRP.getPhrase("keys_unowned")) + if changeDoorAccess then + table.insert(doorInfo, DarkRP.getPhrase("keys_disallow_ownership")) + end + end + + if self:IsVehicle() then + local driver = self:GetDriver() + if driver:IsPlayer() then + table.insert(doorInfo, DarkRP.getPhrase("driver", driver:Nick())) + end + end + + local x, y = ScrW() / 2, ScrH() / 2 + local text = table.concat(doorInfo, "\n") + draw.DrawNonParsedText(text, "Roboto20", x , y + 1 , black, 1) + draw.DrawNonParsedText(text, "Roboto20", x, y, (blocked or owned) and white or red, 1) +end + + +--[[--------------------------------------------------------------------------- +Door data +---------------------------------------------------------------------------]] +DarkRP.doorData = DarkRP.doorData or {} + +--[[--------------------------------------------------------------------------- +Interface functions +---------------------------------------------------------------------------]] +function meta:getDoorData() + local doorData = DarkRP.doorData[self:EntIndex()] or {} + + self.DoorData = doorData -- Backwards compatibility + + return doorData +end + +--[[--------------------------------------------------------------------------- +Networking +---------------------------------------------------------------------------]] + +--[[--------------------------------------------------------------------------- +Retrieve all the data for all doors +---------------------------------------------------------------------------]] +local function retrieveAllDoorData(len) + local count = net.ReadUInt(16) + + for i = 1, count do + local ix = net.ReadUInt(16) + local varCount = net.ReadUInt(8) + + DarkRP.doorData[ix] = DarkRP.doorData[ix] or {} + + for vc = 1, varCount do + local name, value = DarkRP.readNetDoorVar() + DarkRP.doorData[ix][name] = value + end + end +end +net.Receive("DarkRP_AllDoorData", retrieveAllDoorData) + +--[[--------------------------------------------------------------------------- +Update changed variables +---------------------------------------------------------------------------]] +local function updateDoorData() + local door = net.ReadUInt(32) + + DarkRP.doorData[door] = DarkRP.doorData[door] or {} + + local var, value = DarkRP.readNetDoorVar() + + DarkRP.doorData[door][var] = value +end +net.Receive("DarkRP_UpdateDoorData", updateDoorData) + +--[[--------------------------------------------------------------------------- +Set a value of a single doorvar to nil +---------------------------------------------------------------------------]] +local function removeDoorVar() + local door = net.ReadUInt(16) + local id = net.ReadUInt(8) + + local name = id == 0 and net.ReadString() or DarkRP.getDoorVars()[id].name + + if not DarkRP.doorData[door] then return end + DarkRP.doorData[door][name] = nil +end +net.Receive("DarkRP_RemoveDoorVar", removeDoorVar) + +--[[--------------------------------------------------------------------------- +Remove doordata of removed entity +---------------------------------------------------------------------------]] +local function removeDoorData() + local door = net.ReadUInt(32) + DarkRP.doorData[door] = nil +end +net.Receive("DarkRP_RemoveDoorData", removeDoorData) diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/cl_interface.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/cl_interface.lua new file mode 100644 index 0000000..1c81f8e --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/cl_interface.lua @@ -0,0 +1,48 @@ +DarkRP.readNetDoorVar = DarkRP.stub{ + name = "readNetDoorVar", + description = "Internal function. You probably shouldn't need this. DarkRP calls this function when reading DoorVar net messages. This function reads the net data for a specific DoorVar.", + parameters = { + }, + returns = { + { + name = "name", + description = "The name of the DoorVar.", + type = "string" + }, + { + name = "value", + description = "The value of the DoorVar.", + type = "any" + } + }, + metatable = DarkRP +} + +DarkRP.ENTITY.drawOwnableInfo = DarkRP.stub{ + name = "drawOwnableInfo", + description = "Draw the ownability information on a door or vehicle.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.hookStub{ + name = "HUDDrawDoorData", + description = "Called when DarkRP is about to draw the door ownability information of a door or vehicle. Override this hook to ", + parameters = { + { + name = "ent", + description = "The door or vehicle of which the ownability information is about to be drawn.", + type = "Entity" + } + }, + returns = { + { + name = "override", + description = "Return true in your hook to disable the default drawing and use your own.", + type = "boolean" + } + } +} diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/sh_doors.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/sh_doors.lua new file mode 100644 index 0000000..3965157 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/sh_doors.lua @@ -0,0 +1,323 @@ +local meta = FindMetaTable("Entity") +local plyMeta = FindMetaTable("Player") + +local ownableDoors = { + ["func_door"] = true, + ["func_door_rotating"] = true, + ["prop_door_rotating"] = true +} +local unOwnableDoors = { + ["func_door"] = true, + ["func_door_rotating"] = true, + ["prop_door_rotating"] = true, + ["func_movelinear"] = true, + ["prop_dynamic"] = true +} +function meta:isKeysOwnable() + if not IsValid(self) then return false end + + local class = self:GetClass() + + if (ownableDoors[class] or + (GAMEMODE.Config.allowvehicleowning and self:IsVehicle() and (not IsValid(self:GetParent()) or not self:GetParent():IsVehicle()))) then + return true + end + + return false +end + +function meta:isDoor() + local class = self:GetClass() + + if unOwnableDoors[class] then + return true + end + + return false +end + +function meta:isKeysOwned() + if IsValid(self:getDoorOwner()) then return true end + + return false +end + +function meta:getDoorOwner() + local doorData = self:getDoorData() + if not doorData then return nil end + + return doorData.owner and Player(doorData.owner) or nil +end + +function meta:isMasterOwner(ply) + return ply == self:getDoorOwner() +end + +function meta:isKeysOwnedBy(ply) + if self:isMasterOwner(ply) then return true end + + local coOwners = self:getKeysCoOwners() + return coOwners and coOwners[ply:UserID()] or false +end + +function meta:isKeysAllowedToOwn(ply) + local doorData = self:getDoorData() + if not doorData then return false end + + return doorData.allowedToOwn and doorData.allowedToOwn[ply:UserID()] or false +end + +function meta:getKeysNonOwnable() + local doorData = self:getDoorData() + if not doorData then return nil end + + return doorData.nonOwnable +end + +function meta:getKeysTitle() + local doorData = self:getDoorData() + if not doorData then return nil end + + return doorData.title +end + +function meta:getKeysDoorGroup() + local doorData = self:getDoorData() + if not doorData then return nil end + + return doorData.groupOwn +end + +function meta:getKeysDoorTeams() + local doorData = self:getDoorData() + if not doorData or table.IsEmpty(doorData.teamOwn or {}) then return nil end + + return doorData.teamOwn +end + +function meta:getKeysAllowedToOwn() + local doorData = self:getDoorData() + if not doorData then return nil end + + return doorData.allowedToOwn +end + +function meta:getKeysCoOwners() + local doorData = self:getDoorData() + if not doorData then return nil end + + return doorData.extraOwners +end + +local function canLockUnlock(ply, ent) + local Team = ply:Team() + local group = ent:getKeysDoorGroup() + local teamOwn = ent:getKeysDoorTeams() + + return ent:isKeysOwnedBy(ply) or + (group and table.HasValue(RPExtraTeamDoors[group] or {}, Team)) or + (teamOwn and teamOwn[Team]) +end + +function plyMeta:canKeysLock(ent) + local canLock = hook.Run("canKeysLock", self, ent) + + if canLock ~= nil then return canLock end + return canLockUnlock(self, ent) +end + +function plyMeta:canKeysUnlock(ent) + local canUnlock = hook.Run("canKeysUnlock", self, ent) + + if canUnlock ~= nil then return canUnlock end + return canLockUnlock(self, ent) +end + +local netDoorVars = {} +local netDoorVarsByName = {} + +DarkRP.getDoorVars = fp{fn.Id, netDoorVars} +DarkRP.getDoorVarsByName = fp{fn.Id, netDoorVarsByName} + +function DarkRP.registerDoorVar(name, writeFn, readFn) + netDoorVarsByName[name] = {name = name, write = writeFn, read = readFn} + + netDoorVarsByName[name].id = table.insert(netDoorVars, netDoorVarsByName[name]) +end + +if SERVER then + function DarkRP.writeNetDoorVar(name, value) + local var = netDoorVarsByName[name] + + -- Not registered, send inefficiently + if not var then + net.WriteUInt(0, 8) -- indicate unregistered + net.WriteString(name) + net.WriteType(value) + + return + end + + net.WriteUInt(var.id, 8) + var.write(value) + end +end + +if CLIENT then + function DarkRP.readNetDoorVar() + local id = net.ReadUInt(8) + + -- unregistered var + if id == 0 then + return net.ReadString(), net.ReadType(net.ReadUInt(8)) + end + + if not netDoorVars[id] then + DarkRP.error("Unregistered DarkRP Doorvar clientside: " .. id, 2, {"Some addon is registering some DoorVar serverside, but not clientside."}) + end + + return netDoorVars[id].name, netDoorVars[id].read() + end +end + +DarkRP.registerDoorVar("groupOwn", + function(val) + net.WriteUInt(RPExtraTeamDoorIDs[val], 16) + end, + function() + local id = net.ReadUInt(16) + for name, id2 in pairs(RPExtraTeamDoorIDs) do + if id == id2 then return name end + end + end +) + +-- Net helper function for writing tables with numbers as keys and bools as values +local function writeNumBoolTbl(tbl) + net.WriteUInt(table.Count(tbl), 10) + + for num, _ in pairs(tbl) do + net.WriteUInt(num, 16) + end +end + +-- Net helper function for reading tables with numbers as keys and bools as values +local function readNumBoolTbl(tbl) + local res = {} + local count = net.ReadUInt(10) + + for i = 1, count do + res[net.ReadUInt(16)] = true + end + + return res +end + +DarkRP.registerDoorVar("owner", fp{fn.Flip(net.WriteInt), 16}, fp{net.ReadUInt, 16}) +DarkRP.registerDoorVar("nonOwnable", net.WriteBool, net.ReadBool) +DarkRP.registerDoorVar("teamOwn", writeNumBoolTbl, readNumBoolTbl) +DarkRP.registerDoorVar("allowedToOwn", writeNumBoolTbl, readNumBoolTbl) +DarkRP.registerDoorVar("extraOwners", writeNumBoolTbl, readNumBoolTbl) +DarkRP.registerDoorVar("title", net.WriteString, net.ReadString) + +--[[--------------------------------------------------------------------------- +Commands +---------------------------------------------------------------------------]] +DarkRP.declareChatCommand{ + command = "toggleownable", + description = "Toggle ownability status on this door.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "togglegroupownable", + description = "Set this door group ownable.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "toggleteamownable", + description = "Toggle this door ownable by a given team.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "toggleown", + description = "Own or unown the door you're looking at.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "unownalldoors", + description = "Sell all of your doors.", + delay = 1.5 +} + +DarkRP.chatCommandAlias("unownalldoors", "sellalldoors") + +DarkRP.declareChatCommand{ + command = "title", + description = "Set the title of the door you're looking at.", + delay = 1.5 +} + +DarkRP.declareChatCommand{ + command = "removeowner", + description = "Remove an owner from the door you're looking at.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "ro", + description = "Remove an owner from the door you're looking at.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "addowner", + description = "Invite someone to co-own the door you're looking at.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "ao", + description = "Invite someone to co-own the door you're looking at.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "forceunlock", + description = "Force the door you're looking at to be unlocked. This is saved.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "forceremoveowner", + description = "Forcefully remove an owner from the door you're looking at.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "forceunownall", + description = "Force a player to unown all the doors and vehicles they have.", + delay = 0.5, + tableArgs = true +} + +DarkRP.declareChatCommand{ + command = "forcelock", + description = "Force the door you're looking at to be locked. This is saved.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "forceunown", + description = "Forcefully remove any owners from the door you're looking at.", + delay = 0.5 +} + +DarkRP.declareChatCommand{ + command = "forceown", + description = "Forcefully make someone own the door you're looking at.", + delay = 0.5 +} diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/sh_interface.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/sh_interface.lua new file mode 100644 index 0000000..b0b5453 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/sh_interface.lua @@ -0,0 +1,377 @@ +DarkRP.ENTITY.getDoorData = DarkRP.stub{ + name = "getDoorData", + description = "Internal function to get the door/vehicle data.", + parameters = { + }, + returns = { + { + name = "doordata", + description = "All the DarkRP information on a door or vehicle.", + type = "table" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.isKeysOwnable = DarkRP.stub{ + name = "isKeysOwnable", + description = "Whether this door can be bought.", + parameters = { + }, + returns = { + { + name = "answer", + description = "Whether the door can be bought.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.isDoor = DarkRP.stub{ + name = "isDoor", + description = "Whether this entity is considered a door in DarkRP.", + parameters = { + }, + returns = { + { + name = "answer", + description = "Whether it's a door.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.isKeysOwned = DarkRP.stub{ + name = "isKeysOwned", + description = "Whether this door is owned by someone.", + parameters = { + }, + returns = { + { + name = "answer", + description = "Whether it's owned.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.getDoorOwner = DarkRP.stub{ + name = "getDoorOwner", + description = "Get the owner of a door.", + parameters = { + }, + returns = { + { + name = "owner", + description = "The owner of the door.", + type = "Player" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.isMasterOwner = DarkRP.stub{ + name = "isMasterOwner", + description = "Whether the player is the main owner of the door (as opposed to a co-owner).", + parameters = { + { + name = "ply", + description = "The player to query.", + type = "Player", + optional = false + } + }, + returns = { + { + name = "answer", + description = "Whether this player is the master owner.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.isKeysOwnedBy = DarkRP.stub{ + name = "isKeysOwnedBy", + description = "Whether this door is owned or co-owned by this player", + parameters = { + { + name = "ply", + description = "The player to query.", + type = "Player", + optional = false + } + }, + returns = { + { + name = "answer", + description = "Whether this door is (co-)owned by the player.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.isKeysAllowedToOwn = DarkRP.stub{ + name = "isKeysAllowedToOwn", + description = "Whether this player is allowed to co-own a door, as decided by the master door owner.", + parameters = { + { + name = "ply", + description = "The player to query.", + type = "Player", + optional = false + } + }, + returns = { + { + name = "answer", + description = "Whether this door is (co-)ownable by the player.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.getKeysNonOwnable = DarkRP.stub{ + name = "getKeysNonOwnable", + description = "Whether ownability of this door/vehicle is disabled.", + parameters = { + }, + returns = { + { + name = "title", + description = "The ownability status.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.getKeysTitle = DarkRP.stub{ + name = "getKeysTitle", + description = "Get the title of this door or vehicle.", + parameters = { + }, + returns = { + { + name = "title", + description = "The title of the door or vehicle.", + type = "string" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.getKeysDoorGroup = DarkRP.stub{ + name = "getKeysDoorGroup", + description = "The door group of a door if it exists.", + parameters = { + }, + returns = { + { + name = "group", + description = "The door group.", + type = "string" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.getKeysDoorTeams = DarkRP.stub{ + name = "getKeysDoorTeams", + description = "The teams that are allowed to open this door.", + parameters = { + }, + returns = { + { + name = "teams", + description = "The door teams.", + type = "table" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.getKeysAllowedToOwn = DarkRP.stub{ + name = "getKeysAllowedToOwn", + description = "The list of people of which the master door owner has added as allowed to own.", + parameters = { + }, + returns = { + { + name = "players", + description = "The list of people allowed to own.", + type = "table" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.getKeysCoOwners = DarkRP.stub{ + name = "getKeysCoOwners", + description = "The list of people who co-own the door.", + parameters = { + }, + returns = { + { + name = "players", + description = "The list of people allowed to own. The keys of this table are UserIDs, the values are booleans.", + type = "table" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.PLAYER.canKeysLock = DarkRP.stub{ + name = "canKeysLock", + description = "Whether the player can lock a given door.", + parameters = { + { + name = "door", + description = "The door", + optional = false, + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to lock the door.", + type = "boolean" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.canKeysUnlock = DarkRP.stub{ + name = "canKeysUnlock", + description = "Whether the player can unlock a given door.", + parameters = { + { + name = "door", + description = "The door", + optional = false, + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to unlock the door.", + type = "boolean" + } + }, + metatable = DarkRP.PLAYER +} + +DarkRP.registerDoorVar = DarkRP.stub{ + name = "registerDoorVar", + description = "Register a door variable by name. You should definitely register door variables. Registering DarkRPVars will make networking much more efficient.", + parameters = { + { + name = "name", + description = "The name of the door var.", + type = "string", + optional = false + }, + { + name = "writeFn", + description = "The function that writes a value for this door var. Examples: net.WriteString, function(val) net.WriteUInt(val, 8) end.", + type = "function", + optional = false + }, + { + name = "readFn", + description = "The function that reads and returns a value for this door var. Examples: net.ReadString, function() return net.ReadUInt(8) end.", + type = "function", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.getDoorVars = DarkRP.stub{ + name = "getDoorVars", + description = "Internal function, retrieves all the registered door variables.", + parameters = { + + }, + returns = { + { + name = "doorvars", + description = "The door variables, indexed by number", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.getDoorVarsByName = DarkRP.stub{ + name = "getDoorVarsByName", + description = "Internal function, retrieves all the registered door variables, indeded by their names.", + parameters = { + + }, + returns = { + { + name = "doorvars", + description = "The door variables, indexed by name", + type = "table" + } + }, + metatable = DarkRP +} + +DarkRP.hookStub{ + name = "canKeysLock", + description = "Whether the player can lock a given door. This hook is run when ply:canKeysLock is called.", + parameters = { + { + name = "ply", + description = "The player", + type = "Player" + }, + { + name = "door", + description = "The door", + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to lock the door.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "canKeysUnlock", + description = "Whether the player can unlock a given door. This hook is run when ply:canKeysUnlock is called.", + parameters = { + { + name = "ply", + description = "The player", + type = "Player" + }, + { + name = "door", + description = "The door", + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to unlock the door.", + type = "boolean" + } + } +} diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/sv_dooradministration.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_dooradministration.lua new file mode 100644 index 0000000..21c79d3 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_dooradministration.lua @@ -0,0 +1,172 @@ +local function ccDoorUnOwn(ply, args) + if ply:EntIndex() == 0 then + print(DarkRP.getPhrase("cmd_cant_be_run_server_console")) + return + end + + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or not ent:isKeysOwnable() or not ent:getDoorOwner() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return + end + + ent:Fire("unlock", "", 0) + ent:keysUnOwn() + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unowned a door with forceunown", Color(30, 30, 30)) + DarkRP.notify(ply, 0, 4, "Forcefully unowned") +end +DarkRP.definePrivilegedChatCommand("forceunown", "DarkRP_SetDoorOwner", ccDoorUnOwn) + +local function unownAll(ply, args) + local target = DarkRP.findPlayer(args[1]) + + if not IsValid(target) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args)) + return + end + target:keysUnOwnAll() + + if ply:EntIndex() == 0 then + DarkRP.log("Console force-unowned all doors owned by " .. target:Nick(), Color(30, 30, 30)) + else + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unowned all doors owned by " .. target:Nick(), Color(30, 30, 30)) + end + + DarkRP.notify(ply, 0, 4, "All doors of " .. target:Nick() .. " are now unowned") +end +DarkRP.definePrivilegedChatCommand("forceunownall", "DarkRP_SetDoorOwner", unownAll) + +local function ccAddOwner(ply, args) + if ply:EntIndex() == 0 then + print(DarkRP.getPhrase("cmd_cant_be_run_server_console")) + return + end + + local trace = ply:GetEyeTrace() + + local ent = trace.Entity + if not IsValid(ent) or not ent:isKeysOwnable() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or ent:getKeysDoorTeams() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return + end + + local target = DarkRP.findPlayer(args) + + if not target then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args)) + return + end + + if ent:isKeysOwned() then + if not ent:isKeysOwnedBy(target) and not ent:isKeysAllowedToOwn(target) then + ent:addKeysAllowedToOwn(target) + else + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("rp_addowner_already_owns_door", target)) + end + return + end + ent:keysOwn(target) + + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-added a door owner with forceown", Color(30, 30, 30)) + DarkRP.notify(ply, 0, 4, "Forcefully added " .. target:Nick()) +end +DarkRP.definePrivilegedChatCommand("forceown", "DarkRP_SetDoorOwner", ccAddOwner) + +local function ccRemoveOwner(ply, args) + if ply:EntIndex() == 0 then + print(DarkRP.getPhrase("cmd_cant_be_run_server_console")) + return + end + + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or not ent:isKeysOwnable() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or ent:getKeysDoorTeams() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return + end + + local target = DarkRP.findPlayer(args) + + if not target then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args)) + return + end + + if ent:isKeysAllowedToOwn(target) then + ent:removeKeysAllowedToOwn(target) + end + + if ent:isMasterOwner(target) then + ent:keysUnOwn() + elseif ent:isKeysOwnedBy(target) then + ent:removeKeysDoorOwner(target) + end + + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-removed a door owner with forceremoveowner", Color(30, 30, 30)) + DarkRP.notify(ply, 0, 4, "Forcefully removed " .. target:Nick()) +end +DarkRP.definePrivilegedChatCommand("forceremoveowner", "DarkRP_SetDoorOwner", ccRemoveOwner) + +local function ccLock(ply, args) + if ply:EntIndex() == 0 then + print(DarkRP.getPhrase("cmd_cant_be_run_server_console")) + return + end + + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or not ent:isKeysOwnable() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return + end + + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("locked")) + + ent:keysLock() + + if not ent:CreatedByMap() then return end + MySQLite.query(string.format([[REPLACE INTO darkrp_door VALUES(%s, %s, %s, 1, %s);]], + MySQLite.SQLStr(ent:doorIndex()), + MySQLite.SQLStr(string.lower(game.GetMap())), + MySQLite.SQLStr(ent:getKeysTitle() or ""), + ent:getKeysNonOwnable() and 1 or 0 + )) + + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-locked a door with forcelock (locked door is saved)", Color(30, 30, 30)) + DarkRP.notify(ply, 0, 4, "Forcefully locked") +end +DarkRP.definePrivilegedChatCommand("forcelock", "DarkRP_ChangeDoorSettings", ccLock) + +local function ccUnLock(ply, args) + if ply:EntIndex() == 0 then + print(DarkRP.getPhrase("cmd_cant_be_run_server_console")) + return + end + + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or not ent:isKeysOwnable() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return + end + + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("unlocked")) + ent:keysUnLock() + + if not ent:CreatedByMap() then return end + MySQLite.query(string.format([[REPLACE INTO darkrp_door VALUES(%s, %s, %s, 0, %s);]], + MySQLite.SQLStr(ent:doorIndex()), + MySQLite.SQLStr(string.lower(game.GetMap())), + MySQLite.SQLStr(ent:getKeysTitle() or ""), + ent:getKeysNonOwnable() and 1 or 0 + )) + + DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unlocked a door with forcelock (unlocked door is saved)", Color(30, 30, 30)) + DarkRP.notify(ply, 0, 4, "Forcefully unlocked") +end +DarkRP.definePrivilegedChatCommand("forceunlock", "DarkRP_ChangeDoorSettings", ccUnLock) diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/sv_doors.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_doors.lua new file mode 100644 index 0000000..ab6f819 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_doors.lua @@ -0,0 +1,592 @@ +local meta = FindMetaTable("Entity") +local pmeta = FindMetaTable("Player") + +--[[--------------------------------------------------------------------------- +Functions +---------------------------------------------------------------------------]] + +function meta:doorIndex() + return self:CreatedByMap() and self:MapCreationID() or nil +end + +function DarkRP.doorToEntIndex(num) + local ent = ents.GetMapCreatedEntity(num) + + return IsValid(ent) and ent:EntIndex() or nil +end + +function DarkRP.doorIndexToEnt(num) + return ents.GetMapCreatedEntity(num) or NULL +end + +function meta:isLocked() + local save = self:GetSaveTable() + return save and ((self:isDoor() and save.m_bLocked) or (self:IsVehicle() and save.VehicleLocked)) +end + +function meta:keysLock() + self:Fire("lock", "", 0) + if isfunction(self.Lock) then self:Lock(true) end -- SCars + if IsValid(self.EntOwner) and self.EntOwner ~= self then return self.EntOwner:keysLock() end -- SCars + + hook.Call("onKeysLocked", nil, self) +end + +function meta:keysUnLock() + self:Fire("unlock", "", 0) + if isfunction(self.UnLock) then self:UnLock(true) end -- SCars + if IsValid(self.EntOwner) and self.EntOwner ~= self then return self.EntOwner:keysUnLock() end -- SCars + + hook.Call("onKeysUnlocked", nil, self) +end + +function meta:keysOwn(ply) + if self:isKeysAllowedToOwn(ply) then + self:addKeysDoorOwner(ply) + return + end + + local Owner = self:CPPIGetOwner() + + -- Increase vehicle count + if self:IsVehicle() then + if IsValid(ply) then + ply.Vehicles = ply.Vehicles or 0 + ply.Vehicles = ply.Vehicles + 1 + + self.SID = ply.SID + end + + -- Decrease vehicle count of the original owner + if IsValid(Owner) and Owner ~= ply then + Owner.Vehicles = Owner.Vehicles or 1 + Owner.Vehicles = Owner.Vehicles - 1 + end + end + + if self:IsVehicle() then + self:CPPISetOwner(ply) + end + + if not self:isKeysOwned() and not self:isKeysOwnedBy(ply) then + local doorData = self:getDoorData() + doorData.owner = ply:UserID() + DarkRP.updateDoorData(self, "owner") + end + + ply.OwnedNumz = ply.OwnedNumz or 0 + if ply.OwnedNumz == 0 and GAMEMODE.Config.propertytax then + timer.Create(ply:SteamID64() .. "propertytax", 270, 0, function() ply.doPropertyTax(ply) end) + end + + ply.OwnedNumz = ply.OwnedNumz + 1 + + ply.Ownedz[self:EntIndex()] = self +end + +function meta:keysUnOwn(ply) + if not ply then + ply = self:getDoorOwner() + + if not IsValid(ply) then return end + end + + if self:isMasterOwner(ply) then + local doorData = self:getDoorData() + self:removeAllKeysExtraOwners() + self:setKeysTitle(nil) + doorData.owner = nil + DarkRP.updateDoorData(self, "owner") + else + self:removeKeysDoorOwner(ply) + end + + ply.Ownedz[self:EntIndex()] = nil + ply.OwnedNumz = math.Clamp((ply.OwnedNumz or 1) - 1, 0, math.huge) +end + +function pmeta:keysUnOwnAll() + for entIndex, ent in pairs(self.Ownedz or {}) do + if not IsValid(ent) or not ent:isKeysOwnable() then self.Ownedz[entIndex] = nil continue end + if ent:isMasterOwner(self) then + ent:Fire("unlock", "", 0.6) + end + ent:keysUnOwn(self) + end + + for _, ply in ipairs(player.GetAll()) do + if ply == self then continue end + + for _, ent in pairs(ply.Ownedz or {}) do + if IsValid(ent) and ent:isKeysAllowedToOwn(self) then + ent:removeKeysAllowedToOwn(self) + end + end + end + + self.OwnedNumz = 0 +end + +local function taxesUnOwnAll(ply, taxables) + for _, ent in ipairs(taxables) do + if ent:isMasterOwner(ply) then + ent:Fire("unlock", "", 0.6) + end + + ent:keysUnOwn(ply) + end +end + +function pmeta:doPropertyTax() + if not GAMEMODE.Config.propertytax then return end + if self:isCP() and GAMEMODE.Config.cit_propertytax then return end + + local taxables = {} + + for entIndex, ent in pairs(self.Ownedz or {}) do + if not IsValid(ent) or not ent:isKeysOwnable() then self.Ownedz[entIndex] = nil continue end + local isAllowed = hook.Call("canTaxEntity", nil, self, ent) + if isAllowed == false then continue end + + table.insert(taxables, ent) + end + + -- co-owned doors + for _, ply in ipairs(player.GetAll()) do + if ply == self then continue end + + for _, ent in pairs(ply.Ownedz or {}) do + if not IsValid(ent) or not ent:isKeysOwnedBy(self) then continue end + + local isAllowed = hook.Call("canTaxEntity", nil, self, ent) + if isAllowed == false then continue end + + table.insert(taxables, ent) + end + end + + local numowned = #taxables + + if numowned <= 0 then return end + + local price = 10 + local tax = price * numowned + math.random(-5, 5) + + local shouldTax, taxOverride = hook.Call("canPropertyTax", nil, self, tax) + + if shouldTax == false then return end + + tax = taxOverride or tax + if tax == 0 then return end + + local canAfford = self:canAfford(tax) + + if canAfford then + self:addMoney(-tax) + DarkRP.notify(self, 0, 5, DarkRP.getPhrase("property_tax", DarkRP.formatMoney(tax))) + else + taxesUnOwnAll(self, taxables) + DarkRP.notify(self, 1, 8, DarkRP.getPhrase("property_tax_cant_afford")) + end + + hook.Call("onPropertyTax", nil, self, tax, canAfford) +end + +function pmeta:initiateTax() + local taxtime = GAMEMODE.Config.wallettaxtime + local uid = self:SteamID64() -- so we can destroy the timer if the player leaves + timer.Create("rp_tax_" .. uid, taxtime or 600, 0, function() + if not IsValid(self) then + timer.Remove("rp_tax_" .. uid) + + return + end + + if not GAMEMODE.Config.wallettax then + return -- Don't remove the hook in case it's turned on afterwards. + end + + local money = self:getDarkRPVar("money") + local mintax = GAMEMODE.Config.wallettaxmin / 100 + local maxtax = GAMEMODE.Config.wallettaxmax / 100 -- convert to decimals for percentage calculations + local startMoney = GAMEMODE.Config.startingmoney + + + -- Variate the taxes between twice the starting money ($1000 by default) and 200 - 2 times the starting money (100.000 by default) + local tax = (money - (startMoney * 2)) / (startMoney * 198) + tax = math.Min(maxtax, mintax + (maxtax - mintax) * tax) + + local taxAmount = tax * money + + local shouldTax, amount = hook.Call("canTax", GAMEMODE, self, taxAmount) + + if shouldTax == false then return end + + taxAmount = amount or taxAmount + taxAmount = math.Max(0, taxAmount) + + self:addMoney(-taxAmount) + DarkRP.notify(self, 3, 7, DarkRP.getPhrase("taxday", math.Round(taxAmount / money * 100, 3))) + + hook.Call("onPaidTax", DarkRP.hooks, self, tax, money) + end) +end + +function GM:canTax(ply) + -- Don't tax players if they have less than twice the starting amount + if ply:getDarkRPVar("money") < (GAMEMODE.Config.startingmoney * 2) then return false end +end + +--[[--------------------------------------------------------------------------- +Commands +---------------------------------------------------------------------------]] +local function SetDoorOwnable(ply) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return + end + + if IsValid(ent:getDoorOwner()) then + ent:keysUnOwn(ent:getDoorOwner()) + end + ent:setKeysNonOwnable(not ent:getKeysNonOwnable()) + ent:removeAllKeysExtraOwners() + ent:removeAllKeysAllowedToOwn() + ent:removeAllKeysDoorTeams() + ent:setDoorGroup(nil) + ent:setKeysTitle(nil) + + -- Save it for future map loads + DarkRP.storeDoorData(ent) + DarkRP.storeDoorGroup(ent, nil) + DarkRP.storeTeamDoorOwnability(ent) + + return "" +end +DarkRP.definePrivilegedChatCommand("toggleownable", "DarkRP_ChangeDoorSettings", SetDoorOwnable) + +local function SetDoorGroupOwnable(ply, arg) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return + end + + if not RPExtraTeamDoors[arg] and arg ~= "" then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("door_group_doesnt_exist")) return "" end + + ent:keysUnOwn() + + + ent:removeAllKeysDoorTeams() + local group = arg ~= "" and arg or nil + ent:setDoorGroup(group) + + -- Save it for future map loads + DarkRP.storeDoorGroup(ent, group) + DarkRP.storeTeamDoorOwnability(ent) + + + DarkRP.notify(ply, 0, 8, DarkRP.getPhrase("door_group_set")) + return "" +end +DarkRP.definePrivilegedChatCommand("togglegroupownable", "DarkRP_ChangeDoorSettings", SetDoorGroupOwnable) + +local function SetDoorTeamOwnable(ply, arg) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return "" + end + + arg = tonumber(arg) + if not arg then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("job_doesnt_exist")) return "" end + if not RPExtraTeams[arg] and arg ~= nil then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("job_doesnt_exist")) return "" end + if IsValid(ent:getDoorOwner()) then + ent:keysUnOwn(ent:getDoorOwner()) + end + + ent:setDoorGroup(nil) + DarkRP.storeDoorGroup(ent, nil) + + local doorTeams = ent:getKeysDoorTeams() + if not doorTeams or not doorTeams[arg] then + ent:addKeysDoorTeam(arg) + else + ent:removeKeysDoorTeam(arg) + end + + DarkRP.notify(ply, 0, 8, DarkRP.getPhrase("door_group_set")) + DarkRP.storeTeamDoorOwnability(ent) + + ent:keysUnOwn() + return "" +end +DarkRP.definePrivilegedChatCommand("toggleteamownable", "DarkRP_ChangeDoorSettings", SetDoorTeamOwnable) + +local function OwnDoor(ply) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 40000 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return "" + end + + local Owner = ent:CPPIGetOwner() + + if ply:isArrested() then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("door_unown_arrested")) + return "" + end + + if ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or not fn.Null(ent:getKeysDoorTeams() or {}) then + DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("door_unownable")) + return "" + end + + if ent:isKeysOwnedBy(ply) then + local bAllowed, strReason = hook.Call("playerSell" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent) + + if bAllowed == false then + if strReason and strReason ~= "" then + DarkRP.notify(ply, 1, 4, strReason) + end + + return "" + end + + if ent:isMasterOwner(ply) then + ent:removeAllKeysExtraOwners() + ent:removeAllKeysAllowedToOwn() + ent:Fire("unlock", "", 0) + end + + ent:keysUnOwn(ply) + ent:setKeysTitle(nil) + local GiveMoneyBack = math.floor((hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent) * 0.666) + 0.5) + hook.Call("playerKeysSold", GAMEMODE, ply, ent, GiveMoneyBack) + ply:addMoney(GiveMoneyBack) + local bSuppress = hook.Call("hideSellDoorMessage", GAMEMODE, ply, ent) + if not bSuppress then + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("door_sold", DarkRP.formatMoney(GiveMoneyBack))) + end + + else + if ent:isKeysOwned() and not ent:isKeysAllowedToOwn(ply) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_already_owned")) + return "" + end + + local iCost = hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent) + if not ply:canAfford(iCost) then + DarkRP.notify(ply, 1, 4, ent:IsVehicle() and DarkRP.getPhrase("vehicle_cannot_afford") or DarkRP.getPhrase("door_cannot_afford")) + return "" + end + + local bAllowed, strReason, bSuppress = hook.Call("playerBuy" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent) + if bAllowed == false then + if strReason and strReason ~= "" then + DarkRP.notify(ply, 1, 4, strReason) + end + + return "" + end + + local bVehicle = ent:IsVehicle() + + if bVehicle and (ply.Vehicles or 0) >= GAMEMODE.Config.maxvehicles and Owner ~= ply then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", DarkRP.getPhrase("vehicle"))) + return "" + end + + if not bVehicle and (ply.OwnedNumz or 0) >= GAMEMODE.Config.maxdoors then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", DarkRP.getPhrase("door"))) + return "" + end + + ply:addMoney(-iCost) + if not bSuppress then + DarkRP.notify(ply, 0, 4, bVehicle and DarkRP.getPhrase("vehicle_bought", DarkRP.formatMoney(iCost), "") or DarkRP.getPhrase("door_bought", DarkRP.formatMoney(iCost), "")) + end + + ent:keysOwn(ply) + hook.Call("playerBought" .. (bVehicle and "Vehicle" or "Door"), GAMEMODE, ply, ent, iCost) + end + + return "" +end +DarkRP.defineChatCommand("toggleown", OwnDoor) + +local function UnOwnAll(ply, cmd, args) + local amount = 0 + local cost = 0 + + local unownables = {} + for entIndex, ent in pairs(ply.Ownedz or {}) do + if not IsValid(ent) or not ent:isKeysOwnable() then ply.Ownedz[entIndex] = nil continue end + table.insert(unownables, ent) + end + + for _, otherPly in ipairs(player.GetAll()) do + if ply == otherPly then continue end + + for _, ent in pairs(otherPly.Ownedz or {}) do + if IsValid(ent) and ent:isKeysOwnedBy(ply) then + table.insert(unownables, ent) + end + end + end + + for entIndex, ent in ipairs(unownables) do + local bAllowed, _strReason = hook.Call("playerSell" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent) + + if bAllowed == false then continue end + + if ent:isMasterOwner(ply) then + ent:Fire("unlock", "", 0) + end + + ent:keysUnOwn(ply) + amount = amount + 1 + + local GiveMoneyBack = math.floor((hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent) * 0.666) + 0.5) + hook.Call("playerKeysSold", GAMEMODE, ply, ent, GiveMoneyBack) + cost = cost + GiveMoneyBack + end + + if amount == 0 then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("no_doors_owned")) return "" end + + ply:addMoney(math.floor(cost)) + + DarkRP.notify(ply, 2, 4, DarkRP.getPhrase("sold_x_doors", amount, DarkRP.formatMoney(math.floor(cost)))) + return "" +end +DarkRP.defineChatCommand("unownalldoors", UnOwnAll) + +local function SetDoorTitle(ply, args) + local trace = ply:GetEyeTrace() + + local ent = trace.Entity + if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return "" + end + + if ent:isKeysOwnedBy(ply) then + ent:setKeysTitle(args) + return "" + end + + local function onCAMIResult(allowed) + if not allowed then + DarkRP.notify(ply, 1, 6, DarkRP.getPhrase("no_privilege")) + return + end + + local hasTeams = not fn.Null(ent:getKeysDoorTeams() or {}) + if ent:isKeysOwned() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or hasTeams then + ent:setKeysTitle(args) + end + + if ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or hasTeams then + DarkRP.storeDoorData(trace.Entity) + end + end + + CAMI.PlayerHasAccess(ply, "DarkRP_ChangeDoorSettings", onCAMIResult) + + return "" +end +DarkRP.defineChatCommand("title", SetDoorTitle) + +local function RemoveDoorOwner(ply, args) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return "" + end + + local target = DarkRP.findPlayer(args) + + if ent:getKeysNonOwnable() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_rem_owners_unownable")) + return "" + end + + if not target then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(args))) + return "" + end + + if not ent:isKeysOwnedBy(ply) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("do_not_own_ent")) + return "" + end + + + local canDo = hook.Call("onAllowedToOwnRemoved", nil, ply, ent, target) + if canDo == false then return "" end + + if ent:isKeysAllowedToOwn(target) then + ent:removeKeysAllowedToOwn(target) + end + + if ent:isKeysOwnedBy(target) then + ent:removeKeysDoorOwner(target) + end + + return "" +end +DarkRP.defineChatCommand("removeowner", RemoveDoorOwner) +DarkRP.defineChatCommand("ro", RemoveDoorOwner) + +local function AddDoorOwner(ply, args) + local trace = ply:GetEyeTrace() + local ent = trace.Entity + + if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle"))) + return "" + end + + local target = DarkRP.findPlayer(args) + + if ent:getKeysNonOwnable() then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_add_owners_unownable")) + return "" + end + + if not target then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(args))) + return "" + end + + if not ent:isKeysOwnedBy(ply) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("do_not_own_ent")) + return "" + end + + if ent:isKeysOwnedBy(target) or ent:isKeysAllowedToOwn(target) then + DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("rp_addowner_already_owns_door", target:Nick())) + return "" + end + + local canDo = hook.Call("onAllowedToOwnAdded", nil, ply, ent, target) + if canDo == false then return "" end + + ent:addKeysAllowedToOwn(target) + + + return "" +end +DarkRP.defineChatCommand("addowner", AddDoorOwner) +DarkRP.defineChatCommand("ao", AddDoorOwner) diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/sv_doorvars.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_doorvars.lua new file mode 100644 index 0000000..09c6ec7 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_doorvars.lua @@ -0,0 +1,179 @@ +util.AddNetworkString("DarkRP_UpdateDoorData") +util.AddNetworkString("DarkRP_RemoveDoorData") +util.AddNetworkString("DarkRP_RemoveDoorVar") +util.AddNetworkString("DarkRP_AllDoorData") + +--[[--------------------------------------------------------------------------- +Interface functions +---------------------------------------------------------------------------]] +local eMeta = FindMetaTable("Entity") +function eMeta:getDoorData() + if not self:isKeysOwnable() then return {} end + + self.DoorData = self.DoorData or {} + return self.DoorData +end + +function eMeta:setKeysNonOwnable(ownable) + self:getDoorData().nonOwnable = ownable or nil + DarkRP.updateDoorData(self, "nonOwnable") +end + +function eMeta:setKeysTitle(title) + self:getDoorData().title = title ~= "" and title or nil + DarkRP.updateDoorData(self, "title") +end + +function eMeta:setDoorGroup(group) + self:getDoorData().groupOwn = group + DarkRP.updateDoorData(self, "groupOwn") +end + +function eMeta:addKeysDoorTeam(t) + local doorData = self:getDoorData() + doorData.teamOwn = doorData.teamOwn or {} + doorData.teamOwn[t] = true + + DarkRP.updateDoorData(self, "teamOwn") +end + +function eMeta:removeKeysDoorTeam(t) + local doorData = self:getDoorData() + doorData.teamOwn = doorData.teamOwn or {} + doorData.teamOwn[t] = nil + + if fn.Null(doorData.teamOwn) then + doorData.teamOwn = nil + end + + DarkRP.updateDoorData(self, "teamOwn") +end + +function eMeta:removeAllKeysDoorTeams() + local doorData = self:getDoorData() + doorData.teamOwn = nil + + DarkRP.updateDoorData(self, "teamOwn") +end + +function eMeta:addKeysAllowedToOwn(ply) + local doorData = self:getDoorData() + doorData.allowedToOwn = doorData.allowedToOwn or {} + doorData.allowedToOwn[ply:UserID()] = true + + DarkRP.updateDoorData(self, "allowedToOwn") +end + +function eMeta:removeKeysAllowedToOwn(ply) + local doorData = self:getDoorData() + doorData.allowedToOwn = doorData.allowedToOwn or {} + doorData.allowedToOwn[ply:UserID()] = nil + + if fn.Null(doorData.allowedToOwn) then + doorData.allowedToOwn = nil + end + + DarkRP.updateDoorData(self, "allowedToOwn") +end + +function eMeta:removeAllKeysAllowedToOwn() + local doorData = self:getDoorData() + doorData.allowedToOwn = nil + + DarkRP.updateDoorData(self, "allowedToOwn") +end + +function eMeta:addKeysDoorOwner(ply) + local doorData = self:getDoorData() + doorData.extraOwners = doorData.extraOwners or {} + doorData.extraOwners[ply:UserID()] = true + + DarkRP.updateDoorData(self, "extraOwners") + + self:removeKeysAllowedToOwn(ply) +end + +function eMeta:removeKeysDoorOwner(ply) + local doorData = self:getDoorData() + doorData.extraOwners = doorData.extraOwners or {} + doorData.extraOwners[ply:UserID()] = nil + + if fn.Null(doorData.extraOwners) then + doorData.extraOwners = nil + end + + DarkRP.updateDoorData(self, "extraOwners") +end + +function eMeta:removeAllKeysExtraOwners() + local doorData = self:getDoorData() + doorData.extraOwners = nil + + DarkRP.updateDoorData(self, "extraOwners") +end + +function eMeta:removeDoorData() + net.Start("DarkRP_RemoveDoorData") + net.WriteUInt(self:EntIndex(), 32) + net.Send(player.GetAll()) +end + +--[[--------------------------------------------------------------------------- +Networking +---------------------------------------------------------------------------]] + +local plyMeta = FindMetaTable("Player") +function plyMeta:sendDoorData() + if self:EntIndex() == 0 then return end + + local res = {} + for _, v in ipairs(ents.GetAll()) do + if not v:getDoorData() or table.IsEmpty(v:getDoorData()) then continue end + + res[v:EntIndex()] = v:getDoorData() + end + + net.Start("DarkRP_AllDoorData") + net.WriteUInt(table.Count(res), 16) + + for ix, vars in pairs(res) do + net.WriteUInt(ix, 16) + + net.WriteUInt(table.Count(vars), 8) + + for varName, value in pairs(vars) do + DarkRP.writeNetDoorVar(varName, value) + end + end + net.Send(self) +end + +concommand.Add("_sendAllDoorData", fn.Id) -- Backwards compatibility + +hook.Add("PlayerInitialSpawn", "DarkRP_DoorData", plyMeta.sendDoorData) + +function DarkRP.updateDoorData(door, member) + if not IsValid(door) or not door:getDoorData() then error("Calling updateDoorData on a door that has no data!") end + + local value = door:getDoorData()[member] + + if value == nil then + local doorvar = DarkRP.getDoorVarsByName()[member] + net.Start("DarkRP_RemoveDoorVar") + net.WriteUInt(door:EntIndex(), 16) + if not doorvar then + net.WriteUInt(0, 8) + net.WriteString(member) + else + net.WriteUInt(doorvar.id, 8) + end + net.Broadcast() + + return + end + + net.Start("DarkRP_UpdateDoorData") + net.WriteUInt(door:EntIndex(), 32) + DarkRP.writeNetDoorVar(member, value) + net.Broadcast() +end diff --git a/gamemodes/darkrp/gamemode/modules/doorsystem/sv_interface.lua b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_interface.lua new file mode 100644 index 0000000..013d5b1 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/doorsystem/sv_interface.lua @@ -0,0 +1,851 @@ +DarkRP.doorToEntIndex = DarkRP.stub{ + name = "doorToEntIndex", + description = "Get an ENT index from a door index.", + parameters = { + { + name = "index", + description = "The door index", + type = "number", + optional = false + } + }, + returns = { + { + name = "index", + description = "The ENT index", + type = "number", + } + }, + metatable = DarkRP +} + +DarkRP.doorIndexToEnt = DarkRP.stub{ + name = "doorIndexToEnt", + description = "Get the entity of a door index (inverse of ent:doorIndexToEnt()). Note: the door MUST have been created by the map!", + parameters = { + { + name = "index", + description = "The door index", + type = "number", + optional = false + } + }, + returns = { + { + name = "door", + description = "The door entity", + type = "Entity", + } + }, + metatable = DarkRP +} + +DarkRP.writeNetDoorVar = DarkRP.stub{ + name = "writeNetDoorVar", + description = "Internal function. You probably shouldn't need this. DarkRP calls this function when sending DoorVar net messages. This function writes the net data for a specific DoorVar.", + parameters = { + { + name = "name", + description = "The name of the DoorVar.", + type = "string", + optional = false + }, + { + name = "value", + description = "The value of the DoorVar.", + type = "any", + optional = false + } + }, + returns = { + }, + metatable = DarkRP +} + +DarkRP.ENTITY.doorIndex = DarkRP.stub{ + name = "doorIndex", + description = "Get the door index of a door. Use this to store door information in the database.", + parameters = { + }, + returns = { + { + name = "index", + description = "The door index.", + type = "number" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.keysLock = DarkRP.stub{ + name = "keysLock", + description = "Lock this door or vehicle.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.keysUnLock = DarkRP.stub{ + name = "keysUnLock", + description = "Unlock this door or vehicle.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.addKeysAllowedToOwn = DarkRP.stub{ + name = "addKeysAllowedToOwn", + description = "Make this player allowed to co-own the door or vehicle.", + parameters = { + { + name = "ply", + description = "The player to give permission to co-own.", + type = "Player", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.addKeysDoorOwner = DarkRP.stub{ + name = "addKeysDoorOwner", + description = "Make this player a co-owner of the door.", + parameters = { + { + name = "ply", + description = "The player to add as co-owner.", + type = "Player", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.removeKeysDoorOwner = DarkRP.stub{ + name = "removeKeysDoorOwner", + description = "Remove this player as co-owner", + parameters = { + { + name = "ply", + description = "The player to remove from the co-owners list.", + type = "Player", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.keysOwn = DarkRP.stub{ + name = "keysOwn", + description = "Make the player the master owner of the door", + parameters = { + { + name = "ply", + description = "The player set as master owner.", + type = "Player", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.keysUnOwn = DarkRP.stub{ + name = "keysUnOwn", + description = "Make this player unown the door/vehicle.", + parameters = { + { + name = "ply", + description = "The player.", + type = "Player", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.setKeysNonOwnable = DarkRP.stub{ + name = "setKeysNonOwnable", + description = "Set whether this door or vehicle is ownable or not.", + parameters = { + { + name = "ownable", + description = "Whether the door or vehicle is blocked from ownership.", + type = "boolean", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.setKeysTitle = DarkRP.stub{ + name = "setKeysTitle", + description = "Set the title of a door or vehicle.", + parameters = { + { + name = "title", + description = "The title of the door.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.setDoorGroup = DarkRP.stub{ + name = "setDoorGroup", + description = "Set the door group of a door.", + parameters = { + { + name = "group", + description = "The door group.", + type = "string", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.addKeysDoorTeam = DarkRP.stub{ + name = "addKeysDoorTeam", + description = "Allow a team to lock/unlock a door..", + parameters = { + { + name = "team", + description = "The team to add to team owners.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.removeKeysDoorTeam = DarkRP.stub{ + name = "removeKeysDoorTeam", + description = "Disallow a team from locking/unlocking a door.", + parameters = { + { + name = "team", + description = "The team to remove from team owners.", + type = "number", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.removeAllKeysDoorTeams = DarkRP.stub{ + name = "removeAllKeysDoorTeams", + description = "Disallow all teams from locking/unlocking a door.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.removeAllKeysExtraOwners = DarkRP.stub{ + name = "removeAllKeysExtraOwners", + description = "Remove all co-owners from a door.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.removeAllKeysAllowedToOwn = DarkRP.stub{ + name = "removeAllKeysAllowedToOwn", + description = "Disallow all people from owning the door.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.removeKeysAllowedToOwn = DarkRP.stub{ + name = "removeKeysAllowedToOwn", + description = "Remove a player from being allowed to co-own a door.", + parameters = { + { + name = "ply", + description = "The player to be removed.", + type = "Player", + optional = false + } + }, + returns = { + }, + metatable = DarkRP.ENTITY +} + +DarkRP.ENTITY.isLocked = DarkRP.stub{ + name = "isLocked", + description = "Whether this door/vehicle is locked.", + parameters = { + }, + returns = { + { + name = "answer", + description = "Whether it's locked.", + type = "boolean" + } + }, + metatable = DarkRP.ENTITY +} + +DarkRP.PLAYER.keysUnOwnAll = DarkRP.stub{ + name = "keysUnOwnAll", + description = "Unown every door and vehicle owned by this player.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.sendDoorData = DarkRP.stub{ + name = "sendDoorData", + description = "Internal function. Sends all door data to a player.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.doPropertyTax = DarkRP.stub{ + name = "doPropertyTax", + description = "Tax a player based on the amount of doors and vehicles they have.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.PLAYER +} + +DarkRP.PLAYER.initiateTax = DarkRP.stub{ + name = "initiateTax", + description = "Internal function, starts the timer that taxes the player every once in a while.", + parameters = { + }, + returns = { + }, + metatable = DarkRP.PLAYER +} + +DarkRP.hookStub{ + name = "onKeysLocked", + description = "Called when a door or vehicle was locked.", + parameters = { + { + name = "ent", + description = "The entity that was locked.", + type = "Entity" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "onKeysUnlocked", + description = "Called when a door or vehicle was unlocked.", + parameters = { + { + name = "ent", + description = "The entity that was unlocked.", + type = "Entity" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "playerKeysSold", + description = "When a player sold a door or vehicle.", + parameters = { + { + name = "ply", + description = "The player who sold the door or vehicle.", + type = "Player" + }, + { + name = "ent", + description = "The entity that was sold.", + type = "Entity" + }, + { + name = "GiveMoneyBack", + description = "The amount of money refunded to the player", + type = "number" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "hideSellDoorMessage", + description = "Whether to hide the door/vehicle sold notification", + parameters = { + { + name = "ply", + description = "The player who sold the door or vehicle.", + type = "Player" + }, + { + name = "ent", + description = "The entity that was sold.", + type = "Player" + }, + }, + returns = { + { + name = "hide", + description = "Whether to hide the notification.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "getDoorCost", + description = "Get the cost of a door.", + parameters = { + { + name = "ply", + description = "The player who has the intention to purchase the door.", + type = "Player" + }, + { + name = "ent", + description = "The door", + type = "Entity" + } + }, + returns = { + { + name = "cost", + description = "The price of the door.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "getVehicleCost", + description = "Get the cost of a vehicle.", + parameters = { + { + name = "ply", + description = "The player who has the intention to purchase the vehicle.", + type = "Player" + }, + { + name = "ent", + description = "The vehicle", + type = "Entity" + } + }, + returns = { + { + name = "cost", + description = "The price of the vehicle.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "playerBuyDoor", + description = "When a player purchases a door.", + parameters = { + { + name = "ply", + description = "The player who is to buy the door.", + type = "Player" + }, + { + name = "ent", + description = "The door.", + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to buy the door.", + type = "boolean" + }, + { + name = "reason", + description = "The reason why a player is not allowed to buy the door, if applicable.", + type = "string" + }, + { + name = "surpress", + description = "Whether to show the reason in a notification to the player, return true here to surpress the message.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "playerSellDoor", + description = "When a player is about to sell a door.", + parameters = { + { + name = "ply", + description = "The player who is to sell the door.", + type = "Player" + }, + { + name = "ent", + description = "The door.", + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to sell the door.", + type = "boolean" + }, + { + name = "reason", + description = "The reason why a player is not allowed to sell the door, if applicable.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "onAllowedToOwnAdded", + description = "When a player adds a co-owner to a door.", + parameters = { + { + name = "ply", + description = "The player who adds the co-owner.", + type = "Player" + }, + { + name = "ent", + description = "The door.", + type = "Entity" + }, + { + name = "target", + description = "The target who will be allowed to own the door.", + type = "Player" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to add this player as co-owner.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "onAllowedToOwnRemoved", + description = "When a player removes a co-owner to a door.", + parameters = { + { + name = "ply", + description = "The player who removes the co-owner.", + type = "Player" + }, + { + name = "ent", + description = "The door.", + type = "Entity" + }, + { + name = "target", + description = "The target who will not be allowed to own the door anymore.", + type = "Player" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to remove this player as co-owner.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "playerBuyVehicle", + description = "When a player purchases a vehicle.", + parameters = { + { + name = "ply", + description = "The player who is to buy the vehicle.", + type = "Player" + }, + { + name = "ent", + description = "The vehicle.", + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to buy the vehicle.", + type = "boolean" + }, + { + name = "reason", + description = "The reason why a player is not allowed to buy the vehicle, if applicable.", + type = "string" + }, + { + name = "surpress", + description = "Whether to show the reason in a notification to the player, return true here to surpress the message.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "playerSellVehicle", + description = "When a player is about to sell a vehicle.", + parameters = { + { + name = "ply", + description = "The player who is to sell the vehicle.", + type = "Player" + }, + { + name = "ent", + description = "The vehicle.", + type = "Entity" + } + }, + returns = { + { + name = "allowed", + description = "Whether the player is allowed to sell the vehicle.", + type = "boolean" + }, + { + name = "reason", + description = "The reason why a player is not allowed to sell the vehicle, if applicable.", + type = "string" + } + } +} + +DarkRP.hookStub{ + name = "playerBoughtDoor", + description = "Called when a player has purchased a door.", + parameters = { + { + name = "ply", + description = "The player who has purchased the door.", + type = "Player" + }, + { + name = "ent", + description = "The purchased door.", + type = "Entity" + }, + { + name = "cost", + description = "The cost of the purchased door.", + type = "number" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "playerBoughtVehicle", + description = "Called when a player has purchased a vehicle.", + parameters = { + { + name = "ply", + description = "The player who has purchased the vehicle.", + type = "Player" + }, + { + name = "ent", + description = "The purchased vehicle.", + type = "Entity" + }, + { + name = "cost", + description = "The cost of the purchased vehicle.", + type = "number" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "canTax", + description = "Called before a player pays taxes. This hook differs from onPaidTax in that this hook can prevent the taxing and change the tax amount.", + parameters = { + { + name = "ply", + description = "The player who was taxed.", + type = "Player" + }, + { + name = "tax", + description = "The amount of money that is about to be taxed from the player.", + type = "number" + } + }, + returns = { + { + name = "shouldTax", + description = "Whether the player is to be taxed. Return false here to prevent the player from being taxed.", + type = "boolean" + }, + { + name = "tax", + description = "Overrides the amount of money that is taxed.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "onPaidTax", + description = "Called when a player has paid tax.", + parameters = { + { + name = "ply", + description = "The player who was taxed.", + type = "Player" + }, + { + name = "tax", + description = "The percentage of tax taken from his wallet.", + type = "number" + }, + { + name = "wallet", + description = "The amount of money the player had before the tax was applied.", + type = "number" + } + }, + returns = { + + } +} + +DarkRP.hookStub{ + name = "canTaxEntity", + description = "Called right before a player's property is taxed. Decides per entity whether it can be taxed.", + parameters = { + { + name = "ply", + description = "The player whose property will be taxed.", + type = "Player" + }, + { + name = "ent", + description = "The door or vehicle that is to be taxed", + type = "Entity" + } + }, + returns = { + { + name = "shouldTax", + description = "Return false here to prevent this specific entity from being taxed.", + type = "boolean" + } + } +} + +DarkRP.hookStub{ + name = "canPropertyTax", + description = "Called right before a player's property is taxed. This hook differs from onPropertyTax in that onPropertyTax is called AFTER the taxing. With this hook, one can influence the taxing process.", + parameters = { + { + name = "ply", + description = "The player whose property will be taxed.", + type = "Player" + }, + { + name = "tax", + description = "The amount of money that will be taxed (unless overridden by this hook).", + type = "number" + } + }, + returns = { + { + name = "shouldTax", + description = "Return false here to prevent the doors from being taxed.", + type = "boolean" + }, + { + name = "taxOverride", + description = "Override the tax amount.", + type = "number" + } + } +} + +DarkRP.hookStub{ + name = "onPropertyTax", + description = "Called right AFTER a player's property is taxed. Please use canPropertyTax if you want to influence the taxing process.", + parameters = { + { + name = "ply", + description = "The player whose property has been taxed.", + type = "Player" + }, + { + name = "tax", + description = "The amount of money that has been taxed.", + type = "number" + }, + { + name = "couldAfford", + description = "Whether the player was able to afford the tax.", + type = "boolean" + } + }, + returns = { + } +} diff --git a/gamemodes/darkrp/gamemode/modules/events/sh_events.lua b/gamemodes/darkrp/gamemode/modules/events/sh_events.lua new file mode 100644 index 0000000..9ba70c9 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/events/sh_events.lua @@ -0,0 +1,13 @@ +DarkRP.declareChatCommand{ + command = "enablestorm", + description = "Enable meteor storms.", + delay = 1.5, + condition = hasCommandsPriv +} + +DarkRP.declareChatCommand{ + command = "disablestorm", + description = "Disable meteor storms.", + delay = 1.5, + condition = hasCommandsPriv +} diff --git a/gamemodes/darkrp/gamemode/modules/events/sv_events.lua b/gamemodes/darkrp/gamemode/modules/events/sv_events.lua new file mode 100644 index 0000000..0e8cadf --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/events/sv_events.lua @@ -0,0 +1,224 @@ +resource.AddFile("sound/earthquake.mp3") +util.PrecacheSound("earthquake.mp3") + +--[[--------------------------------------------------------- +Variables +---------------------------------------------------------]] +local timeLeft = 10 +local stormOn = false + + +--[[--------------------------------------------------------- +Meteor storm +---------------------------------------------------------]] +local function StormStart() + local phrase = DarkRP.getPhrase("meteor_approaching") + for _, v in ipairs(player.GetAll()) do + if v:Alive() then + v:PrintMessage(HUD_PRINTCENTER, phrase) + v:PrintMessage(HUD_PRINTTALK, phrase) + end + end +end + +local function StormEnd() + local phrase = DarkRP.getPhrase("meteor_passing") + for _, v in ipairs(player.GetAll()) do + if v:Alive() then + v:PrintMessage(HUD_PRINTCENTER, phrase) + v:PrintMessage(HUD_PRINTTALK, phrase) + end + end +end + +local function ControlStorm() + timeLeft = timeLeft - 1 + + if timeLeft < 1 then + if stormOn then + timeLeft = math.random(300, 500) + stormOn = false + timer.Stop("start") + StormEnd() + else + timeLeft = math.random(60, 90) + stormOn = true + timer.Start("start") + StormStart() + end + end +end + +local function AttackEnt(ent) + local meteor = ents.Create("meteor") + meteor.nodupe = true + meteor:Spawn() + meteor:SetMeteorTarget(ent) +end + +local function StartShower() + timer.Adjust("start", math.random(0.1, 1), 0, StartShower) + for _, v in ipairs(player.GetAll()) do + if math.random(0, 2) == 0 and v:Alive() then + AttackEnt(v) + end + end +end + +local function StartStorm(ply) + timer.Start("stormControl") + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("meteor_enabled")) +end +DarkRP.definePrivilegedChatCommand("enablestorm", "DarkRP_AdminCommands", StartStorm) + +local function StopStorm(ply) + timer.Stop("stormControl") + stormOn = false + timer.Stop("start") + StormEnd() + DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("meteor_disabled")) +end +DarkRP.definePrivilegedChatCommand("disablestorm", "DarkRP_AdminCommands", StopStorm) + +timer.Create("start", 1, 0, StartShower) +timer.Create("stormControl", 1, 0, ControlStorm) + +timer.Stop("start") +timer.Stop("stormControl") + +--[[--------------------------------------------------------- +Earthquake +---------------------------------------------------------]] +local lastmagnitudes = {} -- The magnitudes of the last tremors +local tremor + +local function createTremor() + tremor = ents.Create("env_physexplosion") + tremor:SetPos(Vector(0, 0, 0)) + tremor:SetKeyValue("radius", 9999999999) + tremor:SetKeyValue("spawnflags", 7) + tremor.nodupe = true + tremor:Spawn() +end + +hook.Add("PostCleanupMap", "DarkRP_events", createTremor) + +hook.Add("InitPostEntity", "DarkRP_SetupTremor", function() + createTremor() +end) + +local function TremorReport() + local mag = table.remove(lastmagnitudes, 1) + if mag then + if mag < 6.5 then + DarkRP.notifyAll(0, 3, DarkRP.getPhrase("earthtremor_report", tostring(mag))) + return + end + DarkRP.notifyAll(0, 3, DarkRP.getPhrase("earthquake_report", tostring(mag))) + end +end + +local function EarthQuakeTest() + if not GAMEMODE.Config.earthquakes then return end + + if GAMEMODE.Config.quakechance and math.random(0, GAMEMODE.Config.quakechance) < 1 then + local en = ents.FindByClass("prop_physics") + local plys = player.GetAll() + + local force = math.random(10, 1000) + tremor:SetKeyValue("magnitude", force / 6) + + for _, v in ipairs(plys) do + v:EmitSound("earthquake.mp3", force / 6, 100) + end + tremor:Fire("explode","",0.5) + util.ScreenShake(Vector(0, 0, 0), force, math.random(25, 50), math.random(5, 12), 9999999999) + table.insert(lastmagnitudes, math.floor((force / 10) + .5) / 10) + timer.Simple(10, function() TremorReport() end) + for _, e in ipairs(en) do + local rand = math.random(650, 1000) + if rand < force and rand % 2 == 0 then + e:Fire("enablemotion", "", 0) + constraint.RemoveAll(e) + end + if e:IsOnGround() then + e:TakeDamage((force / 100) + 15, game.GetWorld()) + end + end + end +end +timer.Create("EarthquakeTest", 1, 0, EarthQuakeTest) + +--[[--------------------------------------------------------- + Flammable +---------------------------------------------------------]] +-- Class names as index +local flammablePropsKV = { + drug = true, + drug_lab = true, + food = true, + gunlab = true, + letter = true, + microwave = true, + money_printer = true, + spawned_shipment = true, + spawned_weapon = true, + spawned_money = true +} + +local flammableProps = {} -- Numbers as index +for k in pairs(flammablePropsKV) do table.insert(flammableProps, k) end + + +local function IsFlammable(ent) + return flammablePropsKV[ent:GetClass()] ~= nil +end + +-- FireSpread from SeriousRP +local function FireSpread(ent, chanceDiv) + if not ent:IsOnFire() then return end + + if ent:isMoneyBag() then + ent:Remove() + end + + local rand = math.random(0, 300 / chanceDiv) + + if rand > 1 then return end + local en = ents.FindInSphere(ent:GetPos(), math.random(20, 90)) + + for _, v in ipairs(en) do + if not IsFlammable(v) or v == ent then continue end + + if not v.burned then + v:Ignite(math.random(5,180), 0) + v.burned = true + break -- Don't ignite all entities in sphere at once, just one at a time + end + + local color = v:GetColor() + if (color.r - 51) >= 0 then color.r = color.r - 51 end + if (color.g - 51) >= 0 then color.g = color.g - 51 end + if (color.b - 51) >= 0 then color.b = color.b - 51 end + v:SetColor(color) + if (color.r + color.g + color.b) < 103 and math.random(1, 100) < 35 then + v:Fire("enablemotion", "", 0) + constraint.RemoveAll(v) + end + end +end + +local function FlammablePropThink() + local class = flammableProps[math.random(#flammableProps)] + local entities = ents.FindByClass(class) + local ent = entities[math.random(#entities)] + + if class ~= "letter" then return end + + if not ent then return end + + -- The amount of classes and the amount of entities in a class + -- affect the chance of fire spreading. This should be minimized. + FireSpread(ent, #entities * #flammableProps) +end +timer.Create("FlammableProps", 0.1, 0, FlammablePropThink) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin.lua b/gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin.lua new file mode 100644 index 0000000..9828d78 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin.lua @@ -0,0 +1,88 @@ +local function IncludeFolder(fol) + fol = string.lower(fol) + local _, folders = file.Find(fol .. "*", "LUA") + + for _, folder in SortedPairs(folders, true) do + if folder ~= "." and folder ~= ".." then + for _, File in SortedPairs(file.Find(fol .. folder .. "/sh_*.lua", "LUA"), true) do + include(fol .. folder .. "/" .. File) + end + + for _, File in SortedPairs(file.Find(fol .. folder .. "/cl_*.lua", "LUA"), true) do + include(fol .. folder .. "/" .. File) + end + end + end +end + +IncludeFolder(GM.FolderName .. "/gamemode/modules/fadmin/fadmin/") +IncludeFolder(GM.FolderName .. "/gamemode/modules/fadmin/fadmin/playeractions/") + +--[[--------------------------------------------------------------------------- +FAdmin global settings +---------------------------------------------------------------------------]] +net.Receive("FAdmin_GlobalSetting", function(len) + local setting, value = net.ReadString(), net.ReadType(net.ReadUInt(8)) + + FAdmin.GlobalSetting = FAdmin.GlobalSetting or {} + FAdmin.GlobalSetting[setting] = value +end) + +net.Receive("FAdmin_PlayerSetting", function(len) + local uid, setting, value = net.ReadUInt(16), net.ReadString(), net.ReadType(net.ReadUInt(8)) + + FAdmin.PlayerSettings = FAdmin.PlayerSettings or {} + FAdmin.PlayerSettings[uid] = FAdmin.PlayerSettings[uid] or {} + FAdmin.PlayerSettings[uid][setting] = value +end) + +timer.Create("FAdmin_CleanPlayerSettings", 300, 0, function() + if not FAdmin.PlayerSettings then return end + + -- find highest userID + local max = math.huge + for _, v in ipairs(player.GetAll()) do + if IsValid(v) and v:UserID() > max then max = v:UserID() end + end + + -- Anything lower than the maximal UserID can be culled + -- This prevents data from joining players from being removed + -- New players always get a strictly higher UserID than any player before them + for uid in pairs(FAdmin.PlayerSettings) do + if IsValid(Player(uid)) or uid > max then continue end + + FAdmin.PlayerSettings[uid] = nil + end +end) + +local plyMeta = FindMetaTable("Player") + +function plyMeta:FAdmin_GetGlobal(setting) + local uid = self:UserID() + return FAdmin.PlayerSettings and FAdmin.PlayerSettings[uid] and FAdmin.PlayerSettings[uid][setting] or nil +end + +net.Receive("FAdmin_GlobalPlayerSettings", function(len) + local globalCount = net.ReadUInt(8) + + FAdmin.GlobalSetting = FAdmin.GlobalSetting or {} + + for i = 1, globalCount do + FAdmin.GlobalSetting[net.ReadString()] = net.ReadType(net.ReadUInt(8)) + end + + local plyCount = net.ReadUInt(8) + FAdmin.PlayerSettings = FAdmin.PlayerSettings or {} + + for i = 1, plyCount do + local uid = net.ReadUInt(16) + local count = net.ReadUInt(8) + + FAdmin.PlayerSettings[uid] = FAdmin.PlayerSettings[uid] or {} + + for j = 1, count do + FAdmin.PlayerSettings[uid][net.ReadString()] = net.ReadType(net.ReadUInt(8)) + end + end +end) + diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin_darkrp.lua b/gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin_darkrp.lua new file mode 100644 index 0000000..55bfdb8 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/cl_fadmin_darkrp.lua @@ -0,0 +1,99 @@ +if not FAdmin or not FAdmin.StartHooks then return end +FAdmin.StartHooks["DarkRP"] = function() + -- DarkRP information: + FAdmin.ScoreBoard.Player:AddInformation("Money", function(ply) if LocalPlayer():IsAdmin() then return DarkRP.formatMoney(ply:getDarkRPVar("money")) end end) + FAdmin.ScoreBoard.Player:AddInformation("Steam name", function(ply) return ply:SteamName() end) + FAdmin.ScoreBoard.Player:AddInformation("Wanted", function(ply) if ply:getDarkRPVar("wanted") then return tostring(ply:getDarkRPVar("wantedReason") or "N/A") end end) + FAdmin.ScoreBoard.Player:AddInformation("Community link", function(ply) return FAdmin.SteamToProfile(ply) end) + FAdmin.ScoreBoard.Player:AddInformation("Rank", function(ply) + if FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SeeAdmins") then + return ply:GetUserGroup() + end + end) + FAdmin.ScoreBoard.Player:AddInformation("Wanted reason", function(ply) + if ply:isWanted() and LocalPlayer():isCP() then + return ply:getWantedReason() + end + end) + + -- Warrant + FAdmin.ScoreBoard.Player:AddActionButton("Warrant", "fadmin/icons/message", Color(0, 0, 200, 255), + function(ply) return LocalPlayer():isCP() end, + function(ply, button) + Derma_StringRequest("Warrant reason", "Enter the reason for the warrant", "", function(Reason) + RunConsoleCommand("darkrp", "warrant", ply:SteamID(), Reason) + end) + end) + + --wanted + FAdmin.ScoreBoard.Player:AddActionButton(function(ply) + return ((ply:getDarkRPVar("wanted") and "Unw") or "W") .. "anted" + end, + function(ply) return "fadmin/icons/jail", ply:getDarkRPVar("wanted") and "fadmin/icons/disable" end, + Color(0, 0, 200, 255), + function(ply) return LocalPlayer():isCP() end, + function(ply, button) + if not ply:getDarkRPVar("wanted") then + Derma_StringRequest("wanted reason", "Enter the reason to arrest this player", "", function(Reason) + RunConsoleCommand("darkrp", "wanted", ply:SteamID(), Reason) + end) + else + RunConsoleCommand("darkrp", "unwanted", ply:UserID()) + end + end) + + --Teamban + local function teamban(ply, button) + local menu = DermaMenu() + + local Padding = vgui.Create("DPanel") + Padding:SetPaintBackgroundEnabled(false) + Padding:SetSize(1,5) + menu:AddPanel(Padding) + + local Title = vgui.Create("DLabel") + Title:SetText(" Jobs:\n") + Title:SetFont("UiBold") + Title:SizeToContents() + Title:SetTextColor(color_black) + menu:AddPanel(Title) + + local command = "teamban" + local uid = ply:UserID() + for k, v in SortedPairsByMemberValue(RPExtraTeams, "name") do + local submenu = menu:AddSubMenu(v.name) + submenu:AddOption("2 minutes", function() RunConsoleCommand("darkrp", command, uid, k, 120) end) + submenu:AddOption("Half an hour", function() RunConsoleCommand("darkrp", command, uid, k, 1800) end) + submenu:AddOption("An hour", function() RunConsoleCommand("darkrp", command, uid, k, 3600) end) + submenu:AddOption("Until restart", function() RunConsoleCommand("darkrp", command, uid, k, 0) end) + end + menu:Open() + end + FAdmin.ScoreBoard.Player:AddActionButton("Ban from job", "fadmin/icons/changeteam", Color(200, 0, 0, 255), + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "DarkRP_AdminCommands", ply) end, teamban) + + local function teamunban(ply, button) + local menu = DermaMenu() + + local Padding = vgui.Create("DPanel") + Padding:SetPaintBackgroundEnabled(false) + Padding:SetSize(1,5) + menu:AddPanel(Padding) + + local Title = vgui.Create("DLabel") + Title:SetText(" Jobs:\n") + Title:SetFont("UiBold") + Title:SizeToContents() + Title:SetTextColor(color_black) + menu:AddPanel(Title) + + local command = "teamunban" + local uid = ply:UserID() + for k, v in SortedPairsByMemberValue(RPExtraTeams, "name") do + menu:AddOption(v.name, function() RunConsoleCommand("darkrp", command, uid, k) end) + end + menu:Open() + end + FAdmin.ScoreBoard.Player:AddActionButton("Unban from job", function() return "fadmin/icons/changeteam", "fadmin/icons/disable" end, Color(200, 0, 0, 255), + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "DarkRP_AdminCommands", ply) end, teamunban) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/cl_init.lua new file mode 100644 index 0000000..2d3ddae --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/cl_init.lua @@ -0,0 +1,262 @@ +local ContinueNewGroup +local EditGroups + +local function RetrievePRIVS(len) + FAdmin.Access.Groups = net.ReadTable() + + for k, v in pairs(FAdmin.Access.Groups) do + if CAMI.GetUsergroup(k) then continue end + + CAMI.RegisterUsergroup({ + Name = k, + Inherits = FAdmin.Access.ADMIN[v.ADMIN] + }, "FAdmin") + end + + -- Remove any groups that are removed from FAdmin from CAMI. + for k in pairs(CAMI.GetUsergroups()) do + if FAdmin.Access.Groups[k] then continue end + + CAMI.UnregisterUsergroup(k, "FAdmin") + end +end +net.Receive("FADMIN_SendGroups", RetrievePRIVS) + +local function addPriv(um) + local group = um:ReadString() + FAdmin.Access.Groups[group] = FAdmin.Access.Groups[group] or {} + FAdmin.Access.Groups[group].PRIVS[um:ReadString()] = true +end +usermessage.Hook("FAdmin_AddPriv", addPriv) + +local function removePriv(um) + FAdmin.Access.Groups[um:ReadString()].PRIVS[um:ReadString()] = nil +end +usermessage.Hook("FAdmin_RemovePriv", removePriv) + +local function addGroupUI(ply, func) + Derma_StringRequest("Set name", + "What will be the name of the new group?", + "", + function(text) + if text == "" then return end + Derma_Query("On what access will this team be based? (the new group will inherit all the privileges from the group)", "Admin access", + "user", function() ContinueNewGroup(ply, text, 0, func) end, + "admin", function() ContinueNewGroup(ply, text, 1, func) end, + "superadmin", function() ContinueNewGroup(ply, text, 2, func) end) + end) +end + +FAdmin.StartHooks["1SetAccess"] = function() -- 1 in hook name so it will be executed first. + FAdmin.Commands.AddCommand("setaccess", nil, "", "", "[new group based on (number)]", "[new group privileges]") + + FAdmin.ScoreBoard.Player:AddActionButton("Set access", "fadmin/icons/access", Color(155, 0, 0, 255), + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SetAccess") or LocalPlayer():IsSuperAdmin() end, function(ply) + local menu = DermaMenu() + + local Padding = vgui.Create("DPanel") + Padding:SetPaintBackgroundEnabled(false) + Padding:SetSize(1,5) + menu:AddPanel(Padding) + + local Title = vgui.Create("DLabel") + Title:SetText(" Set access:\n") + Title:SetFont("UiBold") + Title:SizeToContents() + Title:SetTextColor(color_black) + + menu:AddPanel(Title) + + for k in SortedPairsByMemberValue(FAdmin.Access.Groups, "ADMIN", true) do + menu:AddOption(k, function() + if not IsValid(ply) then return end + RunConsoleCommand("_FAdmin", "setaccess", ply:UserID(), k) + end) + end + + menu:AddOption("New...", function() addGroupUI(ply) end) + menu:Open() + end) + + FAdmin.ScoreBoard.Server:AddPlayerAction("Edit groups", "fadmin/icons/access", Color(0, 155, 0, 255), function() return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManageGroups") or FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManagePrivileges") end, EditGroups) + + -- Admin immunity + FAdmin.ScoreBoard.Server:AddServerSetting(function() + return (FAdmin.GlobalSetting.Immunity and "Disable" or "Enable") .. " Admin immunity" + end, + function() + return "fadmin/icons/access", FAdmin.GlobalSetting.Immunity and "fadmin/icons/disable" + end, Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManageGroups") end, function(button) + button:SetImage2((not FAdmin.GlobalSetting.Immunity and "fadmin/icons/disable") or "null") + button:SetText((not FAdmin.GlobalSetting.Immunity and "Disable" or "Enable") .. " Admin immunity") + button:GetParent():InvalidateLayout() + + RunConsoleCommand("_Fadmin", "immunity", (FAdmin.GlobalSetting.Immunity and 0) or 1) + end + ) +end + +ContinueNewGroup = function(ply, name, admin_access, func) + if IsValid(ply) then + RunConsoleCommand("_FAdmin", "setaccess", ply:UserID(), name, admin_access) + else + RunConsoleCommand("_FAdmin", "AddGroup", name, admin_access) + end + + if func then + func(name, admin_access) + end +end + +EditGroups = function() + local frame, SelectedGroup, AddGroup, RemGroup, Privileges, SelectedPrivs, AddPriv, RemPriv, lblImmunity, nmbrImmunity + + frame = vgui.Create("DFrame") + frame:SetTitle("Create, edit and remove groups") + frame:MakePopup() + frame:SetVisible(true) + frame:SetSize(640, 480) + frame:Center() + + SelectedGroup = vgui.Create("DComboBox", frame) + SelectedGroup:SetPos(5, 30) + SelectedGroup:SetWidth(145) + + for _, v in pairs(FAdmin.Access.Groups) do + v.immunity = v.immunity or 0 + end + for k in SortedPairsByMemberValue(FAdmin.Access.Groups, "immunity", true) do + SelectedGroup:AddChoice(k) + end + + AddGroup = vgui.Create("DButton", frame) + AddGroup:SetPos(155, 30) + AddGroup:SetSize(60, 22) + AddGroup:SetText("Add Group") + AddGroup.DoClick = function() + addGroupUI(nil, function(name, admin, privs) + SelectedGroup:AddChoice(name) + SelectedGroup:SetValue(name) + RemGroup:SetDisabled(false) + + Privileges:Clear() + SelectedPrivs:Clear() + nmbrImmunity:SetText(FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity) + nmbrImmunity:SetDisabled(false) + nmbrImmunity:SetEditable(true) + + for priv, am in SortedPairs(FAdmin.Access.Privileges) do + if am <= admin + 1 then + SelectedPrivs:AddLine(priv) + else + Privileges:AddLine(priv) + end + end + end) + end + + RemGroup = vgui.Create("DButton", frame) + RemGroup:SetPos(220, 30) + RemGroup:SetSize(85, 22) + RemGroup:SetText("Remove Group") + RemGroup.DoClick = function() + RunConsoleCommand("_FAdmin", "RemoveGroup", SelectedGroup:GetValue()) + + for k, v in pairs(SelectedGroup.Choices) do + if v ~= SelectedGroup:GetValue() then continue end + + SelectedGroup.Choices[k] = nil + break + end + table.ClearKeys(SelectedGroup.Choices) + + SelectedGroup:SetValue("user") + SelectedGroup:OnSelect(1, "user") + end + + Privileges = vgui.Create("DListView", frame) + Privileges:SetPos(5, 55) + Privileges:SetSize(300, 420) + Privileges:AddColumn("Available privileges") + + SelectedPrivs = vgui.Create("DListView", frame) + SelectedPrivs:SetPos(340, 55) + SelectedPrivs:SetSize(295, 420) + SelectedPrivs:AddColumn("Selected Privileges") + + function SelectedGroup:OnSelect(index, value, data) + if not FAdmin.Access.Groups[value] then return end + + RemGroup:SetDisabled(false) + if table.HasValue(FAdmin.Access.ADMIN, value) then + RemGroup:SetDisabled(true) + end + + Privileges:Clear() + SelectedPrivs:Clear() + + for priv, _ in SortedPairs(FAdmin.Access.Privileges) do + if FAdmin.Access.Groups[value].PRIVS[priv] then + SelectedPrivs:AddLine(priv) + else + Privileges:AddLine(priv) + end + end + + if nmbrImmunity then + nmbrImmunity:SetText(FAdmin.Access.Groups[value].immunity or "") + if table.HasValue({"superadmin", "admin", "user", "noaccess"}, string.lower(value)) then + nmbrImmunity:SetDisabled(true) + nmbrImmunity:SetEditable(false) + else + nmbrImmunity:SetDisabled(false) + nmbrImmunity:SetEditable(true) + end + end + end + SelectedGroup:SetValue("user") + SelectedGroup:OnSelect(1, "user") + + AddPriv = vgui.Create("DButton", frame) + AddPriv:SetPos(310, 55) + AddPriv:SetSize(25, 25) + AddPriv:SetText(">") + AddPriv.DoClick = function() + for _, v in ipairs(Privileges:GetSelected()) do + local priv = v.Columns[1]:GetValue() + RunConsoleCommand("FAdmin", "AddPrivilege", SelectedGroup:GetValue(), priv) + SelectedPrivs:AddLine(priv) + Privileges:RemoveLine(v.m_iID) + end + end + + RemPriv = vgui.Create("DButton", frame) + RemPriv:SetPos(310, 85) + RemPriv:SetSize(25, 25) + RemPriv:SetText("<") + RemPriv.DoClick = function() + for _, v in ipairs(SelectedPrivs:GetSelected()) do + local priv = v.Columns[1]:GetValue() + if SelectedGroup:GetValue() == LocalPlayer():GetUserGroup() and priv == "ManagePrivileges" then + return Derma_Message("You shouldn't be removing ManagePrivileges. It will make you unable to edit the groups. This is preventing you from locking yourself out of the system.", "Clever move.") + end + RunConsoleCommand("FAdmin", "RemovePrivilege", SelectedGroup:GetValue(), priv) + Privileges:AddLine(priv) + SelectedPrivs:RemoveLine(v.m_iID) + end + end + + lblImmunity = vgui.Create("DLabel", frame) + lblImmunity:SetPos(340, 30) + lblImmunity:SetText("Immunity number (higher is more immune)") + lblImmunity:SizeToContents() + + nmbrImmunity = vgui.Create("DTextEntry", frame) + nmbrImmunity:SetPos(545, 28) + nmbrImmunity:SetWide(90) + nmbrImmunity:SetNumeric(true) + nmbrImmunity:SetText(FAdmin.Access.Groups.user.immunity) + nmbrImmunity:SetDisabled(true) + nmbrImmunity:SetEditable(false) + nmbrImmunity.OnEnter = function(self) RunConsoleCommand("FAdmin", "SetImmunity", SelectedGroup:GetValue(), self:GetValue()) end +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/sh_shared.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/sh_shared.lua new file mode 100644 index 0000000..ce33000 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/sh_shared.lua @@ -0,0 +1,273 @@ +CreateConVar("_FAdmin_immunity", 1, {FCVAR_GAMEDLL, FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE}) + +FAdmin.Access = FAdmin.Access or {} +FAdmin.Access.ADMIN = {"user", "admin", "superadmin"} +FAdmin.Access.ADMIN[0] = "user" + +FAdmin.Access.Groups = FAdmin.Access.Groups or {} +FAdmin.Access.Privileges = FAdmin.Access.Privileges or {} + +function FAdmin.Access.AddGroup(name, admin_access --[[0 = not admin, 1 = admin, 2 = superadmin]], privs, immunity, fromCAMI, CAMIsrc) + FAdmin.Access.Groups[name] = FAdmin.Access.Groups[name] or {ADMIN = admin_access, PRIVS = privs or {}, immunity = immunity} + + --Make sure things that come from CAMI come with a CAMIsrc + assert((fromCAMI and CAMIsrc ~= nil) or ((not fromCAMI) and CAMIsrc == nil)) + --If the CAMIsrc is a string, save it, otherwise save an empty string + if not isstring(CAMIsrc) then + CAMIsrc = "" + end + + -- Register custom usergroups with CAMI + if name ~= "user" and name ~= "admin" and name ~= "superadmin" and not fromCAMI then + CAMI.RegisterUsergroup({ + Name = name, + Inherits = FAdmin.Access.ADMIN[admin_access] + }, "FAdmin") + end + + -- Add newly created privileges on server reload + for p, _ in pairs(privs or {}) do + FAdmin.Access.Groups[name].PRIVS[p] = true + end + + if not SERVER then return end + + MySQLite.queryValue("SELECT COUNT(*) FROM FADMIN_GROUPS WHERE NAME = " .. MySQLite.SQLStr(name) .. ";", function(val) + if tonumber(val or 0) > 0 then return end + + MySQLite.query("REPLACE INTO FADMIN_GROUPS VALUES(" .. MySQLite.SQLStr(name) .. ", " .. tonumber(admin_access) .. ");", function() + for priv, _ in pairs(privs or {}) do + MySQLite.query("REPLACE INTO FADMIN_PRIVILEGES VALUES(" .. MySQLite.SQLStr(name) .. ", " .. MySQLite.SQLStr(priv) .. ");") + end + if fromCAMI then + MySQLite.query("REPLACE INTO FADMIN_GROUPS_SRC VALUES(" .. MySQLite.SQLStr(name) .. ", " .. MySQLite.SQLStr(CAMIsrc) .. ");") + end + end) + end) + + if immunity then + MySQLite.query("REPLACE INTO FAdmin_Immunity VALUES(" .. MySQLite.SQLStr(name) .. ", " .. tonumber(immunity) .. ");") + end + + if FAdmin.Access.SendGroups and privs then + for _, v in ipairs(player.GetAll()) do + FAdmin.Access.SendGroups(v) + end + end +end + +function FAdmin.Access.OnUsergroupRegistered(usergroup, source) + -- Don't re-add usergroups coming from FAdmin itself + if source == "FAdmin" then return end + + local inheritRoot = CAMI.InheritanceRoot(usergroup.Inherits) + local admin_access = table.KeyFromValue(FAdmin.Access.ADMIN, inheritRoot) or 1 + + -- Add groups registered to CAMI to FAdmin. Assume privileges from either the usergroup it inherits or its inheritance root. + -- Immunity is unknown and can be set by the user later. FAdmin immunity only applies to FAdmin anyway. + local parent = FAdmin.Access.Groups[usergroup.Inherits] or FAdmin.Access.Groups[inheritRoot] or {} + FAdmin.Access.AddGroup(usergroup.Name, admin_access - 1, table.Copy(parent.PRIVS) or {}, parent.immunity or 10, true, source) +end + + +function FAdmin.Access.OnUsergroupUnregistered(usergroup, source) + if table.HasValue({"superadmin", "admin", "user", "noaccess"}, usergroup.Name) then return end + + FAdmin.Access.Groups[usergroup.Name] = nil + + if not SERVER then return end + + MySQLite.query("DELETE FROM FADMIN_GROUPS WHERE NAME = " .. MySQLite.SQLStr(usergroup.Name) .. ";") + + for _, v in ipairs(player.GetAll()) do + FAdmin.Access.SendGroups(v) + end +end + +function FAdmin.Access.RemoveGroup(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if not args[1] then return false end + + local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()] + + if not FAdmin.Access.Groups[args[1]] or table.HasValue({"superadmin", "admin", "user"}, string.lower(args[1])) then return true, args[1] end + + -- Setting a group with a higher rank than one's own + if (not plyGroup or FAdmin.Access.Groups[args[1]].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to remove usergroups with a higher rank than your own") + return false + end + + CAMI.UnregisterUsergroup(args[1], "FAdmin") + + FAdmin.Messages.SendMessage(ply, 4, "Group succesfully removed") +end + +local PLAYER = FindMetaTable("Player") + +local oldplyIsAdmin = PLAYER.IsAdmin +function PLAYER:IsAdmin(...) + local usergroup = self:GetUserGroup() + + if not FAdmin or not FAdmin.Access or not FAdmin.Access.Groups or not FAdmin.Access.Groups[usergroup] then return oldplyIsAdmin(self, ...) or game.SinglePlayer() end + + if (FAdmin.Access.Groups[usergroup] and FAdmin.Access.Groups[usergroup].ADMIN >= 1 --[[1 = admin]]) or (self.IsListenServerHost and self:IsListenServerHost()) then + return true + end + + if CLIENT and tonumber(self:FAdmin_GetGlobal("FAdmin_admin")) and self:FAdmin_GetGlobal("FAdmin_admin") >= 1 then return true end + + return oldplyIsAdmin(self, ...) or game.SinglePlayer() +end + +local oldplyIsSuperAdmin = PLAYER.IsSuperAdmin +function PLAYER:IsSuperAdmin(...) + local usergroup = self:GetUserGroup() + if not FAdmin or not FAdmin.Access or not FAdmin.Access.Groups or not FAdmin.Access.Groups[usergroup] then return oldplyIsSuperAdmin(self, ...) or game.SinglePlayer() end + if (FAdmin.Access.Groups[usergroup] and FAdmin.Access.Groups[usergroup].ADMIN >= 2 --[[2 = superadmin]]) or (self.IsListenServerHost and self:IsListenServerHost()) then + return true + end + if CLIENT and tonumber(self:FAdmin_GetGlobal("FAdmin_admin")) and self:FAdmin_GetGlobal("FAdmin_admin") >= 2 then return true end + return oldplyIsSuperAdmin(self, ...) or game.SinglePlayer() +end + +--Privileges +function FAdmin.Access.AddPrivilege(Name, admin_access) + FAdmin.Access.Privileges[Name] = admin_access +end + +hook.Add("CAMI.OnPrivilegeRegistered", "FAdmin", function(privilege) + FAdmin.Access.AddPrivilege(privilege.Name, table.KeyFromValue(FAdmin.Access.ADMIN, CAMI.InheritanceRoot(privilege.MinAccess)) or 3) + + -- Register privilege and add to respective usergroups + if SERVER then FAdmin.Access.RegisterCAMIPrivilege(privilege) end +end) + +for _, camipriv in pairs(CAMI.GetPrivileges()) do + FAdmin.Access.AddPrivilege(camipriv.Name, table.KeyFromValue(FAdmin.Access.ADMIN, CAMI.InheritanceRoot(camipriv.MinAccess)) or 3) + -- Register if the database has already loaded + if SERVER and FAdmin.Access.RegisterCAMIPrivilege then FAdmin.Access.RegisterCAMIPrivilege(camipriv) end +end + +hook.Add("CAMI.OnPrivilegeUnregistered", "FAdmin", function(privilege) + FAdmin.Access.Privileges[privilege.Name] = nil +end) + +function FAdmin.Access.PlayerIsHost(ply) + return ply:EntIndex() == 0 or game.SinglePlayer() or (ply.IsListenServerHost and ply:IsListenServerHost()) +end + +function FAdmin.Access.PlayerHasPrivilege(ply, priv, target, ignoreImmunity) + -- This is the server console + if FAdmin.Access.PlayerIsHost(ply) then return true end + -- Privilege does not exist + if not FAdmin.Access.Privileges[priv] then return ply:IsAdmin() end + + local Usergroup = ply:GetUserGroup() + + local canTarget = hook.Call("FAdmin_CanTarget", nil, ply, priv, target) + if canTarget ~= nil then + return canTarget + end + + if FAdmin.GlobalSetting.Immunity and + not ignoreImmunity and + not isstring(target) and IsValid(target) and target ~= ply and + FAdmin.Access.Groups[Usergroup] and FAdmin.Access.Groups[target:GetUserGroup()] and + FAdmin.Access.Groups[Usergroup].immunity and FAdmin.Access.Groups[target:GetUserGroup()].immunity and + FAdmin.Access.Groups[target:GetUserGroup()].immunity >= FAdmin.Access.Groups[Usergroup].immunity then + return false + end + + -- Defer answer when usergroup is unknown + if not FAdmin.Access.Groups[Usergroup] then return end + + if FAdmin.Access.Groups[Usergroup].PRIVS[priv] then + return true + end + + if CLIENT and ply.FADMIN_PRIVS and ply.FADMIN_PRIVS[priv] then return true end + + return false +end + +hook.Add("CAMI.PlayerHasAccess", "FAdmin", function(actor, privilegeName, callback, target, extraInfo) + -- FAdmin doesn't know. Defer answer. + if not FAdmin.Access.Privileges[privilegeName] then return end + + local res = FAdmin.Access.PlayerHasPrivilege(actor, privilegeName, target, extraInfo and extraInfo.IgnoreImmunity) + + -- Defer again + if res == nil then return end + + -- Publish the answer + callback(res, "FAdmin") + + -- FAdmin knows the answer. Prevent other hooks from running. + return true +end) + +hook.Add("CAMI.SteamIDHasAccess", "FAdmin", function(actorSteam, privilegeName, callback, targetSteam, extraInfo) + -- The client just doesn't know + if CLIENT then return end + + if not targetSteam or extraInfo and extraInfo.IgnoreImmunity then + MySQLite.query(string.format( + [[SELECT COUNT(*) AS c + FROM FAdmin_PlayerGroup l + JOIN FADMIN_PRIVILEGES r ON l.groupname = r.NAME + WHERE l.steamid = %s AND r.PRIVILEGE = %s]], + MySQLite.SQLStr(actorSteam), + MySQLite.SQLStr(privilegeName) + ), function(res) callback(tonumber(res[1].c) > 0) end) + + return true + end + + MySQLite.query(string.format( + [[SELECT ll.i AND rr.c AS res + FROM (SELECT li.immunity >= ri.immunity AS i + FROM FAdmin_PlayerGroup lg + JOIN FAdmin_Immunity li ON lg.groupname = li.groupname + JOIN FAdmin_PlayerGroup rg + JOIN FAdmin_Immunity ri ON rg.groupname = ri.groupname + WHERE lg.steamid = %s AND rg.steamid = %s) AS ll + JOIN (SELECT COUNT(*) AS c + FROM FAdmin_PlayerGroup l + JOIN FADMIN_PRIVILEGES r ON l.groupname = r.NAME + WHERE l.steamid = %s AND r.PRIVILEGE = %s) AS rr]], + MySQLite.SQLStr(actorSteam), + MySQLite.SQLStr(targetSteam), + MySQLite.SQLStr(actorSteam), + MySQLite.SQLStr(privilegeName) + ), function(res) callback(res and res[1] and tobool(res[1].res) or false) end) + + return true +end) + +FAdmin.StartHooks["AccessFunctions"] = function() + FAdmin.Messages.RegisterNotification{ + name = "setaccess", + hasTarget = true, + message = {"instigator", " set the usergroup of ", "targets", " to ", "extraInfo.1"}, + receivers = "everyone", + writeExtraInfo = function(i) net.WriteString(i[1]) end, + readExtraInfo = function() return {net.ReadString()} end, + extraInfoColors = {Color(255, 102, 0)} + } + + FAdmin.Access.AddPrivilege("SetAccess", 3) -- AddPrivilege is shared, run on both client and server + FAdmin.Access.AddPrivilege("ManagePrivileges", 3) + FAdmin.Access.AddPrivilege("ManageGroups", 3) + FAdmin.Access.AddPrivilege("SeeAdmins", 1) + FAdmin.Commands.AddCommand("RemoveGroup", FAdmin.Access.RemoveGroup) + + FAdmin.Commands.AddCommand("Admins", function(ply) + if not FAdmin.Access.PlayerHasPrivilege(ply, "SeeAdmins") then return false end + for _, v in ipairs(player.GetAll()) do + ply:PrintMessage(HUD_PRINTCONSOLE, v:Nick() .. "\t|\t" .. v:GetUserGroup()) + end + return true + end + ) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/sv_init.lua new file mode 100644 index 0000000..df5b137 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/access/sv_init.lua @@ -0,0 +1,449 @@ +--Immunity +cvars.AddChangeCallback("_FAdmin_immunity", function(Cvar, Previous, New) + FAdmin.SetGlobalSetting("Immunity", (tonumber(New) == 1 and true) or false) + FAdmin.SaveSetting("_FAdmin_immunity", tonumber(New)) +end) + +hook.Add("DatabaseInitialized", "InitializeFAdminGroups", function() + MySQLite.query("CREATE TABLE IF NOT EXISTS FADMIN_GROUPS(NAME VARCHAR(40) NOT NULL PRIMARY KEY, ADMIN_ACCESS INTEGER NOT NULL);") + MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_PlayerGroup(steamid VARCHAR(40) NOT NULL, groupname VARCHAR(40) NOT NULL, PRIMARY KEY(steamid));") + MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_Immunity(groupname VARCHAR(40) NOT NULL, immunity INTEGER NOT NULL, PRIMARY KEY(groupname));") + MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_CAMIPrivileges(privname VARCHAR(255) NOT NULL PRIMARY KEY);") + MySQLite.query("CREATE TABLE IF NOT EXISTS FADMIN_GROUPS_SRC(NAME VARCHAR(40) NOT NULL PRIMARY KEY REFERENCES FADMIN_GROUPS(NAME) ON DELETE CASCADE, SRC VARCHAR(40));") + MySQLite.query([[CREATE TABLE IF NOT EXISTS FADMIN_PRIVILEGES( + NAME VARCHAR(40), + PRIVILEGE VARCHAR(100), + PRIMARY KEY(NAME, PRIVILEGE), + FOREIGN KEY(NAME) REFERENCES FADMIN_GROUPS(NAME) + ON UPDATE CASCADE + ON DELETE CASCADE + );]], function() + + -- Remove SetAccess workaround + MySQLite.query([[DELETE FROM FADMIN_PRIVILEGES WHERE NAME = "user" AND PRIVILEGE = "SetAccess";]]) + + MySQLite.query("SELECT g.NAME, g.ADMIN_ACCESS, p.PRIVILEGE, i.immunity, s.src FROM FADMIN_GROUPS g LEFT OUTER JOIN FADMIN_PRIVILEGES p ON g.NAME = p.NAME LEFT OUTER JOIN FAdmin_Immunity i ON g.NAME = i.groupname LEFT OUTER JOIN FADMIN_GROUPS_SRC s ON g.NAME = s.NAME;", function(data) + if not data then return end + + for _, v in pairs(data) do + FAdmin.Access.Groups[v.NAME] = FAdmin.Access.Groups[v.NAME] or + {ADMIN = tonumber(v.ADMIN_ACCESS), PRIVS = {}} + + if v.PRIVILEGE and v.PRIVILEGE ~= "NULL" then + FAdmin.Access.Groups[v.NAME].PRIVS[v.PRIVILEGE] = true + end + + if v.immunity and v.immunity ~= "NULL" then + FAdmin.Access.Groups[v.NAME].immunity = tonumber(v.immunity) + end + + if CAMI.GetUsergroup(v.NAME) then continue end + + CAMI.RegisterUsergroup({ + Name = v.NAME, + Inherits = FAdmin.Access.ADMIN[v.ADMIN_ACCESS] or "user" + }, v.SRC) + end + + -- Send groups to early joiners and listen server hosts + for _, v in ipairs(player.GetAll()) do + FAdmin.Access.SendGroups(v) + end + + -- See if there are any CAMI usergroups that FAdmin doesn't know about yet. + -- FAdmin doesn't start listening immediately because the database might not have initialised. + -- Besides, other admin mods might add usergroups before FAdmin's Lua files are even run + for _, v in pairs(CAMI.GetUsergroups()) do + if FAdmin.Access.Groups[v.Name] then continue end + + FAdmin.Access.OnUsergroupRegistered(v,"") + end + + -- Start listening for CAMI usergroup registrations. + hook.Add("CAMI.OnUsergroupRegistered", "FAdmin", FAdmin.Access.OnUsergroupRegistered) + hook.Add("CAMI.OnUsergroupUnregistered", "FAdmin", FAdmin.Access.OnUsergroupUnregistered) + + FAdmin.Access.RegisterCAMIPrivileges() + end) + + local function createGroups(privs) + FAdmin.Access.AddGroup("superadmin", 2, privs.superadmin, 100) + FAdmin.Access.AddGroup("admin", 1, privs.admin, 50) + FAdmin.Access.AddGroup("user", 0, privs.user, 10) + FAdmin.Access.AddGroup("noaccess", 0, privs.noaccess, 0) + end + + MySQLite.query("SELECT DISTINCT PRIVILEGE FROM FADMIN_PRIVILEGES;", function(privTbl) + local privs = {} + local hasPrivs = {"noaccess", "user", "admin", "superadmin"} + + -- No privileges registered to anyone. Reset everything + if not privTbl or table.IsEmpty(privTbl) then + for priv, access in pairs(FAdmin.Access.Privileges) do + for i = access + 1, #hasPrivs, 1 do + privs[hasPrivs[i]] = privs[hasPrivs[i]] or {} + privs[hasPrivs[i]][priv] = true + end + end + + createGroups(privs) + + return + end + + -- Check for newly created privileges and assign them to the default usergroups + -- No privilege can be revoke from every group + local privSet = {} + for _, priv in ipairs(privTbl) do + privSet[priv.PRIVILEGE] = true + end + + for priv, access in pairs(FAdmin.Access.Privileges) do + if privSet[priv] then continue end + + for i = access + 1, #hasPrivs do + MySQLite.query(("REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);"):format(MySQLite.SQLStr(hasPrivs[i]), MySQLite.SQLStr(priv))) + end + end + + createGroups(privs) + end) + end) +end) + +-- Assign a privilege to its respective usergroups when they are seen for the first time +function FAdmin.Access.RegisterCAMIPrivilege(priv) + -- Privileges haven't been loaded yet or has already been seen + if not FAdmin.CAMIPrivs or FAdmin.CAMIPrivs[priv.Name] then return end + + FAdmin.CAMIPrivs[priv.Name] = true + + for groupName, groupdata in pairs(FAdmin.Access.Groups) do + if FAdmin.Access.Privileges[priv.Name] - 1 > groupdata.ADMIN then continue end + groupdata.PRIVS[priv.Name] = true + + MySQLite.query(string.format([[REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);]], MySQLite.SQLStr(groupName), MySQLite.SQLStr(priv.Name))) + end + + MySQLite.query(string.format([[REPLACE INTO FAdmin_CAMIPrivileges VALUES(%s);]], MySQLite.SQLStr(priv.Name))) +end + +-- Assign privileges to their respective usergroups when they are seen for the first time +function FAdmin.Access.RegisterCAMIPrivileges() + MySQLite.query([[SELECT privname FROM FAdmin_CAMIPrivileges]], function(data) + FAdmin.CAMIPrivs = {} + + for _, row in ipairs(data or {}) do + FAdmin.CAMIPrivs[row.privname] = true + end + + + for privName, _ in pairs(CAMI.GetPrivileges()) do + if FAdmin.CAMIPrivs[privName] then continue end + FAdmin.CAMIPrivs[privName] = true + + for groupName, groupdata in pairs(FAdmin.Access.Groups) do + if FAdmin.Access.Privileges[privName] - 1 > groupdata.ADMIN then continue end + groupdata.PRIVS[privName] = true + + MySQLite.query(string.format([[REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);]], MySQLite.SQLStr(groupName), MySQLite.SQLStr(privName))) + end + + MySQLite.query(string.format([[REPLACE INTO FAdmin_CAMIPrivileges VALUES(%s);]], MySQLite.SQLStr(privName))) + end + end) +end + +function FAdmin.Access.PlayerSetGroup(ply, group) + if not FAdmin.Access.Groups[group] then return end + ply = isstring(ply) and FAdmin.FindPlayer(ply) and FAdmin.FindPlayer(ply)[1] or ply + + if not isstring(ply) and IsValid(ply) then + ply:SetUserGroup(group) + end +end + +hook.Remove("PlayerInitialSpawn", "PlayerAuthSpawn") -- Remove Garry's usergroup setter. + +-- Update the database only when an end users indicates that a player's usergroup is to be changed. +hook.Add("CAMI.PlayerUsergroupChanged", "FAdmin", function(ply, old, new, source) + MySQLite.query("REPLACE INTO FAdmin_PlayerGroup VALUES(" .. MySQLite.SQLStr(ply:SteamID()) .. ", " .. MySQLite.SQLStr(new) .. ");") +end) + +hook.Add("CAMI.SteamIDUsergroupChanged", "FAdmin", function(steamId, old, new, source) + MySQLite.query("REPLACE INTO FAdmin_PlayerGroup VALUES(" .. MySQLite.SQLStr(steamId) .. ", " .. MySQLite.SQLStr(new) .. ");") +end) + +function FAdmin.Access.SetRoot(ply, cmd, args) -- FAdmin setroot player. Sets the player to superadmin + if not FAdmin.Access.PlayerHasPrivilege(ply, "SetAccess") then + FAdmin.Messages.SendMessage(ply, 5, "No access!") + FAdmin.Messages.SendMessage(ply, 5, "Please use RCon to set yourself to superadmin if you are the owner of the server") + return false + end + + local group = FAdmin.Access.Groups["superadmin"] + local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()] + + -- Setting a group with a higher rank than one's own + if (not plyGroup or group.immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own") + FAdmin.Messages.SendMessage(ply, 5, "Please use RCon to set yourself to superadmin if you are the owner of the server") + return false + end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + for _, target in ipairs(targets) do + if not IsValid(target) then continue end + + local target_previous_group = target:GetUserGroup() + FAdmin.Access.PlayerSetGroup(target, "superadmin") + + -- An end user changed the usergroup. Register with CAMI + CAMI.SignalUserGroupChanged(target, target_previous_group, "superadmin", "FAdmin") + + FAdmin.Messages.SendMessage(ply, 2, "User set to superadmin!") + end + + FAdmin.Messages.FireNotification("setaccess", ply, targets, {"superadmin"}) + return true, targets, "superadmin" +end + +-- AddGroup +local function AddGroup(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + local admin = tonumber(args[2]) + if not args[1] or not admin then FAdmin.Messages.SendMessage(ply, 5, "Incorrect arguments!") return false end + local privs = {} + + for priv, am in SortedPairs(FAdmin.Access.Privileges) do + -- The user cannot create groups with privileges they don't have + if not FAdmin.Access.PlayerHasPrivilege(ply, priv) then continue end + if am <= admin + 1 then privs[priv] = true end + end + + local immunity = FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity + + local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()] + + if (not plyGroup or immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to create usergroups with a higher rank than your own") + return false + end + + FAdmin.Access.AddGroup(args[1], admin, privs, immunity) -- Add new group + FAdmin.Messages.SendMessage(ply, 4, "Group created") + FAdmin.Access.SendGroups() + + return true, args[1] +end + +local function AddPrivilege(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "ManagePrivileges") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + local group, priv = args[1], args[2] + + if not FAdmin.Access.Groups[group] or not FAdmin.Access.Privileges[priv] then + FAdmin.Messages.SendMessage(ply, 5, "Invalid arguments") + return false + end + + -- The player cannot add privileges that they themselves do not have + if not FAdmin.Access.PlayerHasPrivilege(ply, priv) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign privileges that you don't have yourself") + return false + end + + local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()] + + -- Setting a group with a higher rank than one's own + if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to manage the privileges of a usergroup with a higher rank than your own") + return false + end + + FAdmin.Access.Groups[group].PRIVS[priv] = true + + MySQLite.query("REPLACE INTO FADMIN_PRIVILEGES VALUES(" .. MySQLite.SQLStr(group) .. ", " .. MySQLite.SQLStr(priv) .. ");") + SendUserMessage("FAdmin_AddPriv", player.GetAll(), group, priv) + FAdmin.Messages.SendMessage(ply, 4, "Privilege Added!") + + return true, group, priv +end + +local function RemovePrivilege(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "ManagePrivileges") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + local group, priv = args[1], args[2] + if not FAdmin.Access.Groups[group] or not FAdmin.Access.Privileges[priv] then + FAdmin.Messages.SendMessage(ply, 5, "Invalid arguments") + return false + end + + local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()] + + -- Setting a group with a higher rank than one's own + if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to manage the privileges of a usergroup with a higher rank than your own") + return false + end + + FAdmin.Access.Groups[group].PRIVS[priv] = nil + + MySQLite.query("DELETE FROM FADMIN_PRIVILEGES WHERE NAME = " .. MySQLite.SQLStr(group) .. " AND PRIVILEGE = " .. MySQLite.SQLStr(priv) .. ";") + SendUserMessage("FAdmin_RemovePriv", player.GetAll(), group, priv) + FAdmin.Messages.SendMessage(ply, 4, "Privilege Removed!") + + return true, group, priv +end + +function FAdmin.Access.SendGroups(ply) + if not FAdmin.Access.Groups then return end + + net.Start("FADMIN_SendGroups") + net.WriteTable(FAdmin.Access.Groups) + net.Send(IsValid(ply) and ply or player.GetAll()) +end + +-- FAdmin SetAccess [new_groupadmin, new_groupprivs] +function FAdmin.Access.SetAccess(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "SetAccess") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + local targets = FAdmin.FindPlayer(args[1]) + local admin = tonumber(args[3]) + local group = FAdmin.Access.Groups[args[2]] + local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()] + + if not args[2] or not group and not admin then + FAdmin.Messages.SendMessage(ply, 1, "Group not found") + return false + elseif args[2] and not group and admin then + local privs = {} + for priv, am in SortedPairs(FAdmin.Access.Privileges) do + if am <= admin + 1 then privs[priv] = true end + end + + local immunity = FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity + -- Creating and setting a group with a higher rank than one's own + if (not plyGroup or immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own") + return false + end + + FAdmin.Access.AddGroup(args[2], tonumber(args[3]), privs, immunity) -- Add new group + FAdmin.Messages.SendMessage(ply, 4, "Group created") + FAdmin.Access.SendGroups() + end + + -- Setting a group with a higher rank than one's own + if group and (not plyGroup or group.immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own") + return false + end + + if not targets and (string.find(args[1], "^STEAM_[0-9]:[01]:[0-9]+$") or args[1] == "BOT" or (string.find(args[1], "STEAM_") and #args == 6)) then + local target, groupname = args[1], args[2] + -- The console splits arguments on colons. Very annoying. + if args[1] == "STEAM_0" then + target = table.concat(args, "", 1, 5) + groupname = args[6] + end + FAdmin.Access.PlayerSetGroup(target, groupname) + + MySQLite.queryValue(string.format("SELECT groupname FROM FAdmin_PlayerGroup WHERE steamid = %s", MySQLite.SQLStr(target)), function(val) + CAMI.SignalSteamIDUserGroupChanged(target, val or "user", groupname, "FAdmin") + end) + FAdmin.Messages.SendMessage(ply, 4, "User access set!") + return true, target, groupname + elseif not targets then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + for _, target in ipairs(targets) do + if not IsValid(target) then continue end + + local target_previous_group = target:GetUserGroup() + FAdmin.Access.PlayerSetGroup(target, args[2]) + + -- An end user changed the usergroup. Register with CAMI + CAMI.SignalUserGroupChanged(target, target_previous_group, args[2], "FAdmin") + end + + FAdmin.Messages.SendMessage(ply, 4, "User access set!") + FAdmin.Messages.FireNotification("setaccess", ply, targets, {args[2]}) + return true, targets, args[2] +end + +--hooks and stuff + +hook.Add("PlayerInitialSpawn", "FAdmin_SetAccess", function(ply) + MySQLite.queryValue("SELECT groupname FROM FAdmin_PlayerGroup WHERE steamid = " .. MySQLite.SQLStr(ply:SteamID()) .. ";", function(Group) + if not Group then return end + ply:SetUserGroup(Group) + + if FAdmin.Access.Groups[Group] then + ply:FAdmin_SetGlobal("FAdmin_admin", FAdmin.Access.Groups[Group].ADMIN_ACCESS) + end + end, function(err) ErrorNoHalt(err) MsgN() end) + FAdmin.Access.SendGroups(ply) +end) + +local function toggleImmunity(ply, cmd, args) + -- ManageGroups privilege because they can handle immunity settings + if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + if not args[1] then FAdmin.Messages.SendMessage(ply, 5, "Invalid argument!") return false end + RunConsoleCommand("_FAdmin_immunity", args[1]) + local OnOff = (tonumber(args[1]) == 1 and "on") or "off" + FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned " .. OnOff .. " admin immunity!", "Admin immunity has been turned " .. OnOff, "Turned admin immunity " .. OnOff) + + return true, OnOff +end + + +local function setImmunity(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + local group, immunity = args[1], tonumber(args[2]) + + if not FAdmin.Access.Groups[group] or not immunity then return false end + + local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()] + + -- Setting a group with a higher rank than one's own + if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to change the immunity of a group with a higher rank than your") + return false + end + + if immunity > plyGroup.immunity and not FAdmin.Access.PlayerIsHost(ply) then + FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to set the immunity to any value higher than your own group's immunity") + return false + end + + FAdmin.Access.Groups[group].immunity = immunity + MySQLite.query("REPLACE INTO FAdmin_Immunity VALUES(" .. MySQLite.SQLStr(group) .. ", " .. tonumber(immunity) .. ");") + + FAdmin.Access.SendGroups(ply) + + return true, group, immunity +end + +FAdmin.StartHooks["Access"] = function() --Run all functions that depend on other plugins + FAdmin.Commands.AddCommand("setroot", FAdmin.Access.SetRoot) + FAdmin.Commands.AddCommand("setaccess", FAdmin.Access.SetAccess) + + FAdmin.Commands.AddCommand("AddGroup", AddGroup) + + FAdmin.Commands.AddCommand("AddPrivilege", AddPrivilege) + FAdmin.Commands.AddCommand("RemovePrivilege", RemovePrivilege) + + FAdmin.Commands.AddCommand("immunity", toggleImmunity) + FAdmin.Commands.AddCommand("SetImmunity", setImmunity) + + FAdmin.SetGlobalSetting("Immunity", GetConVar("_FAdmin_immunity"):GetBool()) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/adminchat/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/adminchat/cl_init.lua new file mode 100644 index 0000000..89520c2 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/adminchat/cl_init.lua @@ -0,0 +1,17 @@ + +net.Receive("FAdmin_ReceiveAdminMessage", function(len) + local FromPly = net.ReadEntity() + local Text = net.ReadString() + local Team = FromPly:IsPlayer() and FromPly:Team() or 1 + local Nick = FromPly:IsPlayer() and FromPly:Nick() or "Console" + local prefix = (FAdmin.Access.PlayerHasPrivilege(FromPly, "AdminChat") or FromPly:IsAdmin()) and "[Admin Chat] " or "[To admins] " + + chat.AddNonParsedText(Color(255, 0, 0, 255), prefix, team.GetColor(Team), Nick .. ": ", color_white, Text) +end) + +FAdmin.StartHooks["Chatting"] = function() + FAdmin.Commands.AddCommand("adminhelp", nil, "") + FAdmin.Commands.AddCommand("//", nil, "") + + FAdmin.Access.AddPrivilege("AdminChat", 2) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/adminchat/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/adminchat/sv_init.lua new file mode 100644 index 0000000..21cdbae --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/adminchat/sv_init.lua @@ -0,0 +1,28 @@ +util.AddNetworkString("FAdmin_ReceiveAdminMessage") +local function ToAdmins(ply, cmd, args) + if not args[1] then return false end + + local text = table.concat(args, " ") + local send = {} + + if IsValid(ply) then table.insert(send, ply) end + for _, v in ipairs(player.GetAll()) do + if FAdmin.Access.PlayerHasPrivilege(v, "AdminChat") or v:IsAdmin() then + table.insert(send, v) + end + end + + net.Start("FAdmin_ReceiveAdminMessage") + net.WriteEntity(ply) + net.WriteString(text) + net.Send(send) + + return true, text +end + +FAdmin.StartHooks["Chatting"] = function() + FAdmin.Commands.AddCommand("adminhelp", ToAdmins) + FAdmin.Commands.AddCommand("//", ToAdmins) + + FAdmin.Access.AddPrivilege("AdminChat", 2) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_changelevelgui.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_changelevelgui.lua new file mode 100644 index 0000000..f8ba46f --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_changelevelgui.lua @@ -0,0 +1,87 @@ +local PANEL = {} + +AccessorFunc(PANEL, "gamemodeList", "GamemodeList") +AccessorFunc(PANEL, "mapList", "MapList") + +function PANEL:Init() + self:SetMouseInputEnabled(true) + self:SetKeyboardInputEnabled(false) + + self:SetDeleteOnClose(false) + + self:SetTitle("Change level") + self:SetSize(630, ScrH() * 0.8) + + self.gamemodeList = {} + self.mapList = {} + + self.catList = vgui.Create("DCategoryList", self) + self.catList:Dock(FILL) + + self.topPanel = vgui.Create("DPanel", self) + self.topPanel:SetPaintBackground(false) + self.topPanel:DockMargin(0, 0, 0, 4) + self.topPanel:Dock(TOP) + self.gmLabel = vgui.Create("DLabel", self.topPanel) + self.gmLabel:SetText("Gamemode:") + self.gmLabel:Dock(LEFT) + self.gmComboBox = vgui.Create("DComboBox", self.topPanel) + self.gmComboBox:Dock(FILL) + self.gmComboBox:SetValue("(current)") + + self.bottomPanel = vgui.Create("DPanel", self) + self.bottomPanel:SetPaintBackground(false) + self.bottomPanel:DockMargin(0, 4, 0, 0) + self.bottomPanel:Dock(BOTTOM) + self.changeButton = vgui.Create("DButton", self.bottomPanel) + self.changeButton:SetText("Change level") + self.changeButton:Dock(RIGHT) + self.changeButton:SetWidth(100) + self.changeButton:SetEnabled(false) + self.changeButton.DoClick = function() + if not IsValid(self.selectedIconPanel) then return end + local _,gmName = self.gmComboBox:GetSelected() + local mapName = self.selectedIconPanel:GetText() + RunConsoleCommand("_FAdmin", "Changelevel", gmName and gmName or mapName, gmName and mapName) + end +end + +function PANEL:Refresh() + for _, gmInfo in ipairs(self:GetGamemodeList()) do + self.gmComboBox:AddChoice(gmInfo.title, gmInfo.name) + end + self.gmComboBox:SetValue("(current)") + + for catName, maps in pairs(self:GetMapList()) do + local cat = self.catList:Add(catName) + local iconLayout = vgui.Create("DIconLayout") + iconLayout:SetSpaceX(5) + iconLayout:SetSpaceY(5) + for _, map in ipairs(maps) do + local icon = iconLayout:Add("FAdmin_MapIcon") + icon:SetText(map) + icon:SetDark(true) + local mat = Material("maps/thumb/" .. map .. ".png") + if mat:IsError() then mat = Material("maps/thumb/noicon.png") end + icon:SetMaterial(mat) + local onToggled = icon.OnToggled + icon.OnToggled = function(iconSelf, selected) + onToggled(iconSelf, selected) + if IsValid(self.selectedIconPanel) then + if selected and self.selectedIconPanel ~= iconSelf then + self.selectedIconPanel:Toggle() + elseif not selected and self.selectedIconPanel == iconSelf then + self.selectedIconPanel = nil + self.changeButton:SetEnabled(false) + return + end + end + self.selectedIconPanel = iconSelf + self.changeButton:SetEnabled(true) + end + end + cat:SetContents(iconLayout) + end +end + +vgui.Register("FAdmin_Changelevel", PANEL, "DFrame") diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_init.lua new file mode 100644 index 0000000..a3199f8 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_init.lua @@ -0,0 +1,46 @@ +local mapList = {} +local gamemodeList = {} +net.Receive("FAdmin_ChangelevelInfo", function(len) + mapList = {} + local mapLen = net.ReadUInt(16) + + for i = 1, mapLen, 1 do + local cat = net.ReadString() + mapList[cat] = {} + local catLen = net.ReadUInt(16) + + for j = 1, catLen, 1 do + mapList[cat][j] = net.ReadString() + end + end + + gamemodeList = {} + local gmLen = net.ReadUInt(16) + + for i = 1, gmLen, 1 do + gamemodeList[i] = { + name = net.ReadString(), + title = net.ReadString() + } + end +end) + +local Changelevel +FAdmin.StartHooks["ChangeLevel"] = function() + FAdmin.Access.AddPrivilege("changelevel", 2) + FAdmin.Commands.AddCommand("changelevel", "[gamemode]", "") + + FAdmin.ScoreBoard.Server:AddServerAction("Changelevel", "icon16/world.png", Color(155, 0, 0, 255), function() return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "changelevel") end, + function(ply, button) + local refresh = not Changelevel or table.Count(Changelevel:GetMapList()) ~= table.Count(mapList) + Changelevel = Changelevel or vgui.Create("FAdmin_Changelevel") + if refresh then + Changelevel:SetGamemodeList(gamemodeList) + Changelevel:SetMapList(mapList) + Changelevel:Refresh() + end + Changelevel:SetVisible(true) + Changelevel:Center() + Changelevel:MakePopup() + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_mapicon.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_mapicon.lua new file mode 100644 index 0000000..e26cdcc --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/cl_mapicon.lua @@ -0,0 +1,43 @@ +local PANEL = {} + +function PANEL:Init() + self.BaseClass.Init(self) + self:SetPaintBackground(true) + self:SetIsToggle(true) + self:SetSize(96, 110) +end + +function PANEL:Paint(w, h) + if self.m_bToggle then + surface.SetDrawColor(255, 155, 20, 255) + surface.DrawRect(0, 0, w, h) + end + return false +end + +function PANEL:UpdateColours(skin) + return self:SetTextStyleColor(skin.Colours.Button.Normal) +end + +function PANEL:OnToggled(selected) + self:InvalidateLayout(true) +end + +function PANEL:OnMousePressed(code) + DButton.OnMousePressed(self, code) +end + +function PANEL:OnMouseReleased(code) + DButton.OnMouseReleased(self, code) +end + +function PANEL:PerformLayout() + self.m_Image:SetPos(0, 0) + local w,h = self:GetSize() + h = h - 14 + self.m_Image:SetSize(w, h) + self:SetTextInset(5, w / 2) + DLabel.PerformLayout(self) +end + +vgui.Register("FAdmin_MapIcon", PANEL, "DImageButton") diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/sv_init.lua new file mode 100644 index 0000000..289356f --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/changelevel/sv_init.lua @@ -0,0 +1,266 @@ +util.AddNetworkString("FAdmin_ChangelevelInfo") + +local function ChangeLevel(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "changelevel") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + local map = args[2] or args[1] -- Changelevel gamemode map OR changelevel map + local gameMode = args[2] and args[1] + + if gameMode then + RunConsoleCommand("gamemode", gameMode) + end + + RunConsoleCommand("changelevel", map) + + return true, map, gameMode +end + +local mapNames = {} +local mapPatterns = {} + +local ignorePatterns = { + "^asi-", "^background", "^c[%d]m", "^devtest", "^ep1_background", "^ep2_background", "^mp_coop_", "^sp_a", "^styleguide" +} + +local ignoreMaps = { + -- Prefixes + ["ddd_"] = true, + ["sdk_"] = true, + ["test_"] = true, + ["vst_"] = true, + -- Maps + ["c4a1y"] = true, + ["cp_docks"] = true, + ["cp_parkour"] = true, + ["cp_sequence"] = true, + ["cp_terrace"] = true, + ["cp_test"] = true, + ["credits"] = true, + ["curling_stadium"] = true, + ["d2_coast_02"] = true, + ["d3_c17_02_camera"] = true, + ["duel_"] = true, + ["e1912"] = true, + ["ep1_citadel_00_demo"] = true, + ["ffa_community"] = true, + ["free_"] = true, + ["intro"] = true, + ["lobby"] = true, + ["practice_box"] = true, + ["test"] = true, + ["tut_training"] = true, + ["tutorial_standards"] = true, + ["tutorial_standards_vs"] = true +} + +hook.Add("PlayerInitialSpawn", "FAdmin_ChangelevelInfo", function(ply) + local mapList = {} + local maps = file.Find("maps/*.bsp", "GAME") + + for _, v in ipairs(maps) do + local name = string.lower(string.gsub(v, "%.bsp$", "")) + if ignoreMaps[name] then continue end + + local prefix = string.match(name, "^(.-_)") + if ignoreMaps[prefix] then continue end + + for _, ignore in ipairs(ignorePatterns) do + if string.find(name, ignore) then + goto mapContinue + end + end + + -- Check if the map has a simple name or prefix + local mapCategory = mapNames[name] or mapNames[prefix] + + -- Check if the map has an embedded prefix, or is TTT/Sandbox + if not mapCategory then + for pattern, category in pairs(mapPatterns) do + if string.find(name, pattern) then + mapCategory = category + end + end + end + + -- Throw all uncategorized maps into Other + mapCategory = mapCategory or "Other" + -- Don't show CS:GO maps + if mapCategory == "Counter-Strike" and not file.Exists("maps/" .. name .. ".bsp", "cstrike") then + continue + end + + if not mapList[mapCategory] then + mapList[mapCategory] = {} + end + + table.insert(mapList[mapCategory], name) + ::mapContinue:: + end + + local gamemodeList = engine.GetGamemodes() + net.Start("FAdmin_ChangelevelInfo") + net.WriteUInt(table.Count(mapList), 16) -- 65536 should be enough + for cat, mps in pairs(mapList) do + net.WriteString(cat) + net.WriteUInt(#mps, 16) + for _, map in pairs(mps) do + net.WriteString(map) + end + end + net.WriteUInt(#gamemodeList, 16) + for _, gmInfo in ipairs(gamemodeList) do + net.WriteString(gmInfo.name) + net.WriteString(gmInfo.title) + end + net.Send(ply) +end) + +FAdmin.StartHooks["ChangeLevel"] = function() + FAdmin.Commands.AddCommand("changelevel", ChangeLevel) + + FAdmin.Access.AddPrivilege("changelevel", 2) + + mapNames = {} + mapPatterns = {} + + mapNames["aoc_"] = "Age of Chivalry" + + mapNames["ar_"] = "Counter-Strike" + mapNames["cs_"] = "Counter-Strike" + mapNames["de_"] = "Counter-Strike" + mapNames["es_"] = "Counter-Strike" + mapNames["fy_"] = "Counter-Strike" + mapNames["gd_"] = "Counter-Strike" + mapNames["training1"] = "Counter-Strike" + + mapNames["dod_"] = "Day Of Defeat" + + mapNames["de_dam"] = "DIPRIP" + mapNames["dm_city"] = "DIPRIP" + mapNames["dm_refinery"] = "DIPRIP" + mapNames["dm_supermarket"] = "DIPRIP" + mapNames["dm_village"] = "DIPRIP" + mapNames["ur_city"] = "DIPRIP" + mapNames["ur_refinery"] = "DIPRIP" + mapNames["ur_supermarket"] = "DIPRIP" + mapNames["ur_village"] = "DIPRIP" + + mapNames["dys_"] = "Dystopia" + mapNames["pb_dojo"] = "Dystopia" + mapNames["pb_rooftop"] = "Dystopia" + mapNames["pb_round"] = "Dystopia" + mapNames["pb_urbandome"] = "Dystopia" + mapNames["sav_dojo6"] = "Dystopia" + mapNames["varena"] = "Dystopia" + + mapNames["d1_"] = "Half-Life 2" + mapNames["d2_"] = "Half-Life 2" + mapNames["d3_"] = "Half-Life 2" + + mapNames["dm_"] = "Half-Life 2: Deathmatch" + mapNames["halls3"] = "Half-Life 2: Deathmatch" + + mapNames["ep1_"] = "Half-Life 2: Episode 1" + mapNames["ep2_"] = "Half-Life 2: Episode 2" + mapNames["ep3_"] = "Half-Life 2: Episode 3" + + mapNames["d2_lostcoast"] = "Half-Life 2: Lost Coast" + + mapPatterns["^c[%d]a"] = "Half-Life" + mapPatterns["^t0a"] = "Half-Life" + + mapNames["boot_camp"] = "Half-Life Deathmatch" + mapNames["bounce"] = "Half-Life Deathmatch" + mapNames["crossfire"] = "Half-Life Deathmatch" + mapNames["datacore"] = "Half-Life Deathmatch" + mapNames["frenzy"] = "Half-Life Deathmatch" + mapNames["lambda_bunker"] = "Half-Life Deathmatch" + mapNames["rapidcore"] = "Half-Life Deathmatch" + mapNames["snarkpit"] = "Half-Life Deathmatch" + mapNames["stalkyard"] = "Half-Life Deathmatch" + mapNames["subtransit"] = "Half-Life Deathmatch" + mapNames["undertow"] = "Half-Life Deathmatch" + + mapNames["ins_"] = "Insurgency" + + mapNames["l4d_"] = "Left 4 Dead" + + mapNames["clocktower"] = "Nuclear Dawn" + mapNames["coast"] = "Nuclear Dawn" + mapNames["downtown"] = "Nuclear Dawn" + mapNames["gate"] = "Nuclear Dawn" + mapNames["hydro"] = "Nuclear Dawn" + mapNames["metro"] = "Nuclear Dawn" + mapNames["metro_training"] = "Nuclear Dawn" + mapNames["oasis"] = "Nuclear Dawn" + mapNames["oilfield"] = "Nuclear Dawn" + mapNames["silo"] = "Nuclear Dawn" + mapNames["sk_metro"] = "Nuclear Dawn" + mapNames["training"] = "Nuclear Dawn" + + mapNames["bt_"] = "Pirates, Vikings, & Knights II" + mapNames["lts_"] = "Pirates, Vikings, & Knights II" + mapNames["te_"] = "Pirates, Vikings, & Knights II" + mapNames["tw_"] = "Pirates, Vikings, & Knights II" + + mapNames["escape_"] = "Portal" + mapNames["testchmb_"] = "Portal" + + mapNames["achievement_"] = "Team Fortress 2" + mapNames["arena_"] = "Team Fortress 2" + mapNames["cp_"] = "Team Fortress 2" + mapNames["ctf_"] = "Team Fortress 2" + mapNames["itemtest"] = "Team Fortress 2" + mapNames["koth_"] = "Team Fortress 2" + mapNames["mvm_"] = "Team Fortress 2" + mapNames["pl_"] = "Team Fortress 2" + mapNames["plr_"] = "Team Fortress 2" + mapNames["rd_"] = "Team Fortress 2" + mapNames["pd_"] = "Team Fortress 2" + mapNames["sd_"] = "Team Fortress 2" + mapNames["tc_"] = "Team Fortress 2" + mapNames["tr_"] = "Team Fortress 2" + mapNames["trade_"] = "Team Fortress 2" + mapNames["pass_"] = "Team Fortress 2" + + mapNames["zpa_"] = "Zombie Panic! Source" + mapNames["zpl_"] = "Zombie Panic! Source" + mapNames["zpo_"] = "Zombie Panic! Source" + mapNames["zps_"] = "Zombie Panic! Source" + + mapNames["bhop_"] = "Bunny Hop" + mapNames["cinema_"] = "Cinema" + mapNames["theater_"] = "Cinema" + mapNames["xc_"] = "Climb" + mapNames["deathrun_"] = "Deathrun" + mapNames["dr_"] = "Deathrun" + mapNames["fm_"] = "Flood" + mapNames["gmt_"] = "GMod Tower" + mapNames["gg_"] = "Gun Game" + mapNames["scoutzknivez"] = "Gun Game" + mapNames["ba_"] = "Jailbreak" + mapNames["jail_"] = "Jailbreak" + mapNames["jb_"] = "Jailbreak" + mapNames["mg_"] = "Minigames" + mapNames["pw_"] = "Pirate Ship Wars" + mapNames["ph_"] = "Prop Hunt" + mapNames["rp_"] = "Roleplay" + mapNames["slb_"] = "Sled Build" + mapNames["sb_"] = "Spacebuild" + mapNames["slender_"] = "Stop it Slender" + mapNames["gms_"] = "Stranded" + mapNames["surf_"] = "Surf" + mapNames["ts_"] = "The Stalker" + mapNames["zm_"] = "Zombie Survival" + mapNames["zombiesurvival_"] = "Zombie Survival" + mapNames["zs_"] = "Zombie Survival" + + for _, gm in ipairs(engine.GetGamemodes()) do + if gm.maps ~= "" then + for _, pattern in ipairs(string.Split(gm.maps, "|")) do + -- When in doubt, just try to match it with string.find later + mapPatterns[string.lower(pattern)] = gm.title or "Unnammed Gamemode" + end + end + end +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_PlayerLists.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_PlayerLists.lua new file mode 100644 index 0000000..d2ef42c --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_PlayerLists.lua @@ -0,0 +1,41 @@ +local function SortedPairsByFunction(Table, Sorted, SortDown) + local CopyTable = {} + for _, v in pairs(Table) do + table.insert(CopyTable, {NAME = tostring(v:Nick()), PLY = v}) + end + table.SortByMember(CopyTable, "NAME", SortDown) + + local SortedTable = {} + for _, v in ipairs(CopyTable) do + if not IsValid(v.PLY) or not v.PLY[Sorted] then continue end + local SortBy = (Sorted ~= "Team" and v.PLY[Sorted](v.PLY)) or (v.PLY:getDarkRPVar("job") or team.GetName(v.PLY[Sorted](v.PLY))) + SortedTable[SortBy] = SortedTable[SortBy] or {} + table.insert(SortedTable[SortBy], v.PLY) + end + + local SecondSort = {} + for _, v in SortedPairs(SortedTable, SortDown) do + table.insert(SecondSort, v) + end + + CopyTable = {} + for _, v in ipairs(SecondSort) do + for _, b in pairs(v) do + table.insert(CopyTable, b) + end + end + + return ipairs(CopyTable) +end + +function FAdmin.ScoreBoard.Main.PlayerListView(Sorted, SortDown) + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true) + for _, ply in SortedPairsByFunction(player.GetAll(), Sorted, SortDown) do + local Row = vgui.Create("FadminPlayerRow") + Row:SetPlayer(ply) + Row:Dock(TOP) + Row:InvalidateLayout() + + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:AddItem(Row) + end +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_controls.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_controls.lua new file mode 100644 index 0000000..54f75da --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_controls.lua @@ -0,0 +1,308 @@ +FAdmin.PlayerIcon = {} +FAdmin.PlayerIcon.RightClickOptions = {} + +function FAdmin.PlayerIcon.AddRightClickOption(name, func) + FAdmin.PlayerIcon.RightClickOptions[name] = func +end + +-- FAdminPanelList +local PANEL = {} + +function PANEL:Init() + self.Padding = 5 +end + +function PANEL:SizeToContents() + local w, h = self:GetSize() + + -- Fix size of w to have the same size as the scoreboard + w = math.Clamp(w, ScrW() * 0.9, ScrW() * 0.9) + h = math.Min(h, ScrH() * 0.95) + + -- It fucks up when there's only one icon in + if #self:GetChildren() == 1 then + h = math.Max(0, 120) + end + + self:SetSize(w, h) + self:PerformLayout() +end + +function PANEL:Paint() +end + +derma.DefineControl("FAdminPanelList", "DPanellist adapted for FAdmin", PANEL, "DPanelList") + +-- FAdminPlayerCatagoryHeader +local PANEL2 = {} + +function PANEL2:PerformLayout() + self:SetFont("Trebuchet24") +end + +derma.DefineControl("FAdminPlayerCatagoryHeader", "DCatagoryCollapse header adapted for FAdmin", PANEL2, "DCategoryHeader") + +-- FAdminPlayerCatagory +local PANEL3 = {} + +function PANEL3:Init() + if self.Header then + self.Header:Remove() -- the old header is still there don't ask me why + end + self.Header = vgui.Create("FAdminPlayerCatagoryHeader", self) + self.Header:SetSize(20, 25) + self:SetPadding(5) + self.Header:Dock(TOP) + + self:SetExpanded(true) + self:SetMouseInputEnabled(true) + + self:SetAnimTime(0.2) + self.animSlide = Derma_Anim("Anim", self, self.AnimSlide) + + self:SetPaintBackgroundEnabled(true) + +end + +function PANEL3:Paint() + if self.CatagoryColor then + draw.RoundedBox(4, 0, 0, self:GetWide(), self.Header:GetTall(), self.CatagoryColor) + end +end + +derma.DefineControl("FAdminPlayerCatagory", "DCatagoryCollapse adapted for FAdmin", PANEL3, "DCollapsibleCategory") + +-- FAdmin player row (from the sandbox player row) +PANEL = {} + +local PlayerRowSize = CreateClientConVar("FAdmin_PlayerRowSize", 30, true, false) +function PANEL:Init() + self.Size = PlayerRowSize:GetInt() + + self.lblName = vgui.Create("DLabel", self) + self.lblFrags = vgui.Create("DLabel", self) + self.lblTeam = vgui.Create("DLabel", self) + self.lblDeaths = vgui.Create("DLabel", self) + self.lblPing = vgui.Create("DLabel", self) + self.lblWanted = vgui.Create("DLabel", self) + + -- If you don't do this it'll block your clicks + self.lblName:SetMouseInputEnabled(false) + self.lblTeam:SetMouseInputEnabled(false) + self.lblFrags:SetMouseInputEnabled(false) + self.lblDeaths:SetMouseInputEnabled(false) + self.lblPing:SetMouseInputEnabled(false) + self.lblWanted:SetMouseInputEnabled(false) + + self.lblName:SetColor(Color(255,255,255,200)) + self.lblTeam:SetColor(Color(255,255,255,200)) + self.lblFrags:SetColor(Color(255,255,255,200)) + self.lblDeaths:SetColor(Color(255,255,255,200)) + self.lblPing:SetColor(Color(255,255,255,200)) + self.lblWanted:SetColor(Color(255,255,255,200)) + + self.imgAvatar = vgui.Create("AvatarImage", self) + + self:SetCursor("hand") +end + +function PANEL:Paint() + if not IsValid(self.Player) then return end + + self.Size = PlayerRowSize:GetInt() + self.imgAvatar:SetSize(self.Size - 4, self.Size - 4) + + local color = Color(100, 150, 245, 255) + + + if GAMEMODE.Name == "Sandbox" then + color = Color(100, 150, 245, 255) + if self.Player:Team() == TEAM_CONNECTING then + color = Color(200, 120, 50, 255) + elseif self.Player:IsAdmin() then + color = Color(30, 200, 50, 255) + end + + if self.Player:GetFriendStatus() == "friend" then + color = Color(236, 181, 113, 255) + end + else + color = team.GetColor(self.Player:Team()) + end + + local hooks = hook.GetTable().FAdmin_PlayerRowColour + if hooks then + for _, v in pairs(hooks) do + color = (v and v(self.Player, color)) or color + break + end + end + + draw.RoundedBox(4, 0, 0, self:GetWide(), self.Size, color) + + surface.SetTexture(0) + if self.Player == LocalPlayer() or self.Player:GetFriendStatus() == "friend" then + surface.SetDrawColor(255, 255, 255, 50 + math.sin(RealTime() * 2) * 50) + end + surface.DrawTexturedRect(0, 0, self:GetWide(), self.Size) + return true +end + +function PANEL:SetPlayer(ply) + self.Player = ply + + self.imgAvatar:SetPlayer(ply) + + self:UpdatePlayerData() +end + +function PANEL:UpdatePlayerData() + if not self.Player then return end + if not self.Player:IsValid() then return end + + self.lblName:SetText(DarkRP.deLocalise(self.Player:Nick())) + self.lblTeam:SetText((self.Player.DarkRPVars and DarkRP.deLocalise(self.Player:getDarkRPVar("job") or "")) or team.GetName(self.Player:Team())) + self.lblTeam:SizeToContents() + self.lblFrags:SetText(self.Player:Frags()) + self.lblDeaths:SetText(self.Player:Deaths()) + self.lblPing:SetText(self.Player:Ping()) + self.lblWanted:SetText(self.Player:isWanted() and DarkRP.getPhrase("Wanted_text") or "") +end + +function PANEL:ApplySchemeSettings() + self.lblName:SetFont("ScoreboardPlayerNameBig") + self.lblTeam:SetFont("ScoreboardPlayerNameBig") + self.lblFrags:SetFont("ScoreboardPlayerName") + self.lblDeaths:SetFont("ScoreboardPlayerName") + self.lblPing:SetFont("ScoreboardPlayerName") + self.lblWanted:SetFont("ScoreboardPlayerNameBig") + + self.lblName:SetFGColor(color_white) + self.lblTeam:SetFGColor(color_white) + self.lblFrags:SetFGColor(color_white) + self.lblDeaths:SetFGColor(color_white) + self.lblPing:SetFGColor(color_white) + self.lblWanted:SetFGColor(color_white) +end + +function PANEL:DoClick(x, y) + if not IsValid(self.Player) then self:Remove() return end + FAdmin.ScoreBoard.ChangeView("Player", self.Player) +end + +function PANEL:DoRightClick() + if table.IsEmpty(FAdmin.PlayerIcon.RightClickOptions) then return end + local menu = DermaMenu() + + menu:SetPos(gui.MouseX(), gui.MouseY()) + + for Name, func in SortedPairs(FAdmin.PlayerIcon.RightClickOptions) do + menu:AddOption(Name, function() if IsValid(self.Player) then func(self.Player, self) end end) + end + + menu:Open() +end + +function PANEL:Think() + if not self.PlayerUpdate or self.PlayerUpdate < CurTime() then + self.PlayerUpdate = CurTime() + 0.5 + self:UpdatePlayerData() + end +end + +function PANEL:PerformLayout() + self.imgAvatar:SetPos(2, 2) + self.imgAvatar:SetSize(32, 32) + + self:SetSize(self:GetWide(), self.Size) + + self.lblName:SizeToContents() + self.lblName:SetPos(24, 2) + self.lblName:MoveRightOf(self.imgAvatar, 8) + + local COLUMN_SIZE = 75 + + self.lblPing:SetPos(self:GetWide() - COLUMN_SIZE * 0.4, 0) + self.lblDeaths:SetPos(self:GetWide() - COLUMN_SIZE * 1.4, 0) + self.lblFrags:SetPos(self:GetWide() - COLUMN_SIZE * 2.4, 0) + + self.lblTeam:SetPos(self:GetWide() / 2 - (0.5 * self.lblTeam:GetWide())) + + self.lblWanted:SizeToContents() + self.lblWanted:SetPos(math.floor(self:GetWide() / 4), 2) +end +vgui.Register("FadminPlayerRow", PANEL, "Button") + +-- FAdminActionButton +local PANEL6 = {} + +function PANEL6:Init() + self:SetDrawBackground(false) + self:SetDrawBorder(false) + self:SetStretchToFit(false) + self:SetSize(120, 40) + + self.TextLabel = vgui.Create("DLabel", self) + self.TextLabel:SetColor(Color(200,200,200,200)) + self.TextLabel:SetFont("Roboto20") + + self.m_Image2 = vgui.Create("DImage", self) + + self.BorderColor = Color(190,40,0,255) +end + +function PANEL6:SetText(text) + self.TextLabel:SetText(text) + self.TextLabel:SizeToContents() + + self:SetWide(self.TextLabel:GetWide() + 44) +end + +function PANEL6:PerformLayout() + self.m_Image:SetSize(32,32) + self.m_Image:SetPos(4,4) + + self.m_Image2:SetSize(32, 32) + self.m_Image2:SetPos(4,4) + + self.TextLabel:SetPos(38, 8) +end + +function PANEL6:SetImage2(Mat, bckp) + self.m_Image2:SetImage(Mat, bckp) +end + +function PANEL6:SetBorderColor(Col) + self.BorderColor = Col or Color(190,40,0,255) +end + +function PANEL6:Paint() + local BorderColor = self.BorderColor + if self.Hovered then + BorderColor = Color(math.Min(BorderColor.r + 40, 255), math.Min(BorderColor.g + 40, 255), math.Min(BorderColor.b + 40, 255), BorderColor.a) + end + if self.Depressed then + BorderColor = color_transparent + end + draw.RoundedBox(4, 0, 0, self:GetWide(), self:GetTall(), BorderColor) + draw.RoundedBox(4, 2, 2, self:GetWide() - 4, self:GetTall() - 4, Color(40, 40, 40, 255)) +end + +function PANEL6:OnMousePressed(mouse) + if self:GetDisabled() then return end + + self.m_Image:SetSize(24,24) + self.m_Image:SetPos(8,8) + self.Depressed = true +end + +function PANEL6:OnMouseReleased(mouse) + if self:GetDisabled() then return end + + self.m_Image:SetSize(32,32) + self.m_Image:SetPos(4,4) + self.Depressed = false + self:DoClick() +end + +derma.DefineControl("FAdminActionButton", "Button for doing actions", PANEL6, "DImageButton") diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboard.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboard.lua new file mode 100644 index 0000000..e23bb17 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboard.lua @@ -0,0 +1,146 @@ +local OverrideScoreboard = CreateClientConVar("FAdmin_OverrideScoreboard", 0, true, false) -- Set if it's a scoreboard or not + +function FAdmin.ScoreBoard.ChangeView(newView, ...) + if FAdmin.ScoreBoard.CurrentView == newView or not FAdmin.ScoreBoard.Visible then return end + + for _, v in pairs(FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Controls) do + v:SetVisible(false) + end + + FAdmin.ScoreBoard.CurrentView = newView + FAdmin.ScoreBoard[newView].Show(...) + FAdmin.ScoreBoard.ChangeGmodLogo(FAdmin.ScoreBoard[newView].Logo) + + FAdmin.ScoreBoard.Controls.BackButton = FAdmin.ScoreBoard.Controls.BackButton or vgui.Create("DButton") + FAdmin.ScoreBoard.Controls.BackButton:SetVisible(true) + FAdmin.ScoreBoard.Controls.BackButton:SetPos(FAdmin.ScoreBoard.X, FAdmin.ScoreBoard.Y) + FAdmin.ScoreBoard.Controls.BackButton:SetText("") + FAdmin.ScoreBoard.Controls.BackButton:SetTooltip("Click me to go back!") + FAdmin.ScoreBoard.Controls.BackButton:SetCursor("hand") + FAdmin.ScoreBoard.Controls.BackButton:SetSize(100,90) + FAdmin.ScoreBoard.Controls.BackButton:SetZPos(999) + + function FAdmin.ScoreBoard.Controls.BackButton:DoClick() + FAdmin.ScoreBoard.ChangeView("Main") + end + FAdmin.ScoreBoard.Controls.BackButton.Paint = function() end +end + +--"fadmin/back", gui/gmod_tool +local GmodLogo, TempGmodLogo, GmodLogoColor = surface.GetTextureID("gui/gmod_logo"), surface.GetTextureID("gui/gmod_logo"), color_white +function FAdmin.ScoreBoard.ChangeGmodLogo(new) + if surface.GetTextureID(new) == TempGmodLogo then return end + TempGmodLogo = surface.GetTextureID(new) + for i = 0, 0.5, 0.01 do + timer.Simple(i, function() GmodLogoColor = Color(255,255,255,GmodLogoColor.a-5.1) end) + end + timer.Simple(0.5, function() GmodLogo = surface.GetTextureID(new) end) + for i = 0.5, 1, 0.01 do + timer.Simple(i, function() + GmodLogoColor = Color(255, 255, 255, GmodLogoColor.a + 5.1) + end) + end +end + +function FAdmin.ScoreBoard.Background() + surface.SetDrawColor(0,0,0,200) + surface.DrawRect(FAdmin.ScoreBoard.X, FAdmin.ScoreBoard.Y, FAdmin.ScoreBoard.Width, FAdmin.ScoreBoard.Height) + + surface.SetTexture(GmodLogo) + surface.SetDrawColor(255,255,255,GmodLogoColor.a) + surface.DrawTexturedRect(FAdmin.ScoreBoard.X - 20, FAdmin.ScoreBoard.Y, 128, 128) +end + + +function FAdmin.ScoreBoard.DrawScoreBoard() + if (input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE)) and not FAdmin.ScoreBoard.DontGoBack then + FAdmin.ScoreBoard.ChangeView("Main") + elseif FAdmin.ScoreBoard.DontGoBack then + FAdmin.ScoreBoard.DontGoBack = input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE) + end + FAdmin.ScoreBoard.Background() +end + +function FAdmin.ScoreBoard.ShowScoreBoard() + FAdmin.ScoreBoard.Visible = true + FAdmin.ScoreBoard.DontGoBack = input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE) + + FAdmin.ScoreBoard.Controls.Hostname = FAdmin.ScoreBoard.Controls.Hostname or vgui.Create("DLabel") + FAdmin.ScoreBoard.Controls.Hostname:SetText(DarkRP.deLocalise(GetHostName())) + FAdmin.ScoreBoard.Controls.Hostname:SetFont("ScoreboardHeader") + FAdmin.ScoreBoard.Controls.Hostname:SetColor(Color(200,200,200,200)) + FAdmin.ScoreBoard.Controls.Hostname:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 20) + FAdmin.ScoreBoard.Controls.Hostname:SizeToContents() + FAdmin.ScoreBoard.Controls.Hostname:SetVisible(true) + + FAdmin.ScoreBoard.Controls.Description = FAdmin.ScoreBoard.Controls.Description or vgui.Create("DLabel") + FAdmin.ScoreBoard.Controls.Description:SetText(string.format("%s\n%s", GAMEMODE.Name, GAMEMODE.Author)) + FAdmin.ScoreBoard.Controls.Description:SetFont("ScoreboardSubtitle") + FAdmin.ScoreBoard.Controls.Description:SetColor(Color(200,200,200,200)) + FAdmin.ScoreBoard.Controls.Description:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 50) + FAdmin.ScoreBoard.Controls.Description:SizeToContents() + if FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Width / 9.5 + FAdmin.ScoreBoard.Controls.Description:GetWide() > FAdmin.ScoreBoard.Width - 150 then + FAdmin.ScoreBoard.Controls.Description:SetFont("Trebuchet18") + FAdmin.ScoreBoard.Controls.Description:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 50) + end + FAdmin.ScoreBoard.Controls.Description:SetVisible(true) + + FAdmin.ScoreBoard.Controls.ServerSettingsLabel = FAdmin.ScoreBoard.Controls.ServerSettingsLabel or vgui.Create("DLabel") + FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetFont("ScoreboardSubtitle") + FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetText("Server settings") + FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetColor(Color(200,200,200,200)) + FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SizeToContents() + FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetPos(FAdmin.ScoreBoard.Width-150, FAdmin.ScoreBoard.Y + 68) + FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetVisible(true) + + FAdmin.ScoreBoard.Controls.ServerSettings = FAdmin.ScoreBoard.Controls.ServerSettings or vgui.Create("DImageButton") + FAdmin.ScoreBoard.Controls.ServerSettings:SetMaterial("vgui/gmod_tool") + FAdmin.ScoreBoard.Controls.ServerSettings:SetPos(FAdmin.ScoreBoard.Width-200, FAdmin.ScoreBoard.Y - 20) + FAdmin.ScoreBoard.Controls.ServerSettings:SizeToContents() + FAdmin.ScoreBoard.Controls.ServerSettings:SetVisible(true) + + function FAdmin.ScoreBoard.Controls.ServerSettings:DoClick() + FAdmin.ScoreBoard.ChangeView("Server") + end + + if FAdmin.ScoreBoard.Controls.BackButton then FAdmin.ScoreBoard.Controls.BackButton:SetVisible(true) end + + FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Show() + + gui.EnableScreenClicker(true) + hook.Add("HUDPaint", "FAdmin_ScoreBoard", FAdmin.ScoreBoard.DrawScoreBoard) + hook.Call("FAdmin_ShowFAdminMenu") + return true +end +concommand.Add("+FAdmin_menu", FAdmin.ScoreBoard.ShowScoreBoard) + +hook.Add("ScoreboardShow", "FAdmin_scoreboard", function() + if FAdmin.GlobalSetting.FAdmin or OverrideScoreboard:GetBool() then -- Don't show scoreboard when FAdmin is not installed on server + return FAdmin.ScoreBoard.ShowScoreBoard() + end +end) + +function FAdmin.ScoreBoard.HideScoreBoard() + if not FAdmin.GlobalSetting.FAdmin then return end + FAdmin.ScoreBoard.Visible = false + CloseDermaMenus() + + gui.EnableScreenClicker(false) + hook.Remove("HUDPaint", "FAdmin_ScoreBoard") + + for _, v in pairs(FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Controls) do + v:SetVisible(false) + end + + for _, v in pairs(FAdmin.ScoreBoard.Controls) do + v:SetVisible(false) + end + return true +end +concommand.Add("-FAdmin_menu", FAdmin.ScoreBoard.HideScoreBoard) + +hook.Add("ScoreboardHide", "FAdmin_scoreboard", function() + if FAdmin.GlobalSetting.FAdmin or OverrideScoreboard:GetBool() then -- Don't show scoreboard when FAdmin is not installed on server + return FAdmin.ScoreBoard.HideScoreBoard() + end +end) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardMain.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardMain.lua new file mode 100644 index 0000000..0c1986c --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardMain.lua @@ -0,0 +1,110 @@ +local Sorted, SortDown = CreateClientConVar("FAdmin_SortPlayerList", "Team", true), CreateClientConVar("FAdmin_SortPlayerListDown", 1, true) +local allowedSorts = { + ["Name"] = true, + ["Team"] = true, + ["Frags"] = true, + ["Deaths"] = true, + ["Ping"] = true +} + +function FAdmin.ScoreBoard.Main.Show() + local Sort = {} + local ScreenWidth, ScreenHeight = ScrW(), ScrH() + + FAdmin.ScoreBoard.X = ScreenWidth * 0.05 + FAdmin.ScoreBoard.Y = ScreenHeight * 0.025 + FAdmin.ScoreBoard.Width = ScreenWidth * 0.9 + FAdmin.ScoreBoard.Height = ScreenHeight * 0.95 + + FAdmin.ScoreBoard.ChangeView("Main") + + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList = FAdmin.ScoreBoard.Main.Controls.FAdminPanelList or vgui.Create("DPanelList") + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetVisible(true) + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true) + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList.Padding = 3 + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:EnableVerticalScrollbar(true) + + + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true) + + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 90 + 30 + 20) + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetSize(FAdmin.ScoreBoard.Width - 40, FAdmin.ScoreBoard.Height - 90 - 30 - 20 - 20) + + Sort.Name = Sort.Name or vgui.Create("DLabel") + Sort.Name:SetText("Sort by: Name") + Sort.Name:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 90 + 30) + Sort.Name.Type = "Name" + Sort.Name:SetVisible(true) + + Sort.Team = Sort.Team or vgui.Create("DLabel") + Sort.Team:SetText("Team") + Sort.Team:SetPos(ScreenWidth * 0.5 - 30, FAdmin.ScoreBoard.Y + 90 + 30) + Sort.Team.Type = "Team" + Sort.Team:SetVisible(true) + + Sort.Frags = Sort.Frags or vgui.Create("DLabel") + Sort.Frags:SetText("Kills") + Sort.Frags:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 200, FAdmin.ScoreBoard.Y + 90 + 30) + Sort.Frags.Type = "Frags" + Sort.Frags:SetVisible(true) + + Sort.Deaths = Sort.Deaths or vgui.Create("DLabel") + Sort.Deaths:SetText("Deaths") + Sort.Deaths:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 140, FAdmin.ScoreBoard.Y + 90 + 30) + Sort.Deaths.Type = "Deaths" + Sort.Deaths:SetVisible(true) + + Sort.Ping = Sort.Ping or vgui.Create("DLabel") + Sort.Ping:SetText("Ping") + Sort.Ping:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 50, FAdmin.ScoreBoard.Y + 90 + 30) + Sort.Ping.Type = "Ping" + Sort.Ping:SetVisible(true) + + local sortBy = Sorted:GetString() + sortBy = allowedSorts[sortBy] and sortBy or "Team" + + FAdmin.ScoreBoard.Main.PlayerListView(sortBy, SortDown:GetBool()) + + for _, v in pairs(Sort) do + v:SetFont("Trebuchet20") + v:SizeToContents() + + local X, Y = v:GetPos() + + v.BtnSort = vgui.Create("DButton") + v.BtnSort:SetText("") + v.BtnSort.Type = "Down" + v.BtnSort.Paint = function(panel, w, h) derma.SkinHook("Paint", "ButtonDown", panel, w, h) end + v.BtnSort:SetSkin(GAMEMODE.Config.DarkRPSkin) + if Sorted:GetString() == v.Type then + v.BtnSort.Depressed = true + v.BtnSort.Type = (SortDown:GetBool() and "Down") or "Up" + end + v.BtnSort:SetSize(16, 16) + v.BtnSort:SetPos(X + v:GetWide() + 5, Y + 4) + function v.BtnSort.DoClick() + for _, b in pairs(Sort) do + b.BtnSort.Depressed = b.BtnSort == v.BtnSort + end + v.BtnSort.Type = (v.BtnSort.Type == "Down" and "Up") or "Down" + v.BtnSort.Paint = function(panel, w, h) + derma.SkinHook("Paint", "Button" .. v.BtnSort.Type, panel, w, h) + end + + RunConsoleCommand("FAdmin_SortPlayerList", v.Type) + RunConsoleCommand("FAdmin_SortPlayerListDown", (v.BtnSort.Type == "Down" and "1") or "0") + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true) + FAdmin.ScoreBoard.Main.PlayerListView(v.Type, v.BtnSort.Type == "Down") + end + table.insert(FAdmin.ScoreBoard.Main.Controls, v) -- Add them to the table so they get removed when you close the scoreboard + table.insert(FAdmin.ScoreBoard.Main.Controls, v.BtnSort) + end +end + +function FAdmin.ScoreBoard.Main.AddPlayerRightClick(Name, func) + FAdmin.PlayerIcon.RightClickOptions[Name] = func +end + +FAdmin.StartHooks["CopySteamID"] = function() + FAdmin.ScoreBoard.Main.AddPlayerRightClick("Copy SteamID", function(ply) SetClipboardText(ply:SteamID()) end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardPlayer.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardPlayer.lua new file mode 100644 index 0000000..90019ed --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardPlayer.lua @@ -0,0 +1,181 @@ +FAdmin.ScoreBoard.Player.Information = {} +FAdmin.ScoreBoard.Player.ActionButtons = {} + +function FAdmin.ScoreBoard.Player.Show(ply) + ply = ply or FAdmin.ScoreBoard.Player.Player + FAdmin.ScoreBoard.Player.Player = ply + + if not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player) then FAdmin.ScoreBoard.ChangeView("Main") return end + + local ScreenHeight = ScrH() + + FAdmin.ScoreBoard.Player.Controls.AvatarBackground = vgui.Create("AvatarImage") + FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100) + FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetSize(184, 184) + FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetPlayer(ply, 184) + FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetVisible(true) + + FAdmin.ScoreBoard.Player.InfoPanels = FAdmin.ScoreBoard.Player.InfoPanels or {} + for k, v in pairs(FAdmin.ScoreBoard.Player.InfoPanels) do + if IsValid(v) then + v:Remove() + FAdmin.ScoreBoard.Player.InfoPanels[k] = nil + end + end + + if IsValid(FAdmin.ScoreBoard.Player.Controls.InfoPanel1) then + FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Remove() + end + + FAdmin.ScoreBoard.Player.Controls.InfoPanel1 = vgui.Create("DListLayout") + FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100 + 184 + 5 --[[ + Avatar size]]) + FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetSize(184, ScreenHeight * 0.1 + 2) + FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetVisible(true) + FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Clear(true) + + FAdmin.ScoreBoard.Player.Controls.InfoPanel2 = FAdmin.ScoreBoard.Player.Controls.InfoPanel2 or vgui.Create("FAdminPanelList") + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetPos(FAdmin.ScoreBoard.X + 25 + 184 --[[+ Avatar]], FAdmin.ScoreBoard.Y + 100) + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetSize(FAdmin.ScoreBoard.Width - 184 - 30 - 10, 184 + 5 + ScreenHeight * 0.1 + 2) + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetVisible(true) + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:Clear(true) + + local function AddInfoPanel() + local pan = FAdmin.ScoreBoard.Player.Controls.InfoPanel2:Add("DListLayout") + pan:SetSize(1, FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall()) + + table.insert(FAdmin.ScoreBoard.Player.InfoPanels, pan) + return pan + end + + local SelectedPanel = AddInfoPanel() -- Make first panel to put the first things in + + for k, v in pairs(FAdmin.ScoreBoard.Player.Information) do + SelectedPanel:Dock(LEFT) + local Value = v.func(FAdmin.ScoreBoard.Player.Player) + --if not Value or Value == "" then return --[[ Value = "N/A" ]] end + if Value and Value ~= "" then + + local Text = vgui.Create("DLabel") + Text:Dock(LEFT) + Text:SetFont("TabLarge") + Text:SetText(v.name .. ": " .. Value) + Text:SizeToContents() + Text:SetColor(Color(200,200,200,200)) + Text:SetTooltip("Click to copy " .. v.name .. " to clipboard") + Text:SetMouseInputEnabled(true) + + function Text:OnMousePressed(mcode) + self:SetTooltip(v.name .. " copied to clipboard!") + ChangeTooltip(self) + SetClipboardText(Value) + self:SetTooltip("Click to copy " .. v.name .. " to clipboard") + end + + timer.Create("FAdmin_Scoreboard_text_update_" .. v.name, 1, 0, function() + if not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player) or not IsValid(Text) then + timer.Remove("FAdmin_Scoreboard_text_update_" .. v.name) + if FAdmin.ScoreBoard.Visible and (not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player)) then FAdmin.ScoreBoard.ChangeView("Main") end + return + end + Value = v.func(FAdmin.ScoreBoard.Player.Player) + if not Value or Value == "" then Value = "N/A" end + Text:SetText(v.name .. ": " .. Value) + end) + + if (#FAdmin.ScoreBoard.Player.Controls.InfoPanel1:GetChildren() * 17 + 17) <= FAdmin.ScoreBoard.Player.Controls.InfoPanel1:GetTall() and not v.NewPanel then + FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Add(Text) + else + if #SelectedPanel:GetChildren() * 17 + 17 >= SelectedPanel:GetTall() or v.NewPanel then + SelectedPanel = AddInfoPanel() -- Add new panel if the last one is full + end + SelectedPanel:Add(Text) + if Text:GetWide() > SelectedPanel:GetWide() then + SelectedPanel:SetWide(Text:GetWide() + 40) + end + end + end + end + + local CatColor = team.GetColor(ply:Team()) + if GAMEMODE.Name == "Sandbox" then + CatColor = Color(100, 150, 245, 255) + if ply:Team() == TEAM_CONNECTING then + CatColor = Color(200, 120, 50, 255) + elseif ply:IsAdmin() then + CatColor = Color(30, 200, 50, 255) + end + + if ply:GetFriendStatus() == "friend" then + CatColor = Color(236, 181, 113, 255) + end + end + CatColor = hook.Run("FAdmin_PlayerRowColour", ply, CatColor) or CatColor + + FAdmin.ScoreBoard.Player.Controls.ButtonCat = FAdmin.ScoreBoard.Player.Controls.ButtonCat or vgui.Create("FAdminPlayerCatagory") + FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetLabel(" Player options!") + FAdmin.ScoreBoard.Player.Controls.ButtonCat.CatagoryColor = CatColor + FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetSize(FAdmin.ScoreBoard.Width - 40, 100) + FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100 + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall() + 5) + FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetVisible(true) + + function FAdmin.ScoreBoard.Player.Controls.ButtonCat:Toggle() + end + + FAdmin.ScoreBoard.Player.Controls.ButtonPanel = FAdmin.ScoreBoard.Player.Controls.ButtonPanel or vgui.Create("FAdminPanelList", FAdmin.ScoreBoard.Player.Controls.ButtonCat) + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetSpacing(5) + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:EnableHorizontal(true) + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:EnableVerticalScrollbar(true) + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SizeToContents() + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetVisible(true) + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetSize(0, (ScreenHeight - FAdmin.ScoreBoard.Y - 40) - (FAdmin.ScoreBoard.Y + 100 + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall() + 5)) + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:Clear() + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:DockMargin(5, 5, 5, 5) + + for _, v in ipairs(FAdmin.ScoreBoard.Player.ActionButtons) do + if v.Visible == true or (isfunction(v.Visible) and v.Visible(FAdmin.ScoreBoard.Player.Player) == true) then + local ActionButton = vgui.Create("FAdminActionButton") + local imageType = TypeID(v.Image) + if imageType == TYPE_STRING then + ActionButton:SetImage(v.Image or "icon16/exclamation") + elseif imageType == TYPE_TABLE then + ActionButton:SetImage(v.Image[1]) + if v.Image[2] then ActionButton:SetImage2(v.Image[2]) end + elseif imageType == TYPE_FUNCTION then + local img1, img2 = v.Image(ply) + ActionButton:SetImage(img1) + if img2 then ActionButton:SetImage2(img2) end + else + ActionButton:SetImage("icon16/exclamation") + end + local name = v.Name + if isfunction(name) then name = name(FAdmin.ScoreBoard.Player.Player) end + ActionButton:SetText(DarkRP.deLocalise(name)) + ActionButton:SetBorderColor(v.color) + + function ActionButton:DoClick() + if not IsValid(FAdmin.ScoreBoard.Player.Player) then return end + return v.Action(FAdmin.ScoreBoard.Player.Player, self) + end + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:AddItem(ActionButton) + if v.OnButtonCreated then + v.OnButtonCreated(FAdmin.ScoreBoard.Player.Player, ActionButton) + end + end + end + FAdmin.ScoreBoard.Player.Controls.ButtonPanel:Dock(TOP) +end + +function FAdmin.ScoreBoard.Player:AddInformation(name, func, ForceNewPanel) -- ForeNewPanel is to start a new column + table.insert(FAdmin.ScoreBoard.Player.Information, {name = name, func = func, NewPanel = ForceNewPanel}) +end + +function FAdmin.ScoreBoard.Player:AddActionButton(Name, Image, color, Visible, Action, OnButtonCreated) + table.insert(FAdmin.ScoreBoard.Player.ActionButtons, {Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated}) +end + +FAdmin.ScoreBoard.Player:AddInformation("Name", function(ply) return ply:Nick() end) +FAdmin.ScoreBoard.Player:AddInformation("Kills", function(ply) return ply:Frags() end) +FAdmin.ScoreBoard.Player:AddInformation("Deaths", function(ply) return ply:Deaths() end) +FAdmin.ScoreBoard.Player:AddInformation("Health", function(ply) return ply:Health() end) +FAdmin.ScoreBoard.Player:AddInformation("Ping", function(ply) return ply:Ping() end) +FAdmin.ScoreBoard.Player:AddInformation("SteamID", function(ply) return ply:SteamID() end, true) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardServer.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardServer.lua new file mode 100644 index 0000000..a4fc65d --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_scoreboardServer.lua @@ -0,0 +1,227 @@ +FAdmin.ScoreBoard.Server.Information = {} -- Compatibility for autoreload +FAdmin.ScoreBoard.Server.ActionButtons = {} -- Refresh server buttons when reloading gamemode + +local function MakeServerOptions() + local _, YPos, Width = 20, FAdmin.ScoreBoard.Y + 120 + FAdmin.ScoreBoard.Height / 5 + 20, (FAdmin.ScoreBoard.Width - 40) / 3 + + FAdmin.ScoreBoard.Server.Controls.ServerActionsCat = FAdmin.ScoreBoard.Server.Controls.ServerActionsCat or vgui.Create("FAdminPlayerCatagory") + FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetLabel(" Server Actions") + FAdmin.ScoreBoard.Server.Controls.ServerActionsCat.CatagoryColor = Color(155, 0, 0, 255) + FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos) + FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetPos(FAdmin.ScoreBoard.X + 20, YPos) + FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetVisible(true) + function FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:Toggle() + end + + FAdmin.ScoreBoard.Server.Controls.ServerActions = FAdmin.ScoreBoard.Server.Controls.ServerActions or vgui.Create("FAdminPanelList") + FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.ServerActions) + FAdmin.ScoreBoard.Server.Controls.ServerActions:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos) + for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.ServerActions:GetChildren()) do + if k == 1 then continue end + v:Remove() + end + + FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat = FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat or vgui.Create("FAdminPlayerCatagory") + FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetLabel(" Player Actions") + FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat.CatagoryColor = Color(0, 155, 0, 255) + FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos) + FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetPos(FAdmin.ScoreBoard.X + 20 + Width, YPos) + FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetVisible(true) + function FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:Toggle() + end + + FAdmin.ScoreBoard.Server.Controls.PlayerActions = FAdmin.ScoreBoard.Server.Controls.PlayerActions or vgui.Create("FAdminPanelList") + FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.PlayerActions) + FAdmin.ScoreBoard.Server.Controls.PlayerActions:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos) + for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.PlayerActions:GetChildren()) do + if k == 1 then continue end + v:Remove() + end + + FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat = FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat or vgui.Create("FAdminPlayerCatagory") + FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetLabel(" Server Settings") + FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat.CatagoryColor = Color(0, 0, 155, 255) + FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos) + FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetPos(FAdmin.ScoreBoard.X + 20 + Width * 2, YPos) + FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetVisible(true) + function FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:Toggle() + end + + FAdmin.ScoreBoard.Server.Controls.ServerSettings = FAdmin.ScoreBoard.Server.Controls.ServerSettings or vgui.Create("FAdminPanelList") + FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.ServerSettings) + FAdmin.ScoreBoard.Server.Controls.ServerSettings:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos) + for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.ServerSettings:GetChildren()) do + if k == 1 then continue end + v:Remove() + end + + for k, v in ipairs(FAdmin.ScoreBoard.Server.ActionButtons) do + local visible = v.Visible == true or (isfunction(v.Visible) and v.Visible(LocalPlayer()) == true) + + local ActionButton = vgui.Create("FAdminActionButton") + local imageType = TypeID(v.Image) + if imageType == TYPE_STRING then + ActionButton:SetImage(v.Image or "icon16/exclamation") + elseif imageType == TYPE_TABLE then + ActionButton:SetImage(v.Image[1]) + if v.Image[2] then ActionButton:SetImage2(v.Image[2]) end + elseif imageType == TYPE_FUNCTION then + local img1, img2 = v.Image() + ActionButton:SetImage(img1) + if img2 then ActionButton:SetImage2(img2) end + else + ActionButton:SetImage("icon16/exclamation") + end + local name = v.Name + if isfunction(name) then name = name() end + ActionButton:SetText(DarkRP.deLocalise(name)) + ActionButton:SetBorderColor(visible and v.color or Color(120, 120, 120)) + ActionButton:SetDisabled(not visible) + ActionButton:Dock(TOP) + + function ActionButton:DoClick() + return v.Action(self) + end + + FAdmin.ScoreBoard.Server.Controls[v.TYPE]:Add(ActionButton) + if v.OnButtonCreated then + v.OnButtonCreated(ActionButton) + end + end +end + +function FAdmin.ScoreBoard.Server:AddServerAction(Name, Image, color, Visible, Action, OnButtonCreated) + table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "ServerActions", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated}) +end + +function FAdmin.ScoreBoard.Server:AddPlayerAction(Name, Image, color, Visible, Action, OnButtonCreated) + table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "PlayerActions", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated}) +end + +function FAdmin.ScoreBoard.Server:AddServerSetting(Name, Image, color, Visible, Action, OnButtonCreated) + table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "ServerSettings", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated}) +end + +function FAdmin.ScoreBoard.Server.Show(ply) + FAdmin.ScoreBoard.Server.InfoPanels = FAdmin.ScoreBoard.Server.InfoPanels or {} + for k, v in pairs(FAdmin.ScoreBoard.Server.InfoPanels) do + if IsValid(v) then + v:Remove() + FAdmin.ScoreBoard.Server.InfoPanels[k] = nil + end + end + + if IsValid(FAdmin.ScoreBoard.Server.Controls.InfoPanel) then + FAdmin.ScoreBoard.Server.Controls.InfoPanel:Remove() + end + FAdmin.ScoreBoard.Server.Controls.InfoPanel = vgui.Create("FAdminPanelList") + FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 120) + FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetSize(FAdmin.ScoreBoard.Width - 40, FAdmin.ScoreBoard.Height / 5) + FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetVisible(true) + FAdmin.ScoreBoard.Server.Controls.InfoPanel:Clear(true) + + local function AddInfoPanel() + local pan = vgui.Create("FAdminPanelList") + pan:SetSize(1, FAdmin.ScoreBoard.Server.Controls.InfoPanel:GetTall()) + pan:Dock(LEFT) + FAdmin.ScoreBoard.Server.Controls.InfoPanel:Add(pan) + + table.insert(FAdmin.ScoreBoard.Server.InfoPanels, pan) + return pan + end + + local SelectedPanel = AddInfoPanel() -- Make first panel to put the first things in + + for _, v in pairs(FAdmin.ScoreBoard.Server.Information) do + local Text = vgui.Create("DLabel") + Text:SetFont("TabLarge") + Text:SetColor(Color(255,255,255,200)) + Text:Dock(TOP) + Text.Func = v.Func + + local EndText + local function RefreshText() + local Value = v.func() + + if not Value or Value == "" then + Value = "N/A" + end + + EndText = v.name .. ": " .. Value + local strLen = string.len(EndText) + + if strLen > 40 then + local NewValue = string.sub(EndText, 1, 40) + + for i = 40, strLen, 34 do + NewValue = NewValue .. "\n " .. string.sub(EndText, i + 1, i + 34) + end + + EndText = NewValue + else + local MaxWidth = 240 + surface.SetFont("TabLarge") + local TextWidth = surface.GetTextSize(v.name .. ": " .. Value) + + if TextWidth <= MaxWidth then + local SpacesAmount = (MaxWidth - TextWidth) / 3 + local Spaces = "" + + for i = 1, SpacesAmount, 1 do + Spaces = Spaces .. " " + end + + EndText = v.name .. ":" .. Spaces .. Value + end + end + + Text:SetText(DarkRP.deLocalise(EndText)) + Text:SizeToContents() + Text:SetTooltip("Click to copy " .. v.name .. " to clipboard") + Text:SetMouseInputEnabled(true) + end + + RefreshText() + + function Text:OnMousePressed(mcode) + self:SetTooltip(v.name .. " copied to clipboard!") + ChangeTooltip(self) + SetClipboardText(v.func() or "") + self:SetTooltip("Click to copy " .. v.name .. " to clipboard") + end + + timer.Create("FAdmin_Scoreboard_text_update_" .. v.name, 1, 0, function() + if not IsValid(Text) then + timer.Remove("FAdmin_Scoreboard_text_update_" .. v.name) + FAdmin.ScoreBoard.ChangeView("Main") + + return + end + + RefreshText() + end) + + if #SelectedPanel:GetChildren() * 17 + 17 >= SelectedPanel:GetTall() or v.NewPanel then + SelectedPanel = AddInfoPanel() + end + -- Add new panel if the last one is full + SelectedPanel:Add(Text) + if Text:GetWide() > SelectedPanel:GetWide() then + SelectedPanel:SetWide(Text:GetWide() + 40) + end + end + + MakeServerOptions() +end + +function FAdmin.ScoreBoard.Server:AddInformation(name, func, ForceNewPanel) -- ForeNewPanel is to start a new column + table.insert(FAdmin.ScoreBoard.Server.Information, {name = name, func = func, NewPanel = ForceNewPanel}) +end + +FAdmin.ScoreBoard.Server:AddInformation("Hostname", GetHostName) +FAdmin.ScoreBoard.Server:AddInformation("Gamemode", function() return GAMEMODE.Name end) +FAdmin.ScoreBoard.Server:AddInformation("Author", function() return GAMEMODE.Author end) +FAdmin.ScoreBoard.Server:AddInformation("Map", game.GetMap) +FAdmin.ScoreBoard.Server:AddInformation("Players", function() return player.GetCount() .. "/" .. game.MaxPlayers() end) +FAdmin.ScoreBoard.Server:AddInformation("Ping", function() return LocalPlayer():Ping() end) + diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_start.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_start.lua new file mode 100644 index 0000000..dc5a6cb --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/cl_start.lua @@ -0,0 +1,24 @@ +FAdmin.ScoreBoard = FAdmin.ScoreBoard or {} + +local ScreenWidth, ScreenHeight = ScrW(), ScrH() + +FAdmin.ScoreBoard.X = ScreenWidth * 0.05 +FAdmin.ScoreBoard.Y = ScreenHeight * 0.025 +FAdmin.ScoreBoard.Width = ScreenWidth * 0.9 +FAdmin.ScoreBoard.Height = ScreenHeight * 0.95 + +FAdmin.ScoreBoard.Controls = FAdmin.ScoreBoard.Controls or {} +FAdmin.ScoreBoard.CurrentView = "Main" + +FAdmin.ScoreBoard.Main = FAdmin.ScoreBoard.Main or {} +FAdmin.ScoreBoard.Main.Controls = FAdmin.ScoreBoard.Main.Controls or {} +FAdmin.ScoreBoard.Main.Logo = "gui/gmod_logo" + +FAdmin.ScoreBoard.Player = FAdmin.ScoreBoard.Player or {} +FAdmin.ScoreBoard.Player.Controls = FAdmin.ScoreBoard.Player.Controls or {} +FAdmin.ScoreBoard.Player.Player = NULL +FAdmin.ScoreBoard.Player.Logo = "fadmin/back" + +FAdmin.ScoreBoard.Server = FAdmin.ScoreBoard.Server or {} +FAdmin.ScoreBoard.Server.Controls = FAdmin.ScoreBoard.Server.Controls or {} +FAdmin.ScoreBoard.Server.Logo = "fadmin/back" diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/sh_commontimes.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/sh_commontimes.lua new file mode 100644 index 0000000..96ac87b --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cl_interface/sh_commontimes.lua @@ -0,0 +1,53 @@ +--[[--------------------------------------------------------------------------- +Common times for several punishment actions +---------------------------------------------------------------------------]] +FAdmin.PlayerActions.commonTimes = {} +FAdmin.PlayerActions.commonTimes[0] = "indefinitely" +FAdmin.PlayerActions.commonTimes[10] = "10 seconds" +FAdmin.PlayerActions.commonTimes[30] = "30 seconds" +FAdmin.PlayerActions.commonTimes[60] = "1 minute" +FAdmin.PlayerActions.commonTimes[300] = "5 minutes" +FAdmin.PlayerActions.commonTimes[600] = "10 minutes" + +function FAdmin.PlayerActions.addTimeSubmenu(menu, submenuText, submenuClick, submenuItemClick) + local SubMenu = menu:AddSubMenu(submenuText, submenuClick) + + local Padding = vgui.Create("DPanel") + Padding:SetPaintBackgroundEnabled(false) + Padding:SetSize(1,5) + SubMenu:AddPanel(Padding) + + local SubMenuTitle = vgui.Create("DLabel") + SubMenuTitle:SetText(" Time:\n") + SubMenuTitle:SetFont("UiBold") + SubMenuTitle:SizeToContents() + SubMenuTitle:SetTextColor(color_black) + + SubMenu:AddPanel(SubMenuTitle) + + for secs, Time in SortedPairs(FAdmin.PlayerActions.commonTimes) do + SubMenu:AddOption(Time, function() submenuItemClick(secs) end) + end +end + +function FAdmin.PlayerActions.addTimeMenu(ItemClick) + local menu = DermaMenu() + + local Padding = vgui.Create("DPanel") + Padding:SetPaintBackgroundEnabled(false) + Padding:SetSize(1,5) + menu:AddPanel(Padding) + + local Title = vgui.Create("DLabel") + Title:SetText(" Time:\n") + Title:SetFont("UiBold") + Title:SizeToContents() + Title:SetTextColor(color_black) + + menu:AddPanel(Title) + + for secs, Time in SortedPairs(FAdmin.PlayerActions.commonTimes) do + menu:AddOption(Time, function() ItemClick(secs) end) + end + menu:Open() +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cleanup/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cleanup/cl_init.lua new file mode 100644 index 0000000..cb1f955 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cleanup/cl_init.lua @@ -0,0 +1,22 @@ +FAdmin.StartHooks["CleanUp"] = function() + FAdmin.Access.AddPrivilege("CleanUp", 2) + FAdmin.Commands.AddCommand("ClearDecals", nil) + FAdmin.Commands.AddCommand("StopSounds", nil) + FAdmin.Commands.AddCommand("CleanUp", nil) + + FAdmin.ScoreBoard.Server:AddServerAction("Clear decals", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button) + RunConsoleCommand("_FAdmin", "ClearDecals") + end) + + FAdmin.ScoreBoard.Server:AddServerAction("Stop all sounds", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button) + RunConsoleCommand("_FAdmin", "StopSounds") + end) + + usermessage.Hook("FAdmin_StopSounds", function() + RunConsoleCommand("stopsound") -- bypass for ConCommand blocking it + end) + + FAdmin.ScoreBoard.Server:AddServerAction("Clean up server", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button) + RunConsoleCommand("_FAdmin", "CleanUp") + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cleanup/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cleanup/sv_init.lua new file mode 100644 index 0000000..8cdb7e2 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/cleanup/sv_init.lua @@ -0,0 +1,44 @@ +local function ClearDecals(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + for _, v in ipairs(player.GetAll()) do + v:ConCommand("r_cleardecals") + end + FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have removed all decals. NOTE: this does NOT make the server ANY less laggy!", "All decals have been removed. NOTE: this does NOT make the server ANY less laggy!", "Removed all decals.") + + return true +end + +local function StopSounds(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + umsg.Start("FAdmin_StopSounds") + umsg.End() + + FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have stopped all sounds", "All sounds have been stopped", "Stopped all sounds") + + return true +end + +local function CleanUp(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + game.CleanUpMap() + FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have cleaned up the map", "The map has been cleaned up", "Cleaned up the map") + + return true +end + +FAdmin.StartHooks["CleanUp"] = function() + FAdmin.Commands.AddCommand("ClearDecals", ClearDecals) + FAdmin.Commands.AddCommand("StopSounds", StopSounds) + FAdmin.Commands.AddCommand("CleanUp", CleanUp) + + local oldCleanup = concommand.GetTable()["gmod_admin_cleanup"] + concommand.Add("gmod_admin_cleanup", function(ply, cmd, args) + if args[1] then return oldCleanup(ply, cmd, args) end + return CleanUp(ply, cmd, args) + end) + + FAdmin.Access.AddPrivilege("CleanUp", 2) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/cl_chatcommands.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/cl_chatcommands.lua new file mode 100644 index 0000000..a9c8e04 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/cl_chatcommands.lua @@ -0,0 +1,109 @@ +local Options = {} +local targets +local colorBackground = Color(0, 0, 0, 200) +local colorHighlight = Color(255, 125, 0, 200) +hook.Add("ChatTextChanged", "FAdmin_Chat_autocomplete", function(text) + if not FAdmin.GlobalSetting.FAdmin then return end + Options = {} + local prefix = GetGlobalString("FAdmin_commandprefix") + prefix = prefix ~= '' and prefix or '/' + + if string.sub(text, 1, 1) ~= prefix then targets = nil return end + + local TExplode = string.Explode(" ", string.sub(text, 2)) + if not TExplode[1] then return end + local Command = string.lower(TExplode[1]) + local Args = table.Copy(TExplode) + Args[1] = nil + Args = table.ClearKeys(Args) + + + local optionsCount = 0 + for k, v in pairs(FAdmin.Commands.List) do + if string.find(string.lower(k), Command, 1, true) ~= 1 then continue end + + Options[prefix .. k] = table.Copy(v.ExtraArgs) + + optionsCount = optionsCount + 1 + end + + local ChatBoxPosX, ChatBoxPosY = chat.GetChatBoxPos() + local ChatBoxWidth = chat.GetChatBoxSize() -- Don't need height + local DidMakeShorter = false + table.sort(Options) + local i = 1 + for k in pairs(Options) do + local Pos = ChatBoxPosY + i * 24 + if Pos + 24 > ScrH() then + Options[k] = nil + DidMakeShorter = true + optionsCount = optionsCount - 1 + end + i = i + 1 + end + + -- Player arguments + local firstVal = table.GetFirstValue(Options) + if optionsCount == 1 and firstVal[#Args] and string.match(firstVal[#Args], ".Player.") then + local players = {} + + for _, v in ipairs(FAdmin.FindPlayer(Args[#Args]) or {}) do + if not IsValid(v) then continue end + table.insert(players, v:Nick()) + end + + targets = table.concat(players, ", ") + end + + local xPos = ChatBoxPosX + ChatBoxWidth + 2 + hook.Add("HUDPaint", "FAdmin_Chat_autocomplete", function() + local j = 0 + for option, args in pairs(Options) do + draw.WordBox(4, xPos, ChatBoxPosY + j * 24, option, "UiBold", colorBackground, color_white) + + for k, arg in pairs(args) do + draw.WordBox(4, xPos + k * 130, ChatBoxPosY + j * 24, arg, "UiBold", colorBackground, color_white) + end + + j = j + 1 + end + + if targets then + draw.WordBox(4, xPos, ChatBoxPosY + j * 24, "Targets: " .. targets, "UiBold", colorHighlight, color_white) + end + + if DidMakeShorter then + draw.WordBox(4, xPos, ChatBoxPosY + j * 24, "...", "UiBold", colorBackground, color_white) + end + end) +end) + +hook.Add("FinishChat", "FAdmin_Chat_autocomplete", function() hook.Remove("HUDPaint", "FAdmin_Chat_autocomplete") end) + +local plyIndex = 1 + +hook.Add("OnChatTab", "FAdmin_Chat_autocomplete", function(text) + if not FAdmin.GlobalSetting.FAdmin then return end + + for command in pairs(Options) do + if string.find(text, " ") == nil then + return string.sub(command, 1, string.find(command, " ")) + elseif string.find(text, " ") then + plyIndex = plyIndex + 1 + + if plyIndex > player.GetCount() then + plyIndex = 1 + end + + return string.sub(command, 1, string.find(command, " ")) .. " " .. string.sub(player.GetAll()[plyIndex]:Nick(), 1, string.find(player.GetAll()[plyIndex]:Nick(), " ")) + end + end +end) + +FAdmin.StartHooks["Chatcommands"] = function() + FAdmin.ScoreBoard.Server:AddServerSetting("Set FAdmin's chat command prefix", "fadmin/icons/message", Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "ServerSetting") end, function() + local prefix = GetGlobalString("FAdmin_commandprefix") + prefix = prefix ~= '' and prefix or '/' + Derma_StringRequest("Set chat command prefix", "Make sure it's only one character!", prefix, fp{RunConsoleCommand, "_Fadmin", "CommandPrefix"}) + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/cl_concommands.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/cl_concommands.lua new file mode 100644 index 0000000..a8c76f3 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/cl_concommands.lua @@ -0,0 +1,28 @@ +local function AutoComplete(command, args) + local autocomplete = {} + args = string.Explode(" ", args) + table.remove(args, 1) --Remove the first space + if args[1] == "" then + for k in pairs(FAdmin.Commands.List) do + table.insert(autocomplete, command .. " " .. k) + end + elseif not args[2] or args[3] then + for k, v in pairs(FAdmin.Commands.List) do + if string.sub(k, 1, string.len(args[1])) == args[1] then + local ExtraArgs = table.concat(v.ExtraArgs, " ") + table.insert(autocomplete, command .. " " .. k .. " " .. ExtraArgs) + end + end + elseif not args[3] and FAdmin.Commands.List[string.lower(args[1])] and FAdmin.Commands.List[string.lower(args[1])].ExtraArgs[1] == "" then + for _, v in ipairs(player.GetAll()) do + if args[2] == "" or table.HasValue(FAdmin.FindPlayer(args[2]) or {}, v) then + table.insert(autocomplete, command .. " " .. args[1] .. " " .. v:Nick()) + end + end + end + table.sort(autocomplete) + return autocomplete +end +concommand.Add("FAdmin", function(ply, cmd, args) + RunConsoleCommand("_" .. cmd, unpack(args)) +end, AutoComplete) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sh_concommands.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sh_concommands.lua new file mode 100644 index 0000000..fae711a --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sh_concommands.lua @@ -0,0 +1,6 @@ +FAdmin.Commands = {} +FAdmin.Commands.List = {} + +function FAdmin.Commands.AddCommand(name, callback, ...) + FAdmin.Commands.List[string.lower(name)] = {callback = callback, ExtraArgs = {...}} +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sv_chatcommands.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sv_chatcommands.lua new file mode 100644 index 0000000..ee84b17 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sv_chatcommands.lua @@ -0,0 +1,52 @@ +local convar = CreateConVar("FAdmin_commandprefix", "/", {FCVAR_SERVER_CAN_EXECUTE}) + +SetGlobalString("FAdmin_commandprefix", convar:GetString()) + +cvars.AddChangeCallback("FAdmin_commandprefix", function() + SetGlobalString("FAdmin_commandprefix", convar:GetString()) +end) + +hook.Add("PlayerSay", "FAdminChatCommands", function(ply, text, Team, dead) + local prefix = convar:GetString() + + if string.sub(text, 1, 1) ~= prefix then return end + + local TExplode = string.Explode(" ", string.sub(text, 2)) + if not TExplode then return end + + for k, v in ipairs(TExplode) do + if string.sub(v, -1) == "," and TExplode[k + 1] then + TExplode[k] = (TExplode[k] or "") .. (TExplode[k + 1] or "") + table.remove(TExplode, k + 1) + end + end + table.ClearKeys(TExplode, false) + + local Command = string.lower(TExplode[1]) + local Args = table.Copy(TExplode) + Args[1] = nil + Args = table.ClearKeys(Args) + if FAdmin.Commands.List[Command] then + local res = {FAdmin.Commands.List[Command].callback(ply, Command, Args)} + hook.Call("FAdmin_OnCommandExecuted", nil, ply, Command, Args, res) + return "" + end +end) + + +FAdmin.StartHooks["Chatcommands"] = function() + convar = convar or GetConVar("FAdmin_commandprefix") + + FAdmin.Commands.AddCommand("CommandPrefix", function(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "ServerSetting") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if not args[1] or string.len(args[1]) ~= 1 then return end + + FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " set FAdmin's chat command prefix to " .. args[1], "FAdmin's chat command prefix has been set to " .. args[1], "Chat command prefix set to" .. args[1]) + + RunConsoleCommand("FAdmin_commandprefix", args[1]) + + FAdmin.SaveSetting("FAdmin_commandprefix", args[1]) + + return true + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sv_concommands.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sv_concommands.lua new file mode 100644 index 0000000..7f2589c --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/commands/sv_concommands.lua @@ -0,0 +1,71 @@ +local function concommand_executed(ply, cmd, args) + if not args[1] then return end + local name = string.lower(args[1]) + + if not name or not FAdmin.Commands.List[name] then + FAdmin.Messages.SendMessage(ply, 1, "Command does not exist!") + return + end + + local args2 = args + table.remove(args2, 1) + for k, v in pairs(args2) do + if string.sub(v, -1) == "," and args2[k + 1] then + args2[k] = args2[k] .. args2[k + 1] + table.remove(args2, k + 1) + end + end + table.ClearKeys(args2) + local res = {FAdmin.Commands.List[name].callback(ply, name, args2)} + hook.Call("FAdmin_OnCommandExecuted", nil, ply, name, args2, res) +end + +local function AutoComplete(command, ...) + local autocomplete = {} + local args = string.Explode(" ", ...) + table.remove(args, 1) --Remove the first space + if args[1] == "" then + for k in pairs(FAdmin.Commands.List) do + table.insert(autocomplete, command .. " " .. k) + end + elseif not args[2] then + for k in pairs(FAdmin.Commands.List) do + if string.sub(k, 1, string.len(args[1])) == args[1] then + table.insert(autocomplete, command .. " " .. k) + end + end + end + table.sort(autocomplete) + return autocomplete +end +concommand.Add("_FAdmin", concommand_executed, AutoComplete) +concommand.Add("FAdmin", concommand_executed, AutoComplete) + +-- DO NOT EDIT THIS, NO MATTER HOW MUCH YOU'VE EDITED FADMIN IT DOESN'T GIVE YOU ANY RIGHT TO CHANGE CREDITS AND/OR REMOVE THE AUTHOR +FAdmin.Commands.AddCommand("FAdminCredits", function(ply, cmd, args) + if ply:SteamID() == "STEAM_0:0:8944068" and args[1] then + local targets = FAdmin.FindPlayer(args[1]) + if not targets or (#targets == 1 and not IsValid(targets[1])) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + for _, target in ipairs(targets) do + if IsValid(target) then + concommand_executed(target, "FAdmin", {"FAdminCredits"}) + end + end + + FAdmin.Messages.SendMessage(ply, 4, "Credits sent!") + return true + end + FAdmin.Messages.SendMessage(ply, 2, "FAdmin by (FPtje) Falco, STEAM_0:0:8944068") + for _, v in ipairs(player.GetAll()) do + if v:SteamID() == "STEAM_0:0:8944068" then + FAdmin.Messages.SendMessage(ply, 4, "(FPtje) Falco is in the server at this moment") + return true + end + end + FAdmin.Messages.SendMessage(ply, 5, "(FPtje) Falco is NOT in the server at this moment") + + return true +end) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/logging/sh_shared.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/logging/sh_shared.lua new file mode 100644 index 0000000..ee89bf9 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/logging/sh_shared.lua @@ -0,0 +1,17 @@ +local logging = CreateConVar("FAdmin_logging", 1, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE}) + +if SERVER then return end +FAdmin.StartHooks["Logging"] = function() + FAdmin.Access.AddPrivilege("Logging", 3) + FAdmin.Commands.AddCommand("Logging", nil) + + FAdmin.ScoreBoard.Server:AddServerSetting(function() return (logging:GetBool() and "Disable" or "Enable") .. " Logging" end, + function() return "fadmin/icons/message", logging:GetBool() and "fadmin/icons/disable" end, + Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "Logging") end, function(button) + button:SetImage2((not logging:GetBool() and "fadmin/icons/disable") or "null") + button:SetText((not logging:GetBool() and "Disable" or "Enable") .. " Logging") + button:GetParent():InvalidateLayout() + + RunConsoleCommand("_Fadmin", "Logging", logging:GetBool() and 0 or 1) + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/logging/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/logging/sv_init.lua new file mode 100644 index 0000000..55d6724 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/logging/sv_init.lua @@ -0,0 +1,161 @@ +local logging +FAdmin.StartHooks["Logging"] = function() + FAdmin.Access.AddPrivilege("Logging", 3) + FAdmin.Commands.AddCommand("Logging", function(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "Logging") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if not tonumber(args[1]) then return end + + local OnOff = (tobool(tonumber(args[1])) and "on") or "off" + FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned logging " .. OnOff, "Logging has been turned " .. OnOff, "Turned logging " .. OnOff) + + RunConsoleCommand("FAdmin_logging", args[1]) + + FAdmin.SaveSetting("FAdmin_logging", args[1]) + + return true, OnOff + end) + logging = GetConVar("FAdmin_logging") +end + +function FAdmin.Log(text) + if not text or text == "" then return end + if not logging or not logging:GetBool() then return end + + ServerLog("[FAdmin] " .. text .. "\n") +end + +hook.Add("PlayerGiveSWEP", "FAdmin_Log", function(ply, class) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Gave themself a " .. (class or "Unknown")) +end) + +hook.Add("PlayerSpawnedSENT", "FAdmin_Log", function(ply, ent) + if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (ent:GetClass() or "Unknown")) +end) + +hook.Add("PlayerSpawnSWEP", "FAdmin_Log", function(ply, class) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (class or "Unknown")) +end) + +hook.Add("PlayerSpawnedProp", "FAdmin_Log", function(ply, model, ent) + if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end + + for _, v in ipairs(player.GetAll()) do + if v:IsAdmin() then + v:PrintMessage(HUD_PRINTCONSOLE, ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown")) + end + end + + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown")) +end) + +hook.Add("PlayerSpawnedNPC", "FAdmin_Log", function(ply, ent) + if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (ent:GetClass() or "Unknown")) +end) + +hook.Add("PlayerSpawnedVehicle", "FAdmin_Log", function(ply, ent) + if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (ent:GetClass() or "Unknown")) +end) + +hook.Add("PlayerSpawnedEffect", "FAdmin_Log", function(ply, model, ent) + if not IsValid(ply) or not ply:IsPlayer() or not model then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown")) +end) + +hook.Add("PlayerSpawnedRagdoll", "FAdmin_Log", function(ply, model, ent) + if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown")) +end) + +hook.Add("CanTool", "FAdmin_Log", function(ply, tr, toolclass) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Attempted to use tool " .. (toolclass or "Unknown")) +end) + +hook.Add("PlayerLeaveVehicle", "FAdmin_Log", function(ply, vehicle) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") exited a " .. (IsValid(vehicle) and vehicle:GetClass() or "Unknown")) +end) + +hook.Add("OnNPCKilled", "FAdmin_Log", function(NPC, Killer, Weapon) + if not IsValid(NPC) then return end + FAdmin.Log(NPC:GetClass() .. " was killed by " .. (IsValid(Killer) and (Killer:IsPlayer() and Killer:Nick() or Killer:GetClass()) or "Unknown") .. " with a " .. (IsValid(Weapon) and Weapon:GetClass() or "Unknown")) +end) + +hook.Add("OnPlayerChangedTeam", "FAdmin_Log", function(ply, oldteam, newteam) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") changed from " .. team.GetName(oldteam) .. " to " .. team.GetName(newteam)) +end) + +hook.Add("WeaponEquip", "FAdmin_Log", function(weapon) + timer.Simple(0, function() + if not IsValid(weapon) then return end + local ply = weapon:GetOwner() + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Attempted to pick up a " .. weapon:GetClass()) + end) +end) + +hook.Add("PlayerDeath", "FAdmin_Log", function(ply, inflictor, Killer) + local Nick = IsValid(ply) and ply:Nick() or "N/A" + local SteamID = IsValid(ply) and ply:SteamID() or "N/A" + local KillerName = IsValid(Killer) and (Killer:IsPlayer() and Killer:Nick() or Killer:GetClass()) or "N/A" + local InflictorName = IsValid(inflictor) and inflictor:GetClass() or "N/A" + + FAdmin.Log(Nick .. " (" .. SteamID .. ") Got killed by " .. KillerName .. " with a " .. InflictorName) +end) + +hook.Add("PlayerSilentDeath", "FAdmin_Log", function(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Got killed silently") +end) + +hook.Add("PlayerDisconnected", "FAdmin_Log", function(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Disconnected") +end) + +hook.Add("PlayerInitialSpawn", "FAdmin_Log", function(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned for the first time") +end) + +hook.Add("PlayerSpawn", "FAdmin_Log", function(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned") +end) + +hook.Add("PlayerSpray", "FAdmin_Log", function(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Sprayed his spray") +end) + +hook.Add("PlayerEnteredVehicle", "FAdmin_Log", function(ply, vehicle) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Entered " .. (IsValid(vehicle) and vehicle:GetClass() or "Unknown")) +end) + +hook.Add("EntityRemoved", "FAdmin_Log", function(ent) + if IsValid(ent) and ent:GetClass() == "prop_physics" then + FAdmin.Log(ent:GetClass() .. "(" .. (ent:GetModel() or "") .. ") Got removed") + end +end) + +hook.Add("PlayerAuthed", "FAdmin_Log", function(ply, SteamID, _) + if not IsValid(ply) then return end + FAdmin.Log(ply:Nick() .. " (" .. (SteamID or "Unknown Steam ID") .. ") is Authed") +end) + +hook.Add("PlayerNoClip", "FAdmin_Log", function(ply) + if not IsValid(ply) or not ply:IsPlayer() then return end + FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Attempted to switch noclip") +end) + +hook.Add("ShutDown", "FAdmin_Log", function() + FAdmin.shuttingDown = true + FAdmin.Log("Server succesfully shut down.") +end) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/cl_init.lua new file mode 100644 index 0000000..cd834cb --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/cl_init.lua @@ -0,0 +1,189 @@ +local showChat = CreateClientConVar("FAdmin_ShowChatNotifications", 1, true, false) + +local HUDNote_c = 0 +local HUDNote_i = 1 +local HUDNotes = {} + +--Notify ripped off the Sandbox notify, changed to my likings +function FAdmin.Messages.AddMessage(MsgType, Message) + local tab = {} + tab.text = Message + tab.recv = SysTime() + tab.velx = 0 + tab.vely = -5 + surface.SetFont("GModNotify") + local w, _ = surface.GetTextSize(Message) + tab.x = ScrW() / 2 + w * 0.5 + (ScrW() / 20) + tab.y = ScrH() + tab.a = 255 + local MsgTypeNames = {"ERROR", "NOTIFY", "QUESTION", "GOOD", "BAD"} + if not MsgTypeNames[MsgType] then return end + tab.col = FAdmin.Messages.MsgTypes[MsgTypeNames[MsgType]].COLOR + + table.insert(HUDNotes, tab) + + HUDNote_c = HUDNote_c + 1 + HUDNote_i = HUDNote_i + 1 + + LocalPlayer():EmitSound("npc/turret_floor/click1.wav", 30, 100) +end + +usermessage.Hook("FAdmin_SendMessage", function(u) FAdmin.Messages.AddMessage(u:ReadShort(), u:ReadString()) end) + + +local function DrawNotice(k, v, i) + local H = ScrH() / 1024 + local x = v.x - 75 * H + local y = v.y - 27 + surface.SetFont("GModNotify") + local w, h = surface.GetTextSize(v.text) + h = h + 16 + local col = v.col + + draw.RoundedBox(4, x - w - h + 24, y - 8, w + h - 16, h, col) + -- Draw Icon + surface.SetDrawColor(255, 255, 255, v.a) + + draw.DrawNonParsedSimpleText(v.text, "GModNotify", x + 1, y + 1, Color(0, 0, 0, v.a * 0.8), TEXT_ALIGN_RIGHT) + draw.DrawNonParsedSimpleText(v.text, "GModNotify", x - 1, y - 1, Color(0, 0, 0, v.a * 0.5), TEXT_ALIGN_RIGHT) + draw.DrawNonParsedSimpleText(v.text, "GModNotify", x + 1, y - 1, Color(0, 0, 0, v.a * 0.6), TEXT_ALIGN_RIGHT) + draw.DrawNonParsedSimpleText(v.text, "GModNotify", x - 1, y + 1, Color(0, 0, 0, v.a * 0.6), TEXT_ALIGN_RIGHT) + draw.DrawNonParsedSimpleText(v.text, "GModNotify", x, y, Color(255, 255, 255, v.a), TEXT_ALIGN_RIGHT) + local ideal_y = ScrH() - (HUDNote_c - i) * h + local ideal_x = ScrW() / 2 + w * 0.5 + (ScrW() / 20) + local timeleft = 6 - (SysTime() - v.recv) + + -- Cartoon style about to go thing + if (timeleft < 0.8) then + ideal_x = ScrW() / 2 + w * 0.5 + 200 + end + + -- Gone! + if (timeleft < 0.5) then + ideal_y = ScrH() + 50 + end + + local spd = RealFrameTime() * 15 + v.y = v.y + v.vely * spd + v.x = v.x + v.velx * spd + local dist = ideal_y - v.y + v.vely = v.vely + dist * spd * 1 + + if (math.abs(dist) < 2 and math.abs(v.vely) < 0.1) then + v.vely = 0 + end + + dist = ideal_x - v.x + v.velx = v.velx + dist * spd * 1 + + if math.abs(dist) < 2 and math.abs(v.velx) < 0.1 then + v.velx = 0 + end + + -- Friction.. kind of FPS independant. + v.velx = v.velx * (0.95 - RealFrameTime() * 8) + v.vely = v.vely * (0.95 - RealFrameTime() * 8) +end + +local function HUDPaint() + if not HUDNotes then return end + local i = 0 + + for k, v in ipairs(HUDNotes) do + if v ~= 0 then + i = i + 1 + DrawNotice(k, v, i) + end + end + + for k, v in ipairs(HUDNotes) do + if v ~= 0 and v.recv + 6 < SysTime() then + HUDNotes[k] = 0 + HUDNote_c = HUDNote_c - 1 + + if HUDNote_c == 0 then + HUDNotes = {} + end + end + end +end +hook.Add("HUDPaint", "FAdmin_MessagePaint", HUDPaint) + +local function ConsoleMessage(um) + MsgC(Color(255, 0, 0, 255), "(FAdmin) ", Color(200, 0, 200, 255), um:ReadString() .. "\n") +end +usermessage.Hook("FAdmin_ConsoleMessage", ConsoleMessage) + + +local red = Color(255, 0, 0) +local white = Color(190, 190, 190) +local brown = Color(102, 51, 0) +local blue = Color(102, 0, 255) + +-- Inserts the instigator into a notification message +local function insertInstigator(res, instigator, _) + table.insert(res, brown) + table.insert(res, FAdmin.PlayerName(instigator)) +end + +-- Inserts the targets into the notification message +local function insertTargets(res, _, targets) + table.insert(res, blue) + table.insert(res, FAdmin.TargetsToString(targets)) +end + +local modMessage = { + instigator = insertInstigator, + you = function(res) table.insert(res, brown) table.insert(res, "you") end, + targets = insertTargets, +} +local function showNotification(notification, instigator, targets, extraInfo) + local res = {red, "[", white, "FAdmin", red, "] "} + + for _, text in pairs(notification.message) do + if modMessage[text] then modMessage[text](res, instigator, targets) continue end + + if string.sub(text, 1, 10) == "extraInfo." then + local id = tonumber(string.sub(text, 11)) + + table.insert(res, notification.extraInfoColors and notification.extraInfoColors[id] or white) + table.insert(res, extraInfo[id]) + continue + end + + table.insert(res, white) + table.insert(res, text) + end + + if showChat:GetBool() then + chat.AddText(unpack(res)) + else + local msgTbl = {} + for i = 8, #res, 2 do table.insert(msgTbl, res[i]) end + + FAdmin.Messages.AddMessage(FAdmin.Messages.MsgTypesByName[notification.msgType], table.concat(msgTbl, "")) + + MsgC(unpack(res)) + Msg("\n") + end +end + +local function receiveNotification() + local id = net.ReadUInt(16) + local notification = FAdmin.Notifications[id] + local instigator = net.ReadEntity() + + local targets = {} + + if notification.hasTarget then + local targetCount = net.ReadUInt(8) + for i = 1, targetCount do + table.insert(targets, net.ReadEntity()) + end + end + + local extraInfo = notification.readExtraInfo and notification.readExtraInfo() + + showNotification(notification, instigator, targets, extraInfo) +end +net.Receive("FAdmin_Notification", receiveNotification) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/sh_shared.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/sh_shared.lua new file mode 100644 index 0000000..4f7ec9c --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/sh_shared.lua @@ -0,0 +1,135 @@ +FAdmin.Messages = {} + +FAdmin.Messages.MsgTypes = { + ERROR = {TEXTURE = "icon16/exclamation.png", COLOR = Color(255,180,0,80)}, + NOTIFY = {TEXTURE = "vgui/notices/error", COLOR = Color(255,255,0,80)}, + QUESTION = {TEXTURE = "vgui/notices/hint", COLOR = Color(0,0,255,80)}, + GOOD = {TEXTURE = "icon16/tick.png", COLOR = Color(0,255,0,80)}, + BAD = {TEXTURE = "icon16/cross.png", COLOR = Color(255,0,0,80)} +} +FAdmin.Messages.MsgTypesByName = { + ERROR = 1, + NOTIFY = 2, + QUESTION = 3, + GOOD = 4, + BAD = 5, +} + +function FAdmin.PlayerName(ply) + if CLIENT and ply == LocalPlayer() then return "you" end + + if isstring(ply) then return ply end + + return isentity(ply) and (ply:EntIndex() == 0 and "Console" or ply:Nick()) or "unknown" +end + +function FAdmin.TargetsToString(targets) + if not istable(targets) then + return FAdmin.PlayerName(targets) + end + + local targetCount = #targets + if targetCount == 0 then + return "no one" + end + + if targetCount == player.GetCount() and targetCount ~= 1 then + return "everyone" + end + + targets = table.Copy(targets) + local names = fn.Map(FAdmin.PlayerName, targets) + + if #names == 1 then + return names[1] + end + + return table.concat(names, ", ", 1, #names - 1) .. " and " .. names[#names] +end + +FAdmin.Notifications = {} + +local validNotification = tc.checkTable{ + -- A name to identify the notification by + name = + tc.addHint( + isstring, + "The name must be a string!" + ), + + -- Whether the notification applies to some kind of target + hasTarget = + tc.addHint( + tc.optional(isbool), + "hasTarget must either be true, false or nil!" + ), + + -- Who receives the notification. Can be either one of the list or a function that returns a table of players + receivers = + tc.addHint( + fn.FOr{tc.client, isfunction, tc.oneOf{"everyone", "admins", "superadmins", "self", "targets", "involved", "involved+admins", "involved+superadmins"}}, + "receivers must either be a function returning a table of players or one of 'admins', 'superadmins', 'everyone', 'self', 'targets', 'involved', 'involved+admins', 'involved+superadmins'" + ), + + -- A table containing the message in parts. There are special strings + message = + tc.addHint( + tc.tableOf(isstring), + "The message field must be a table of strings! with special strings 'targets', 'you', 'instigator', 'extraInfo.#', with # a number." + ), + + -- The message type when chat notifications are disabled. NOTIFY by default + msgType = + tc.default( + "NOTIFY", + tc.addHint( + tc.oneOf{"ERROR", "NOTIFY", "QUESTION", "GOOD", "BAD"}, "msgType must be one of 'ERROR', 'NOTIFY', 'QUESTION', 'GOOD', 'BAD'" + ) + ), + + -- A function that writes extra data in the net message + writeExtraInfo = + tc.addHint( + tc.optional(isfunction), + "writeExtraInfo must be a function" + ), + + -- A function that reads the written data, formats it and puts it in a table + readExtraInfo = + tc.addHint( + tc.optional(isfunction), + "writeExtraInfo must be a function" + ), + + -- When using extra information, this table contains the colours of the extraInfo messages + extraInfoColors = + tc.addHint( + tc.optional(tc.tableOf(tc.iscolor)), + "extraInfoColors must be a table of colours!" + ), + + -- Whether the notification is to be logged to console + logging = + tc.default(true, + tc.addHint( + isbool, + "logging must be a boolean!" + ) + ), +} + + +FAdmin.NotificationNames = {} + +function FAdmin.Messages.RegisterNotification(tbl) + local correct, err = validNotification(tbl) + + if not correct then + error(string.format("Incorrect notification format for notification '%s'!\n\n%s", istable(tbl) and tbl.name or "unknown", err), 2) + end + + local key = table.insert(FAdmin.Notifications, tbl) + FAdmin.NotificationNames[tbl.name] = key + + return key +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/sv_init.lua new file mode 100644 index 0000000..9e478a1 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/messaging/sv_init.lua @@ -0,0 +1,137 @@ +util.AddNetworkString("FAdmin_Notification") + +function FAdmin.Messages.SendMessage(ply, MsgType, text) + if ply:EntIndex() == 0 then + ServerLog("FAdmin: " .. text .. "\n") + print("FAdmin: " .. text) + + return + end + + umsg.Start("FAdmin_SendMessage", ply) + umsg.Short(MsgType) + umsg.String(text) + umsg.End() + ply:PrintMessage(HUD_PRINTCONSOLE, text) +end + +function FAdmin.Messages.SendMessageAll(text, MsgType) + FAdmin.Log("FAdmin message to everyone: " .. text) + umsg.Start("FAdmin_SendMessage") + umsg.Short(MsgType) + umsg.String(text) + umsg.End() + + for _, ply in ipairs(player.GetAll()) do + ply:PrintMessage(HUD_PRINTCONSOLE, text) + end +end + +function FAdmin.Messages.ConsoleNotify(ply, message) + umsg.Start("FAdmin_ConsoleMessage", ply) + umsg.String(message) + umsg.End() +end + +function FAdmin.Messages.ActionMessage(ply, target, messageToPly, MessageToTarget, LogMSG) + if not target then return end + local Targets = (target.IsPlayer and target:IsPlayer() and target:Nick()) or "" + + local plyNick = IsValid(ply) and ply:IsPlayer() and ply:Nick() or "Console" + local plySteamID = IsValid(ply) and ply:IsPlayer() and ply:SteamID() or "Console" + local bad = false + + if ply ~= target then + if istable(target) then + if table.IsEmpty(target) then Targets = "no one" bad = true end + for k, v in ipairs(target) do + local suffix = ((k == #target-1) and " and ") or (k ~= #target and ", ") or "" + local Name = (v == ply and "yourself") or v:Nick() + + if v ~= ply then FAdmin.Messages.SendMessage(v, 2, string.format(MessageToTarget, plyNick)) end + Targets = Targets .. Name .. suffix + end + else + FAdmin.Messages.SendMessage(target, 2, string.format(MessageToTarget, plyNick)) + end + + FAdmin.Messages.SendMessage(ply, bad and 1 or 4, string.format(messageToPly, Targets)) + + else + FAdmin.Messages.SendMessage(ply, bad and 1 or 4, string.format(messageToPly, "yourself")) + end + + local action = plyNick .. " (" .. plySteamID .. ") " .. string.format(LogMSG, Targets:gsub("yourself", "themselves")) + FAdmin.Log("FAdmin Action: " .. action) + + local haspriv = fn.Partial(fn.Flip(FAdmin.Access.PlayerHasPrivilege), "SeeAdmins") + local plys = fn.Filter(haspriv, player.GetAll()) + if table.IsEmpty(plys) then return end + FAdmin.Messages.ConsoleNotify(plys, action) +end + + +local function logNotification(notification, instigator, targets, extraInfo) + local msgs = table.Copy(notification.message) + + local function replace(val) + if val == "instigator" then return FAdmin.PlayerName(instigator) end + if val == "targets" then return FAdmin.TargetsToString(targets) end + if string.sub(val, 1, 10) == "extraInfo." then return tostring(extraInfo[tonumber(string.sub(val, 11))]) end + + return val + end + + fn.Map(replace, msgs) + + FAdmin.Log(table.concat(msgs)) +end + +local receiversToPlayers -- allows usage of variable inside +receiversToPlayers = { + everyone = player.GetAll, + admins = function() return table.ClearKeys(fn.Filter(tc.player.IsAdmin, player.GetAll())) end, + superadmins = function() return table.ClearKeys(fn.Filter(tc.player.IsSuperAdmin, player.GetAll())) end, + self = fn.Id, + targets = function(_, t) return t end, + involved = function(i, t) local res = table.Copy(istable(t) and t or {t}) table.insert(res, i) return res end, + ["involved+admins"] = function(i, t) return table.Add(receiversToPlayers.admins(i, t), receiversToPlayers.involved(i, t)) end, + ["involved+superadmins"] = function(i, t) return table.Add(receiversToPlayers.superadmins(i, t), receiversToPlayers.involved(i, t)) end, +} +function FAdmin.Messages.FireNotification(name, instigator, targets, extraInfo) + local notId = FAdmin.NotificationNames[name] + + if not notId then + error(string.format("Notification '%s' does not exist!", name), 2) + end + + local notification = FAdmin.Notifications[notId] + local receivers = receiversToPlayers[notification.receivers] + receivers = receivers and receivers(instigator, targets) or notification.receivers(instigator, targets) + + local targetCount = istable(targets) and #targets or not IsValid(targets) and 0 or 1 + + net.Start("FAdmin_Notification") + net.WriteUInt(notId, 16) + + net.WriteEntity(instigator) + + if notification.hasTarget then + net.WriteUInt(targetCount, 8) + + if istable(targets) then + for _, t in ipairs(targets) do + net.WriteEntity(t) + end + else + net.WriteEntity(targets) + end + end + + if notification.writeExtraInfo then notification.writeExtraInfo(extraInfo) end + net.Send(receivers) + + if notification.logging then + logNotification(notification, instigator, targets, extraInfo) + end +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/motd/sh_shared.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/motd/sh_shared.lua new file mode 100644 index 0000000..9f87a85 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/motd/sh_shared.lua @@ -0,0 +1,85 @@ +local MOTDPage = CreateConVar("_FAdmin_MOTDPage", "default", {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE}) + +if CLIENT then -- I can't be bothered to make a cl_init when there's a shared file with just one line in it. + FAdmin.StartHooks["MOTD"] = function() + FAdmin.ScoreBoard.Server:AddServerAction("Place MOTD", "fadmin/icons/motd", Color(155, 0, 0, 255), function(ply) return ply:IsSuperAdmin() end, function() + RunConsoleCommand("_FAdmin", "CreateMOTD") + end) + + FAdmin.ScoreBoard.Server:AddServerSetting("Set MOTD page", "fadmin/icons/motd", Color(0, 0, 155, 255), function(ply) return ply:IsSuperAdmin() end, function() + local Window = vgui.Create("DFrame") + Window:SetTitle("Set MOTD page") + Window:SetDraggable(false) + Window:ShowCloseButton(false) + Window:SetBackgroundBlur(true) + Window:SetDrawOnTop(true) + + local InnerPanel = vgui.Create("DPanel", Window) + InnerPanel:SetPaintBackground(false) -- clear background + + local Text = vgui.Create("DLabel", InnerPanel) + Text:SetText("Set the MOTD page. Click default to reset the MOTD to default.") + Text:SizeToContents() + Text:SetContentAlignment(5) + Text:SetTextColor(color_white) + + local TextEntry = vgui.Create("DTextEntry", InnerPanel) + TextEntry:SetText(MOTDPage:GetString()) + TextEntry.OnEnter = function() Window:Close() RunConsoleCommand("_FAdmin", "motdpage", TextEntry:GetValue()) end + function TextEntry:OnFocusChanged(changed) + self:RequestFocus() + self:SelectAllText(true) + end + + local ButtonPanel = vgui.Create("DPanel", Window) + ButtonPanel:SetPaintBackground(false) -- clear background + ButtonPanel:SetTall(30) + + local Button = vgui.Create("DButton", ButtonPanel) + Button:SetText("OK") + Button:SizeToContents() + Button:SetTall(20) + Button:SetWide(Button:GetWide() + 20) + Button:SetPos(5, 5) + + Button.DoClick = function() + Window:Close() + RunConsoleCommand("_FAdmin", "motdpage", TextEntry:GetValue()) + end + + local ButtonDefault = vgui.Create("DButton", ButtonPanel) + ButtonDefault:SetText("Default") + ButtonDefault:SizeToContents() + ButtonDefault:SetTall(20) + ButtonDefault:SetWide(Button:GetWide() + 20) + ButtonDefault:SetPos(5, 5) + ButtonDefault.DoClick = function() Window:Close() RunConsoleCommand("_FAdmin", "motdpage", "default") end + ButtonDefault:MoveRightOf(Button, 5) + + local ButtonCancel = vgui.Create("DButton", ButtonPanel) + ButtonCancel:SetText("Cancel") + ButtonCancel:SizeToContents() + ButtonCancel:SetTall(20) + ButtonCancel:SetWide(Button:GetWide() + 20) + ButtonCancel:SetPos(5, 5) + ButtonCancel.DoClick = function() Window:Close() end + ButtonCancel:MoveRightOf(ButtonDefault, 5) + + ButtonPanel:SetWide(Button:GetWide() + 5 + ButtonCancel:GetWide() + 10 + ButtonDefault:GetWide() + 5) + + local w, h = Text:GetSize() + w = math.max(w, 400) + Window:SetSize(w + 50, h + 25 + 75 + 10) + Window:Center() + InnerPanel:StretchToParent(5, 25, 5, 45) + Text:StretchToParent(5, 5, 5, 35) + TextEntry:StretchToParent(5, nil, 5, nil) + TextEntry:AlignBottom(5) + TextEntry:RequestFocus() + ButtonPanel:CenterHorizontal() + ButtonPanel:AlignBottom(8) + Window:MakePopup() + Window:DoModal() + end) + end +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/motd/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/motd/sv_init.lua new file mode 100644 index 0000000..5ad36ab --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/motd/sv_init.lua @@ -0,0 +1,91 @@ +FAdmin.MOTD = {} +local MOTDPage + +sql.Query([[CREATE TABLE IF NOT EXISTS FADMIN_MOTD( + 'map' TEXT NOT NULL PRIMARY KEY, + x INTEGER NOT NULL, + y INTEGER NOT NULL, + z INTEGER NOT NULL, + pitch INTEGER NOT NULL, + yaw INTEGER NOT NULL, + roll INTEGER NOT NULL + );]]) + +local MOTD = sql.Query("SELECT * FROM FADMIN_MOTD WHERE LOWER(map) = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";") + +hook.Add("InitPostEntity", "PlaceMOTD", function() + if not MOTD or (not MOTD[1] and not MOTD["1"]) then return end + MOTD = MOTD[1] or MOTD["1"] + + local ent = ents.Create("fadmin_motd") + ent:SetPos(Vector(MOTD.x, MOTD.y, MOTD.z)) + ent:SetAngles(Angle(MOTD.pitch % 360, MOTD.yaw % 360, MOTD.roll % 360)) + ent:Spawn() + ent:Activate() + + if file.Exists("FAdmin/CurMOTDPage.txt", "DATA") and file.Read("FAdmin/CurMOTDPage.txt", "DATA") ~= "" then + game.ConsoleCommand("_FAdmin_MOTDPage \"" .. file.Read("FAdmin/CurMOTDPage.txt", "DATA") .. "\"\n") + end +end) + +function FAdmin.MOTD.SaveMOTD(ent, ply) + local pos = ent:GetPos() + local ang = ent:GetAngles() + + local map, x, y, z, pitch, yaw, roll = + string.lower(game.GetMap()), + pos.x, pos.y, pos.z, + ang.p, ang.y, ang.r + if MOTD then + sql.Query([[UPDATE FADMIN_MOTD SET ]] + .. "x = " .. MySQLite.SQLStr(x) .. ", " + .. "y = " .. MySQLite.SQLStr(y) .. ", " + .. "z = " .. MySQLite.SQLStr(z) .. ", " + .. "pitch = " .. MySQLite.SQLStr(pitch) .. ", " + .. "yaw = " .. MySQLite.SQLStr(yaw) .. ", " + .. "roll = " .. MySQLite.SQLStr(roll) + .. " WHERE map = " .. MySQLite.SQLStr(map) .. ";") + else + sql.Query([[INSERT INTO FADMIN_MOTD VALUES(]] + .. MySQLite.SQLStr(map) .. ", " + .. MySQLite.SQLStr(x) .. ", " + .. MySQLite.SQLStr(y) .. ", " + .. MySQLite.SQLStr(z) .. ", " + .. MySQLite.SQLStr(pitch) .. ", " + .. MySQLite.SQLStr(yaw) .. ", " + .. MySQLite.SQLStr(roll) + .. ");") + end + FAdmin.Messages.SendMessage(ply, 4, "MOTD position saved!") +end + +function FAdmin.MOTD.RemoveMOTD(ent, ply) + sql.Query("DELETE FROM FADMIN_MOTD WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";") + FAdmin.Messages.SendMessage(ply, 4, "MOTD removed!") +end + +function FAdmin.MOTD.SetMOTDPage(ply, cmd, args) + if not args[1] then + FAdmin.Messages.SendMessage(ply, 4, "MOTD is set to: " .. MOTDPage:GetString()) + return false + end + if ply:EntIndex() ~= 0 and (not ply.IsSuperAdmin or not ply:IsSuperAdmin()) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + RunConsoleCommand("_FAdmin_MOTDPage", args[1]) + file.Write("FAdmin/CurMOTDPage.txt", args[1]) + + return true, args[1] +end + +local function CreateMOTD(ply) + if IsValid(ply) and not ply:IsSuperAdmin() then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + local MOTDEnt = ents.Create("fadmin_motd") + MOTDEnt:SpawnFunction(ply, ply:GetEyeTrace()) + + return true, MOTDEnt +end + +FAdmin.StartHooks["MOTD"] = function() + MOTDPage = GetConVar("_FAdmin_MOTDPage") + FAdmin.Commands.AddCommand("MOTDPage", FAdmin.MOTD.SetMOTDPage) + FAdmin.Commands.AddCommand("CreateMOTD", CreateMOTD) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/pickupplayers/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/pickupplayers/cl_init.lua new file mode 100644 index 0000000..19763f9 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/pickupplayers/cl_init.lua @@ -0,0 +1,21 @@ +local AdminsCanPickUpPlayers = CreateConVar("AdminsCanPickUpPlayers", 1, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE}) +local PlayersCanPickUpPlayers = CreateConVar("PlayersCanPickUpPlayers", 0, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE}) + +FAdmin.StartHooks["PickUpPlayers"] = function() + FAdmin.Access.AddPrivilege("PickUpPlayers", 2) + FAdmin.ScoreBoard.Server:AddPlayerAction(function() return (AdminsCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Admin>Player pickup" end, + function() return "fadmin/icons/pickup", AdminsCanPickUpPlayers:GetBool() and "fadmin/icons/disable" end, Color(0, 155, 0, 255), function(ply) return ply:IsSuperAdmin() end, function(button) + button:SetImage2((not AdminsCanPickUpPlayers:GetBool() and "fadmin/icons/disable") or "null") + button:SetText((not AdminsCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Admin>Player pickup") + button:GetParent():InvalidateLayout() + RunConsoleCommand("_FAdmin", "AdminsCanPickUpPlayers", AdminsCanPickUpPlayers:GetBool() and "0" or "1") + end) + + FAdmin.ScoreBoard.Server:AddPlayerAction(function() return (PlayersCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Player>Player pickup" end, + function() return "fadmin/icons/pickup", PlayersCanPickUpPlayers:GetBool() and "fadmin/icons/disable" end, Color(0, 155, 0, 255), function(ply) return ply:IsSuperAdmin() end, function(button) + button:SetImage2((not PlayersCanPickUpPlayers:GetBool() and "fadmin/icons/disable") or "null") + button:SetText((not PlayersCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Player>Player pickup") + button:GetParent():InvalidateLayout() + RunConsoleCommand("_FAdmin", "PlayersCanPickUpPlayers", PlayersCanPickUpPlayers:GetBool() and "0" or "1") + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/pickupplayers/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/pickupplayers/sv_init.lua new file mode 100644 index 0000000..4ded46f --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/pickupplayers/sv_init.lua @@ -0,0 +1,58 @@ +local AdminsCanPickUpPlayers = CreateConVar("AdminsCanPickUpPlayers", 1, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE}) +local PlayersCanPickUpPlayers = CreateConVar("PlayersCanPickUpPlayers", 0, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE}) + +hook.Add("PhysgunPickup", "FAdmin_PickUpPlayers", function(ply, ent) + if not IsValid(ent) or not ent:IsPlayer() then return end + + if PlayersCanPickUpPlayers:GetBool() or AdminsCanPickUpPlayers:GetBool() and + FAdmin.Access.PlayerHasPrivilege(ply, "PickUpPlayers", ent) and tobool(ply:GetInfo("cl_pickupplayers")) then + ent:SetMoveType(MOVETYPE_NONE) + ent:Freeze(true) + return true + end +end) + +hook.Add("PhysgunDrop", "FAdmin_PickUpPlayers", function(ply, ent) + if IsValid(ent) and ent:IsPlayer() then + ent:SetMoveType(MOVETYPE_WALK) + ent:Freeze(false) + end +end) + +local function ChangeAdmin(ply, cmd, args) + if not ply:IsSuperAdmin() then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if not args[1] then return false end + + local Value = tonumber(args[1]) + if Value ~= 1 and Value ~= 0 then return false end + RunConsoleCommand("AdminsCanPickUpPlayers", Value) + + FAdmin.SaveSetting("AdminsCanPickUpPlayers", Value) + + local OnOff = (tobool(Value) and "on") or "off" + FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned Admin>Player pickup " .. OnOff, "Admin>Player pickup has been turned " .. OnOff, "Turned Admin>Player pickup " .. OnOff) + + return true, OnOff +end + +local function ChangeUser(ply, cmd, args) + if not ply:IsSuperAdmin() then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if not args[1] then return false end + + local Value = tonumber(args[1]) + if Value ~= 1 and Value ~= 0 then return false end + RunConsoleCommand("PlayersCanPickUpPlayers", Value) + + FAdmin.SaveSetting("PlayersCanPickUpPlayers", Value) + + local OnOff = (tobool(Value) and "on") or "off" + FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned Player>Player pickup " .. OnOff, "Player>Player pickup has been turned " .. OnOff, "Turned Player>Player pickup " .. OnOff) + + return true, OnOff +end + +FAdmin.StartHooks["PickUpPlayers"] = function() + FAdmin.Access.AddPrivilege("PickUpPlayers", 2) + FAdmin.Commands.AddCommand("AdminsCanPickUpPlayers", ChangeAdmin) + FAdmin.Commands.AddCommand("PlayersCanPickUpPlayers", ChangeUser) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/changeteam/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/changeteam/cl_init.lua new file mode 100644 index 0000000..dc9ac33 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/changeteam/cl_init.lua @@ -0,0 +1,37 @@ +FAdmin.StartHooks["zzSetTeam"] = function() + FAdmin.Messages.RegisterNotification{ + name = "setteam", + hasTarget = true, + message = {"instigator", " set the team of ", "targets", " to ", "extraInfo.1"}, + readExtraInfo = function() + return {team.GetName(net.ReadUInt(16))} + end, + extraInfoColors = {Color(255, 102, 0)} + } + + FAdmin.Access.AddPrivilege("SetTeam", 2) + FAdmin.Commands.AddCommand("SetTeam", nil, "", "") + + FAdmin.ScoreBoard.Player:AddActionButton("Set team", "fadmin/icons/changeteam", Color(0, 200, 0, 255), + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SetTeam", ply) end, function(ply, button) + local menu = DermaMenu() + + local Padding = vgui.Create("DPanel") + Padding:SetPaintBackgroundEnabled(false) + Padding:SetSize(1,5) + menu:AddPanel(Padding) + + local Title = vgui.Create("DLabel") + Title:SetText(" Teams:\n") + Title:SetFont("UiBold") + Title:SizeToContents() + Title:SetTextColor(color_black) + + menu:AddPanel(Title) + for k, v in SortedPairsByMemberValue(team.GetAllTeams(), "Name") do + local uid = ply:UserID() + menu:AddOption(v.Name, function() RunConsoleCommand("_FAdmin", "setteam", uid, k) end) + end + menu:Open() + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/changeteam/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/changeteam/sv_init.lua new file mode 100644 index 0000000..d47ecba --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/changeteam/sv_init.lua @@ -0,0 +1,67 @@ +local function checkDarkRP(ply, target, t) + if not DarkRP then return true end + + local TEAM = RPExtraTeams[t] + if not TEAM then return true end + + if TEAM.customCheck then + local ret = TEAM.customCheck(target) + if ret ~= nil and not (ply:IsAdmin() and GAMEMODE.Config.adminBypassJobRestrictions) then return ret end + end + + local hookValue = hook.Call("playerCanChangeTeam", nil, target, t, true) + if hookValue == false then return false end + + local a = TEAM.admin + if a > 0 and not target:IsAdmin() + or a > 1 and not target:IsSuperAdmin() + then return false end + + return true +end + +local function SetTeam(ply, cmd, args) + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + local targetsSet = {} + for k, v in pairs(team.GetAllTeams()) do + if k == tonumber(args[2]) or string.lower(v.Name) == string.lower(args[2] or "") then + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "SetTeam", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + local setTeam = target.changeTeam or target.SetTeam -- DarkRP compatibility + if IsValid(target) and checkDarkRP(ply, target, k) then + setTeam(target, k, true) + table.insert(targetsSet, target) + end + end + + if not table.IsEmpty(targetsSet) then + FAdmin.Messages.FireNotification("setteam", ply, targetsSet, {k}) + else + FAdmin.Messages.SendMessage(ply, 1, "Could not set team") + end + + break + end + end + + return true, targets +end + +FAdmin.StartHooks["zzSetTeam"] = function() + FAdmin.Messages.RegisterNotification{ + name = "setteam", + hasTarget = true, + receivers = "everyone", + writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end, + message = {"instigator", " set the team of ", "targets", " to ", "extraInfo.1"}, + } + + FAdmin.Commands.AddCommand("SetTeam", SetTeam) + + FAdmin.Access.AddPrivilege("SetTeam", 2) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/chatmute/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/chatmute/cl_init.lua new file mode 100644 index 0000000..48f38e0 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/chatmute/cl_init.lua @@ -0,0 +1,47 @@ +FAdmin.StartHooks["Chatmute"] = function() + FAdmin.Messages.RegisterNotification{ + name = "chatmute", + hasTarget = true, + message = {"instigator", " chat muted ", "targets", " ", "extraInfo.1"}, + readExtraInfo = function() + local time = net.ReadUInt(16) + + return {time == 0 and FAdmin.PlayerActions.commonTimes[time] or string.format("for %s", FAdmin.PlayerActions.commonTimes[time] or (time .. " seconds"))} + end + } + + FAdmin.Messages.RegisterNotification{ + name = "chatunmute", + hasTarget = true, + message = {"instigator", " chat unmuted ", "targets"}, + } + + FAdmin.Access.AddPrivilege("Chatmute", 2) + FAdmin.Commands.AddCommand("Chatmute", nil, "") + FAdmin.Commands.AddCommand("UnChatmute", nil, "") + + FAdmin.ScoreBoard.Player:AddActionButton(function(ply) + if ply:FAdmin_GetGlobal("FAdmin_chatmuted") then return "Unmute chat" end + return "Mute chat" + end, function(ply) + if ply:FAdmin_GetGlobal("FAdmin_chatmuted") then return "fadmin/icons/chatmute" end + return "fadmin/icons/chatmute", "fadmin/icons/disable" + end, Color(255, 130, 0, 255), + + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Chatmute", ply) end, function(ply, button) + if not ply:FAdmin_GetGlobal("FAdmin_chatmuted") then + FAdmin.PlayerActions.addTimeMenu(function(secs) + RunConsoleCommand("_FAdmin", "chatmute", ply:UserID(), secs) + button:SetImage2("null") + button:SetText("Unmute chat") + button:GetParent():InvalidateLayout() + end) + else + RunConsoleCommand("_FAdmin", "UnChatmute", ply:UserID()) + end + + button:SetImage2("fadmin/icons/disable") + button:SetText("Mute chat") + button:GetParent():InvalidateLayout() + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/chatmute/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/chatmute/sv_init.lua new file mode 100644 index 0000000..1eb9f38 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/chatmute/sv_init.lua @@ -0,0 +1,75 @@ +local function MuteChat(ply, cmd, args) + if not args[1] then return false end + + local targets = FAdmin.FindPlayer(args[1]) or {} + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + local time = tonumber(args[2] or 0) + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Chatmute", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) and not target:FAdmin_GetGlobal("FAdmin_chatmuted") then + target:FAdmin_SetGlobal("FAdmin_chatmuted", true) + + if time == 0 then continue end + + timer.Simple(time, function() + if not IsValid(target) or not target:FAdmin_GetGlobal("FAdmin_chatmuted") then return false end + target:FAdmin_SetGlobal("FAdmin_chatmuted", false) + end) + end + end + + FAdmin.Messages.FireNotification("chatmute", ply, targets, {time}) + + return true, targets, time +end + +local function UnMuteChat(ply, cmd, args) + if not args[1] then return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Chatmute", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) and target:FAdmin_GetGlobal("FAdmin_chatmuted") then + target:FAdmin_SetGlobal("FAdmin_chatmuted", false) + end + end + FAdmin.Messages.FireNotification("chatunmute", ply, targets) + + return true, targets +end + +FAdmin.StartHooks["Chatmute"] = function() + FAdmin.Messages.RegisterNotification{ + name = "chatmute", + hasTarget = true, + receivers = "involved+admins", + writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end, + message = {"instigator", " chat muted ", "targets", " ", "extraInfo.1"}, + } + + FAdmin.Messages.RegisterNotification{ + name = "chatunmute", + hasTarget = true, + receivers = "involved+admins", + message = {"instigator", " chat unmuted ", "targets"}, + } + + FAdmin.Commands.AddCommand("Chatmute", MuteChat) + FAdmin.Commands.AddCommand("UnChatmute", UnMuteChat) + + FAdmin.Access.AddPrivilege("Chatmute", 2) +end + +hook.Add("PlayerSay", "FAdmin_Chatmute", function(ply, text, Team, dead) + if ply:FAdmin_GetGlobal("FAdmin_chatmuted") then return "" end +end) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/cloak/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/cloak/cl_init.lua new file mode 100644 index 0000000..130c5c7 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/cloak/cl_init.lua @@ -0,0 +1,26 @@ +FAdmin.StartHooks["zz_Cloak"] = function() + FAdmin.Access.AddPrivilege("Cloak", 2) + FAdmin.Commands.AddCommand("Cloak", nil, "") + FAdmin.Commands.AddCommand("Uncloak", nil, "") + + FAdmin.ScoreBoard.Player:AddActionButton(function(ply) + if ply:FAdmin_GetGlobal("FAdmin_cloaked") then return "Uncloak" end + return "Cloak" + end, function(ply) + if ply:FAdmin_GetGlobal("FAdmin_cloaked") then return "fadmin/icons/cloak", "fadmin/icons/disable" end + return "fadmin/icons/cloak" + end, Color(0, 200, 0, 255), + + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Cloak", ply) end, function(ply, button) + if not ply:FAdmin_GetGlobal("FAdmin_cloaked") then + RunConsoleCommand("_FAdmin", "Cloak", ply:UserID()) + else + RunConsoleCommand("_FAdmin", "Uncloak", ply:UserID()) + end + + if not ply:FAdmin_GetGlobal("FAdmin_cloaked") then button:SetImage2("fadmin/icons/disable") button:SetText("Uncloak") button:GetParent():InvalidateLayout() return end + button:SetImage2("null") + button:SetText("Cloak") + button:GetParent():InvalidateLayout() + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/cloak/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/cloak/sv_init.lua new file mode 100644 index 0000000..0f340bb --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/cloak/sv_init.lua @@ -0,0 +1,89 @@ +local CloakThink + +local function Cloak(ply, cmd, args) + local targets = FAdmin.FindPlayer(args[1]) or {ply} + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Cloak", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) and not target:FAdmin_GetGlobal("FAdmin_cloaked") then + target:FAdmin_SetGlobal("FAdmin_cloaked", true) + target:SetNoDraw(true) + for _, v in ipairs(target:GetWeapons()) do + v:SetNoDraw(true) + end + + for _, v in ipairs(ents.FindByClass("physgun_beam")) do + if v:GetParent() == target then + v:SetNoDraw(true) + end + end + + hook.Add("Think", "FAdmin_Cloak", CloakThink) + end + end + FAdmin.Messages.ActionMessage(ply, targets, "You have cloaked %s", "You were cloaked by %s", "Cloaked %s") + + return true, targets +end + +local function UnCloak(ply, cmd, args) + local targets = FAdmin.FindPlayer(args[1]) or {ply} + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Cloak", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) and target:FAdmin_GetGlobal("FAdmin_cloaked") then + target:FAdmin_SetGlobal("FAdmin_cloaked", false) + + target:SetNoDraw(false) + + for _, v in ipairs(target:GetWeapons()) do + v:SetNoDraw(false) + end + + for _, v in ipairs(ents.FindByClass("physgun_beam")) do + if v:GetParent() == target then + v:SetNoDraw(false) + end + end + + target.FAdmin_CloakWeapon = nil + + local RemoveThink = true + for _, v in ipairs(player.GetAll()) do + if v:FAdmin_GetGlobal("FAdmin_cloaked") then + RemoveThink = false + break + end + end + if RemoveThink then hook.Remove("Think", "FAdmin_Cloak") end + end + end + FAdmin.Messages.ActionMessage(ply, targets, "You have uncloaked %s", "You were uncloaked by %s", "Uncloaked %s") + + return true, targets +end + +FAdmin.StartHooks["Cloak"] = function() + FAdmin.Commands.AddCommand("Cloak", Cloak) + FAdmin.Commands.AddCommand("Uncloak", UnCloak) + + FAdmin.Access.AddPrivilege("Cloak", 2) +end + +function CloakThink() + for _, v in ipairs(player.GetAll()) do + local ActiveWeapon = v:GetActiveWeapon() + if v:FAdmin_GetGlobal("FAdmin_cloaked") and ActiveWeapon:IsValid() and ActiveWeapon ~= v.FAdmin_CloakWeapon then + v.FAdmin_CloakWeapon = ActiveWeapon + ActiveWeapon:SetNoDraw(true) + + if ActiveWeapon:GetClass() == "weapon_physgun" then + for a,b in ipairs(ents.FindByClass("physgun_beam")) do + if b:GetParent() == v then + b:SetNoDraw(true) + end + end + end + end + end +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/freeze/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/freeze/cl_init.lua new file mode 100644 index 0000000..0b80780 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/freeze/cl_init.lua @@ -0,0 +1,48 @@ +FAdmin.StartHooks["Freeze"] = function() + FAdmin.Messages.RegisterNotification{ + name = "freeze", + hasTarget = true, + message = {"instigator", " froze ", "targets", " ", "extraInfo.1"}, + readExtraInfo = function() + local time = net.ReadUInt(16) + + return {time == 0 and FAdmin.PlayerActions.commonTimes[time] or string.format("for %s", FAdmin.PlayerActions.commonTimes[time] or (time .. " seconds"))} + end + } + + FAdmin.Messages.RegisterNotification{ + name = "unfreeze", + hasTarget = true, + message = {"instigator", " unfroze ", "targets"}, + } + + + FAdmin.Access.AddPrivilege("Freeze", 2) + FAdmin.Commands.AddCommand("freeze", nil, "") + FAdmin.Commands.AddCommand("unfreeze", nil, "") + + FAdmin.ScoreBoard.Player:AddActionButton(function(ply) + if ply:FAdmin_GetGlobal("FAdmin_frozen") then return "Unfreeze" end + return "Freeze" + end, function(ply) + if ply:FAdmin_GetGlobal("FAdmin_frozen") then return "fadmin/icons/freeze", "fadmin/icons/disable" end + return "fadmin/icons/freeze" + end, Color(255, 130, 0, 255), + + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Freeze", ply) end, function(ply, button) + if not ply:FAdmin_GetGlobal("FAdmin_frozen") then + FAdmin.PlayerActions.addTimeMenu(function(secs) + RunConsoleCommand("_FAdmin", "freeze", ply:UserID(), secs) + button:SetImage2("fadmin/icons/disable") + button:SetText("Unfreeze") + button:GetParent():InvalidateLayout() + end) + else + RunConsoleCommand("_FAdmin", "unfreeze", ply:UserID()) + end + + button:SetImage2("null") + button:SetText("Freeze") + button:GetParent():InvalidateLayout() + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/freeze/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/freeze/sv_init.lua new file mode 100644 index 0000000..5a02c4e --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/freeze/sv_init.lua @@ -0,0 +1,79 @@ +local function Freeze(ply, cmd, args) + if not args[1] then return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + local time = tonumber(args[2]) or 0 + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Freeze", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) and not target:FAdmin_GetGlobal("FAdmin_frozen") then + target:FAdmin_SetGlobal("FAdmin_frozen", true) + target:Lock() + + if time == 0 then continue end + + timer.Simple(time, function() + if not IsValid(target) or not target:FAdmin_GetGlobal("FAdmin_frozen") then return end + target:FAdmin_SetGlobal("FAdmin_frozen", false) + target:UnLock() + end) + end + end + FAdmin.Messages.FireNotification("freeze", ply, targets, {time}) + + return true, targets, time +end + +local function Unfreeze(ply, cmd, args) + if not args[1] then return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Freeze", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) and target:FAdmin_GetGlobal("FAdmin_frozen") then + target:FAdmin_SetGlobal("FAdmin_frozen", false) + target:UnLock() + end + end + + FAdmin.Messages.FireNotification("unfreeze", ply, targets) + + return true, targets +end + +FAdmin.StartHooks["Freeze"] = function() + FAdmin.Messages.RegisterNotification{ + name = "freeze", + hasTarget = true, + receivers = "involved+admins", + writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end, + message = {"instigator", " froze ", "targets", " ", "extraInfo.1"}, + } + + FAdmin.Messages.RegisterNotification{ + name = "unfreeze", + hasTarget = true, + receivers = "involved+admins", + message = {"instigator", " unfroze ", "targets"}, + } + + FAdmin.Commands.AddCommand("freeze", Freeze) + FAdmin.Commands.AddCommand("unfreeze", Unfreeze) + + FAdmin.Access.AddPrivilege("Freeze", 2) +end + +local disallow = function(ply) if ply:FAdmin_GetGlobal("FAdmin_frozen") then return false end end + +hook.Add("PlayerSpawnObject", "FAdmin_jail", disallow) +hook.Add("CanPlayerSuicide", "FAdmin_jail", disallow) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/cl_controls.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/cl_controls.lua new file mode 100644 index 0000000..4f7917b --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/cl_controls.lua @@ -0,0 +1,164 @@ +-- Controls for the give weapons menu. These are litterally copied and edited from the garry's mod code. +-- Remaking them in case the gamemode is not derived from sandbox +-- Copying from garry's mod code because I'm lazy and because it looks good. + + +-- Weapon icon: +local PANEL = {} + +function PANEL:Init() + self:SetSize(83, 83) + + self.Label = vgui.Create("DLabel", self) + + self:SetKeepAspect(true) + self:SetDrawBorder(true) + self.m_Image:SetPaintedManually(true) +end + + +function PANEL:PerformLayout() + self.Label:SizeToContents() + self.Label:SetFont("TabLarge") + self.Label:SetTextColor(color_white) + self.Label:SetContentAlignment(5) + self.Label:SetWide(self:GetWide()) + self.Label:AlignBottom(2) + + DImageButton.PerformLayout(self) + + if self.imgAdmin then + self.imgAdmin:SizeToContents() + self.imgAdmin:AlignTop(4) + self.imgAdmin:AlignRight(4) + end +end + +function PANEL:CreateAdminIcon() + self.imgAdmin = vgui.Create("DImage", self) + self.imgAdmin:SetImage("icon16/shield.png") -- SilkIcons are now merged into GMOD as materials/icon16 + self.imgAdmin:SetTooltip("#Admin Only") +end + +function PANEL:Paint() + local w, h = self:GetSize() + self.m_Image:Paint() + + surface.SetDrawColor(30, 30, 30, 200) + surface.DrawRect(0, h - 16, w, 16) +end + +function PANEL:Setup(NiceName, SpawnName, IconMaterial, AdminOnly, Parent, IsAmmo) + self.Label:SetText(DarkRP.deLocalise(NiceName)) + + self.DoClick = function() Parent:DoGiveWeapon(SpawnName, IsAmmo) end + self.DoRightClick = function() end + + if not IconMaterial then + IconMaterial = "VGUI/entities/" .. SpawnName + end + + self:SetOnViewMaterial(IconMaterial, "vgui/swepicon") + + if AdminOnly then self:CreateAdminIcon() end + + self:InvalidateLayout() +end + +local WeaponIcon = vgui.RegisterTable(PANEL, "DImageButton") + +-- Full panel: +local PANEL2 = {} + +function PANEL2:Init() + self.PanelList = vgui.Create("DPanelList", self) + self.PanelList:SetPadding(4) + self.PanelList:SetSpacing(2) + self.PanelList:EnableVerticalScrollbar(true) +end + +function PANEL2:BuildList() + self.PanelList:Clear() + + if not self.HideAmmo then + local AmmoCat = vgui.Create("DCollapsibleCategory", self) + self.PanelList:AddItem(AmmoCat) + AmmoCat:SetLabel("Give ammo") + + local AmmoPan = vgui.Create("DPanelList") + AmmoCat:SetContents(AmmoPan) + AmmoPan:EnableHorizontal(true) + AmmoPan:SetPaintBackground(false) + AmmoPan:SetSpacing(2) + AmmoPan:SetPadding(2) + AmmoPan:SetAutoSize(true) + + for k in SortedPairs(FAdmin.AmmoTypes) do + local Icon = vgui.CreateFromTable(WeaponIcon, self) + Icon:Setup(k, k, "spawnicons/models/items/boxmrounds60x60.png", false, self, true) -- Gets created clientside by GMOD when someone is after that model, or trying to buy ammo. + AmmoPan:AddItem(Icon) + end + end + + local Weapons = weapons.GetList() + local Categorised = {} + + Categorised["Half-life 2"] = {} + for k, weapon in pairs(FAdmin.HL2Guns) do + table.insert(Categorised["Half-life 2"], {PrintName = k, ClassName = weapon, Spawnable = true, + Author = "Half-life 2", + Contact = "gaben@valvesoftware.com", + Instructions = "Shoot!"}) + end + + for k, weapon in pairs(Weapons) do + weapon = weapons.Get(weapon.ClassName) + Weapons[k] = weapon + weapon.Category = weapon.Category or "Other" + + if not weapon.Spawnable and not weapon.AdminSpawnable then + Weapons[k] = nil + else + Categorised[weapon.Category] = Categorised[weapon.Category] or {} + table.insert(Categorised[weapon.Category], weapon) + Weapons[k] = nil + end + end + + Weapons = nil + + for CategoryName, v in SortedPairs(Categorised) do + local Category = vgui.Create("DCollapsibleCategory", self) + self.PanelList:AddItem(Category) + Category:SetLabel(CategoryName) + Category:SetCookieName("WeaponSpawn." .. CategoryName) + + local Content = vgui.Create("DPanelList") + Category:SetContents(Content) + Content:EnableHorizontal(true) + Content:SetPaintBackground(false) + Content:SetSpacing(2) + Content:SetPadding(2) + Content:SetAutoSize(true) + + for _, WeaponTable in SortedPairsByMemberValue(v, "PrintName") do + local Icon = vgui.CreateFromTable(WeaponIcon, self) + Icon:Setup(WeaponTable.PrintName or WeaponTable.ClassName, WeaponTable.ClassName, WeaponTable.SpawnMenuIcon, WeaponTable.AdminSpawnable and not WeaponTable.Spawnable, self) + + local Tooltip = Format("Name: %s", WeaponTable.PrintName) + if WeaponTable.Author ~= "" then Tooltip = Format("%s\nAuthor: %s", Tooltip, WeaponTable.Author) end + if WeaponTable.Contact ~= "" then Tooltip = Format("%s\nContact: %s", Tooltip, WeaponTable.Contact) end + if WeaponTable.Instructions ~= "" then Tooltip = Format("%s\n\n%s", Tooltip, WeaponTable.Instructions) end + + Icon:SetTooltip(Tooltip) + Content:AddItem(Icon) + end + end + self.PanelList:InvalidateLayout() +end + +function PANEL2:PerformLayout() + self.PanelList:StretchToParent(0, 0, 0, 0) +end + +derma.DefineControl("FAdmin_weaponPanel", "Weapon panel for giving weapons in FAdmin", PANEL2, "Panel") diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/cl_init.lua new file mode 100644 index 0000000..21bd0e8 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/cl_init.lua @@ -0,0 +1,31 @@ +local function GiveWeaponGui(ply) + local frame = vgui.Create("DFrame") + frame:SetTitle("Give weapon") + frame:SetSize(ScrW() / 2, ScrH() - 50) + frame:Center() + frame:SetVisible(true) + frame:MakePopup() + + local WeaponMenu = vgui.Create("FAdmin_weaponPanel", frame) + WeaponMenu:StretchToParent(0,25,0,0) + + function WeaponMenu:DoGiveWeapon(SpawnName, IsAmmo) + if not ply:IsValid() then return end + local giveWhat = (IsAmmo and "ammo") or "weapon" + + RunConsoleCommand("FAdmin", "give" .. giveWhat, ply:UserID(), SpawnName) + end + + WeaponMenu:BuildList() +end + +FAdmin.StartHooks["GiveWeapons"] = function() + FAdmin.Access.AddPrivilege("giveweapon", 2) + FAdmin.Commands.AddCommand("giveweapon", nil, "", "") + + FAdmin.ScoreBoard.Player:AddActionButton("Give weapon(s)", "fadmin/icons/weapon", Color(255, 130, 0, 255), + + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "giveweapon") end, function(ply, button) + GiveWeaponGui(ply) + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/sh_HL2Weapons.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/sh_HL2Weapons.lua new file mode 100644 index 0000000..84cd8bd --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/sh_HL2Weapons.lua @@ -0,0 +1,57 @@ +FAdmin.HL2Guns = { + Crowbar = "weapon_crowbar", + Gravgun = "weapon_gravgun", + Physgun = "weapon_physgun", + Stunstick = "weapon_stunstick", + Pistol = "weapon_pistol", + ["357"] = "weapon_357", + Smg1 = "weapon_smg1", + Ar2 = "weapon_ar2", + Shotgun = "weapon_shotgun", + Crossbow = "weapon_crossbow", + Grenade = "weapon_frag", + RPG = "weapon_rpg", + ["S.L.A.M."] = "weapon_SLAM", + Camera = "gmod_camera", + Toolgun = "gmod_tool" +} + +FAdmin.AmmoTypes = { + AR2 = 30, + AR2AltFire = 3, + pistol = 180, + smg1 = 45, + ["357"] = 6, + XBowBolt = 3, + Buckshot = 6, + RPG_Round = 3, + SMG1_Grenade = 1, + Grenade = 3, + slam = 1 +} + +FAdmin.StartHooks["GivingWeapons"] = function() + FAdmin.Messages.RegisterNotification{ + name = "giveweapon", + hasTarget = true, + message = {"instigator", " gave ", "extraInfo.1", " to ", "targets"}, + receivers = "everyone", + writeExtraInfo = function(i) net.WriteString(i[1]) end, + readExtraInfo = function() + return {net.ReadString()} + end, + extraInfoColors = {Color(255, 102, 0)} + } + + FAdmin.Messages.RegisterNotification{ + name = "giveammo", + hasTarget = true, + message = {"instigator", " gave ", "extraInfo.1", " ", "extraInfo.2", " ammo to ", "targets"}, + receivers = "everyone", + writeExtraInfo = function(info) net.WriteUInt(info[1], 32) net.WriteString(info[2]) end, + readExtraInfo = function() + return {tostring(net.ReadUInt(32)), net.ReadString()} + end, + extraInfoColors = {Color(255, 102, 0), Color(255, 102, 153)} + } +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/sv_init.lua new file mode 100644 index 0000000..d712c99 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/giveweapons/sv_init.lua @@ -0,0 +1,57 @@ +local function GiveWeapon(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "giveweapon") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if not args[2] then return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + local weapon = weapons.GetStored(args[2]) + if table.HasValue(FAdmin.HL2Guns, args[2]) then weapon = args[2] + elseif weapon and weapon.ClassName then weapon = weapon.ClassName end + + if not weapon then return false end + + for _, target in ipairs(targets) do + if IsValid(target) then + target:Give(weapon) + end + end + FAdmin.Messages.ActionMessage(ply, targets, "You gave %s a " .. weapon, "%s gave you a " .. weapon, "Gave %s a " .. weapon) + FAdmin.Messages.FireNotification("giveweapon", ply, targets, {weapon}) + + return true, targets, weapon +end + +local function GiveAmmo(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "giveweapon") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if not args[2] or not FAdmin.AmmoTypes[args[2]] then return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + local ammo = args[2] + local amount = tonumber(args[3]) or FAdmin.AmmoTypes[args[2]] + + for _, target in ipairs(targets) do + if IsValid(target) then + target:GiveAmmo(amount, ammo) + end + end + + FAdmin.Messages.FireNotification("giveammo", ply, targets, {amount, ammo}) + + return true, targets, ammo, amount +end + +FAdmin.StartHooks["GiveWeapons"] = function() + FAdmin.Commands.AddCommand("giveweapon", GiveWeapon) + FAdmin.Commands.AddCommand("giveammo", GiveAmmo) + + FAdmin.Access.AddPrivilege("giveweapon", 3) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/god/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/god/cl_init.lua new file mode 100644 index 0000000..b438466 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/god/cl_init.lua @@ -0,0 +1,40 @@ +FAdmin.StartHooks["God"] = function() + FAdmin.Messages.RegisterNotification{ + name = "god", + hasTarget = true, + message = {"instigator", " enabled godmode for ", "targets"}, + receivers = "everyone", + } + + FAdmin.Messages.RegisterNotification{ + name = "ungod", + hasTarget = true, + message = {"instigator", " disabled godmode for ", "targets"}, + receivers = "everyone", + } + + FAdmin.Access.AddPrivilege("God", 2) + FAdmin.Commands.AddCommand("god", nil, "") + FAdmin.Commands.AddCommand("ungod", nil, "") + + FAdmin.ScoreBoard.Player:AddActionButton(function(ply) + if ply:FAdmin_GetGlobal("FAdmin_godded") then return "Ungod" end + return "God" + end, function(ply) + if ply:FAdmin_GetGlobal("FAdmin_godded") then return "fadmin/icons/god", "fadmin/icons/disable" end + return "fadmin/icons/god" + end, Color(255, 130, 0, 255), + + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "God") end, function(ply, button) + if not ply:FAdmin_GetGlobal("FAdmin_godded") then + RunConsoleCommand("_FAdmin", "god", ply:UserID()) + else + RunConsoleCommand("_FAdmin", "ungod", ply:UserID()) + end + + if not ply:FAdmin_GetGlobal("FAdmin_godded") then button:SetImage2("fadmin/icons/disable") button:SetText("Ungod") button:GetParent():InvalidateLayout() return end + button:SetImage2("null") + button:SetText("God") + button:GetParent():InvalidateLayout() + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/god/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/god/sv_init.lua new file mode 100644 index 0000000..6c10c2a --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/god/sv_init.lua @@ -0,0 +1,66 @@ +local function God(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "God") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + for _, target in ipairs(targets) do + if IsValid(target) and not target:FAdmin_GetGlobal("FAdmin_godded") then + target:FAdmin_SetGlobal("FAdmin_godded", true) + target:GodEnable() + end + end + FAdmin.Messages.FireNotification("god", ply, targets) + + return true, targets +end + +local function Ungod(ply, cmd, args) + if not FAdmin.Access.PlayerHasPrivilege(ply, "God") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + for _, target in ipairs(targets) do + if IsValid(target) and target:FAdmin_GetGlobal("FAdmin_godded") then + target:FAdmin_SetGlobal("FAdmin_godded", false) + target:GodDisable() + end + end + FAdmin.Messages.FireNotification("ungod", ply, targets) + + return true, targets +end + +FAdmin.StartHooks["God"] = function() + FAdmin.Messages.RegisterNotification{ + name = "god", + hasTarget = true, + message = {"instigator", " enabled godmode for ", "targets"}, + receivers = "everyone", + } + + FAdmin.Messages.RegisterNotification{ + name = "ungod", + hasTarget = true, + message = {"instigator", " disabled godmode for ", "targets"}, + receivers = "everyone", + } + + FAdmin.Commands.AddCommand("God", God) + FAdmin.Commands.AddCommand("Ungod", Ungod) + + FAdmin.Access.AddPrivilege("God", 2) +end + +hook.Add("PlayerSpawn", "FAdmin_God", function(ply) + if ply:FAdmin_GetGlobal("FAdmin_godded") then + ply:GodEnable() + end +end) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/health/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/health/cl_init.lua new file mode 100644 index 0000000..10acf70 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/health/cl_init.lua @@ -0,0 +1,31 @@ +FAdmin.StartHooks["Health"] = function() + FAdmin.Messages.RegisterNotification{ + name = "sethealth", + hasTarget = true, + message = {"instigator", " set the health of ", "targets", " to ", "extraInfo.1"}, + readExtraInfo = function() return {tostring(net.ReadUInt(16))} end + } + + FAdmin.Access.AddPrivilege("SetHealth", 2) + FAdmin.Commands.AddCommand("hp", nil, "", "") + FAdmin.Commands.AddCommand("SetHealth", nil, "[Player]", "") + + FAdmin.ScoreBoard.Player:AddActionButton("Set health", "icon16/heart.png", Color(255, 130, 0, 255), + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SetHealth", ply) end, + function(ply, button) + --Do nothing when the button has been clicked + end, + function(ply, button) -- Create the Wang when the mouse is pressed + button.OnMousePressed = function() + local window = Derma_StringRequest("Select health", "What do you want the health of the person to be?", "", + function(text) + local health = DarkRP.toInt(text or 100) or 100 + RunConsoleCommand("_fadmin", "SetHealth", ply:UserID(), health) + end + ) + + -- The user is usually holding tab when clicking health, so fix the focus + window:RequestFocus() + end + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/health/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/health/sv_init.lua new file mode 100644 index 0000000..980816e --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/health/sv_init.lua @@ -0,0 +1,39 @@ +local function SetHealth(ply, cmd, args) + if not args[1] then return false end + + local Health = tonumber(args[2] or 100) + if not Health then return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + targets = {ply} + Health = math.floor(tonumber(args[1] or 100) or 100) + return false + end + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "SetHealth", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) then + target:SetHealth(Health) + end + end + + FAdmin.Messages.FireNotification("sethealth", ply, targets, {Health}) + + return true, targets, Health +end + +FAdmin.StartHooks["Health"] = function() + FAdmin.Messages.RegisterNotification{ + name = "sethealth", + hasTarget = true, + receivers = "everyone", + writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end, + message = {"instigator", " set the health of ", "targets", " to ", "extraInfo.1"}, + } + + FAdmin.Commands.AddCommand("SetHealth", SetHealth) + FAdmin.Commands.AddCommand("hp", SetHealth) + + FAdmin.Access.AddPrivilege("SetHealth", 2) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/ignite/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/ignite/cl_init.lua new file mode 100644 index 0000000..7715cb6 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/ignite/cl_init.lua @@ -0,0 +1,39 @@ +FAdmin.StartHooks["Ignite"] = function() + FAdmin.Messages.RegisterNotification{ + name = "ignite", + hasTarget = true, + message = {"instigator", " ignited ", "targets", " ", "extraInfo.1"}, + readExtraInfo = function() + local time = net.ReadUInt(16) + return {time == 0 and FAdmin.PlayerActions.commonTimes[time] or string.format("for %s", FAdmin.PlayerActions.commonTimes[time] or (time .. " seconds"))} + end + } + + FAdmin.Messages.RegisterNotification{ + name = "unignite", + hasTarget = true, + message = {"instigator", " unignited ", "targets"}, + } + + FAdmin.Access.AddPrivilege("Ignite", 2) + FAdmin.Commands.AddCommand("Ignite", nil, "", "[time]") + FAdmin.Commands.AddCommand("unignite", nil, "") + + FAdmin.ScoreBoard.Player:AddActionButton(function(ply) return (ply:FAdmin_GetGlobal("FAdmin_ignited") and "Extinguish") or "Ignite" end, + function(ply) local disabled = (ply:FAdmin_GetGlobal("FAdmin_ignited") and "fadmin/icons/disable") or nil return "fadmin/icons/ignite", disabled end, + Color(255, 130, 0, 255), + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Ignite", ply) end, + function(ply, button) + if not ply:FAdmin_GetGlobal("FAdmin_ignited") then + RunConsoleCommand("_FAdmin", "ignite", ply:UserID()) + button:SetImage2("fadmin/icons/disable") + button:SetText("Extinguish") + button:GetParent():InvalidateLayout() + else + RunConsoleCommand("_FAdmin", "unignite", ply:UserID()) + button:SetImage2("null") + button:SetText("Ignite") + button:GetParent():InvalidateLayout() + end + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/ignite/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/ignite/sv_init.lua new file mode 100644 index 0000000..0ab1375 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/ignite/sv_init.lua @@ -0,0 +1,67 @@ +local function Ignite(ply, cmd, args) + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + local time = tonumber(args[2]) or 10 + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Ignite", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) then + target:Ignite(time, 0) + target:FAdmin_SetGlobal("FAdmin_ignited", true) + + timer.Simple(time, function() + if IsValid(target) then target:FAdmin_SetGlobal("FAdmin_ignited", false) end + end) + end + end + FAdmin.Messages.FireNotification("ignite", ply, targets, {time}) + + return true, targets +end + +local function UnIgnite(ply, cmd, args) + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + for _, target in ipairs(targets) do + if not FAdmin.Access.PlayerHasPrivilege(ply, "Ignite") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + if IsValid(target) then + target:Extinguish() + + target:FAdmin_SetGlobal("FAdmin_ignited", false) + end + end + + FAdmin.Messages.FireNotification("unignite", ply, targets) + return true, targets +end + + +FAdmin.StartHooks["Ignite"] = function() + FAdmin.Messages.RegisterNotification{ + name = "ignite", + hasTarget = true, + receivers = "involved+admins", + writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end, + message = {"instigator", " ignited ", "targets", " ", "extraInfo.1"}, + } + + FAdmin.Messages.RegisterNotification{ + name = "unignite", + hasTarget = true, + message = {"instigator", " unignited ", "targets"}, + receivers = "involved+admins", + } + + FAdmin.Commands.AddCommand("Ignite", Ignite) + FAdmin.Commands.AddCommand("Unignite", UnIgnite) + + FAdmin.Access.AddPrivilege("Ignite", 2) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/cl_init.lua new file mode 100644 index 0000000..7639ca8 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/cl_init.lua @@ -0,0 +1,61 @@ +FAdmin.StartHooks["Jail"] = function() + FAdmin.Access.AddPrivilege("Jail", 2) + FAdmin.Commands.AddCommand("Jail", nil, "", "[Small/Normal/Big]", "[Time]") + FAdmin.Commands.AddCommand("UnJail", nil, "") + + FAdmin.ScoreBoard.Main.AddPlayerRightClick("Jail/Unjail", function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then + RunConsoleCommand("_FAdmin", "unjail", ply:UserID()) + else + RunConsoleCommand("_FAdmin", "jail", ply:UserID()) + end + end) + + FAdmin.ScoreBoard.Player:AddActionButton(function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then return "Unjail" end + return "Jail" + end, + function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then return "fadmin/icons/jail", "fadmin/icons/disable" end + return "fadmin/icons/jail" + end, + Color(255, 130, 0, 255), + function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Jail", ply) end, + function(ply, button) + if ply:FAdmin_GetGlobal("fadmin_jailed") then RunConsoleCommand("_FAdmin", "unjail", ply:UserID()) button:SetImage2("null") button:SetText("Jail") button:GetParent():InvalidateLayout() return end + + local menu = DermaMenu() + + local Padding = vgui.Create("DPanel") + Padding:SetPaintBackgroundEnabled(false) + Padding:SetSize(1,5) + menu:AddPanel(Padding) + + local Title = vgui.Create("DLabel") + Title:SetText(" Jail Type:\n") + Title:SetFont("UiBold") + Title:SizeToContents() + Title:SetTextColor(color_black) + + menu:AddPanel(Title) + + for k, v in ipairs(FAdmin.PlayerActions.JailTypes) do + if v == "Unjail" then continue end + FAdmin.PlayerActions.addTimeSubmenu(menu, v .. " jail", + function() + RunConsoleCommand("_FAdmin", "Jail", ply:UserID(), k) + button:SetText("Unjail") button:GetParent():InvalidateLayout() + button:SetImage2("fadmin/icons/disable") + end, + function(secs) + RunConsoleCommand("_FAdmin", "Jail", ply:UserID(), k, secs) + button:SetText("Unjail") + button:GetParent():InvalidateLayout() + button:SetImage2("fadmin/icons/disable") + end + ) + end + + menu:Open() + end) +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/sh_shared.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/sh_shared.lua new file mode 100644 index 0000000..c0035cd --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/sh_shared.lua @@ -0,0 +1,38 @@ +FAdmin.PlayerActions.JailTypes = {} +FAdmin.PlayerActions.JailTypes[1] = "Small" +FAdmin.PlayerActions.JailTypes[2] = "Normal" +FAdmin.PlayerActions.JailTypes[3] = "Big" +FAdmin.PlayerActions.JailTypes[4] = "Unjail" + +hook.Add("CanTool", "FAdmin_jailed", function(ply) -- shared so it doesn't look like you can use tool + if ply:FAdmin_GetGlobal("fadmin_jailed") then + return false + end +end) + +hook.Add("PlayerNoClip", "FAdmin_jail", function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then + return false + end +end) + +FAdmin.StartHooks["Jailing"] = function() + FAdmin.Messages.RegisterNotification{ + name = "jail", + hasTarget = true, + message = {"instigator", " jailed ", "targets", " ", "extraInfo.1"}, + receivers = "involved+admins", + writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end, + readExtraInfo = function() + local time = net.ReadUInt(16) + return {time == 0 and FAdmin.PlayerActions.commonTimes[time] or string.format("for %s", FAdmin.PlayerActions.commonTimes[time] or (time .. " seconds"))} + end + } + + FAdmin.Messages.RegisterNotification{ + name = "unjail", + hasTarget = true, + message = {"instigator", " unjailed ", "targets"}, + receivers = "involved+admins", + } +end diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/sv_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/sv_init.lua new file mode 100644 index 0000000..d86156b --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/jail/sv_init.lua @@ -0,0 +1,167 @@ +local function createJailTimer(target, jailTime) + if jailTime == 0 then return end + + timer.Create("FAdmin_jail" .. target:UserID(), jailTime, 1, function() + if not IsValid(target) then return end + if not target:FAdmin_GetGlobal("fadmin_jailed") then return end + timer.Remove("FAdmin_jail_watch" .. target:UserID()) + target:FAdmin_SetGlobal("fadmin_jailed", false) + + for k in pairs(target.FAdminJailProps) do + if not IsValid(k) then continue end + k:SetCanRemove(true) + k:Remove() + end + + target.FAdminJailProps = nil + end) +end + +local function Jail(ply, cmd, args) + if not args[1] then return false end + + local targets = FAdmin.FindPlayer(args[1]) + if not targets or #targets == 1 and not IsValid(targets[1]) then + FAdmin.Messages.SendMessage(ply, 1, "Player not found") + return false + end + + local JailType = FAdmin.PlayerActions.JailTypes[tonumber(args[2])] or args[2] or FAdmin.PlayerActions.JailTypes[2] + JailType = string.lower(JailType) + local JailTime = tonumber(args[3]) or 0 + local time = "" + + for _, target in ipairs(targets) do + if not IsValid(target) then continue end + if not FAdmin.Access.PlayerHasPrivilege(ply, "Jail", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end + + local jailDistance + + ply:ExitVehicle() + + local JailProps = {} + if JailType == "unjail" or string.lower(cmd) == "unjail" then + if target.FAdminJailProps then + for k in pairs(target.FAdminJailProps) do + if not IsValid(k) then continue end + k:SetCanRemove(true) + k:Remove() + end + end + + target.FAdminJailProps = nil + timer.Remove("FAdmin_jail" .. target:UserID()) + timer.Remove("FAdmin_jail_watch" .. target:UserID()) + target:FAdmin_SetGlobal("fadmin_jailed", false) + elseif JailType == "small" then + jailDistance = 50 + table.insert(JailProps, {pos = Vector(0,0,58), ang = Angle(0,0,0), model = "models/props_wasteland/laundry_dryer001.mdl"}) + elseif JailType == "normal" then + jailDistance = 70 + table.insert(JailProps, {pos = Vector(0,0,-5), ang = Angle(90,0,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + table.insert(JailProps, {pos = Vector(0,0,97), ang = Angle(90,0,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + + table.insert(JailProps, {pos = Vector(21,31,46), ang = Angle(0,90,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + table.insert(JailProps, {pos = Vector(21,-31,46), ang = Angle(0,90,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + table.insert(JailProps, {pos = Vector(-21,31,46), ang = Angle(0,90,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + table.insert(JailProps, {pos = Vector(-21,-31,46), ang = Angle(0,90,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + + table.insert(JailProps, {pos = Vector(-52,0,46), ang = Angle(0,0,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + table.insert(JailProps, {pos = Vector(52,0,46), ang = Angle(0,0,0), model = "models/props_building_details/Storefront_Template001a_Bars.mdl"}) + + elseif JailType == "big" then -- Requires CSS but it's really funny + jailDistance = 80 + table.insert(JailProps, {pos = Vector(0,0,-5), ang = Angle(0,0,0), model = "models/props/cs_havana/gazebo.mdl"}) + table.insert(JailProps, {pos = Vector(70,0,50), ang = Angle(0,0,0), model = "models/props_borealis/borealis_door001a.mdl"}) + + table.insert(JailProps, {pos = Vector(-70,0,100), ang = Angle(0,0,90), model = "models/props_lab/lockerdoorleft.mdl"}) + table.insert(JailProps, {pos = Vector(-35,-55,100), ang = Angle(0,60,90), model = "models/props_lab/lockerdoorleft.mdl"}) + table.insert(JailProps, {pos = Vector(-35,55,100), ang = Angle(0,300,90), model = "models/props_lab/lockerdoorleft.mdl"}) + + table.insert(JailProps, {pos = Vector(35,-55,100), ang = Angle(0,300,90), model = "models/props_lab/lockerdoorleft.mdl"}) + table.insert(JailProps, {pos = Vector(35,55,100), ang = Angle(0,240,90), model = "models/props_lab/lockerdoorleft.mdl"}) + else + FAdmin.Messages.SendMessage(ply, 5, "Bad arguments") + return + end + if not target:FAdmin_GetGlobal("fadmin_jailed") and JailType ~= "unjail" and string.lower(cmd) ~= "unjail" then + target:SetMoveType(MOVETYPE_WALK) + + target:FAdmin_SetGlobal("fadmin_jailed", true) + target.FAdminJailPos = target:GetPos() + target.FAdminJailProps = {} + + for _, v in ipairs(JailProps) do + local JailProp = ents.Create("fadmin_jail") + JailProp:SetPos(target.FAdminJailPos + v.pos) + JailProp:SetAngles(v.ang) + JailProp:SetModel(v.model) + JailProp:Spawn() + JailProp:Activate() + + JailProp.target = target + JailProp.targetPos = target.FAdminJailPos + target.FAdminJailProps[JailProp] = true + end + + createJailTimer(target, JailTime) + + jailDistance = jailDistance * jailDistance + local userid = target:UserID() + timer.Create("FAdmin_jail_watch" .. target:UserID(), 1, 0, function() + if not IsValid(target) then + timer.Remove("FAdmin_jail_watch" .. userid) + + return + end + + if target:GetPos():DistToSqr(target.FAdminJailPos) > jailDistance then + target:SetPos(target.FAdminJailPos) + end + end) + + time = "for " .. JailTime .. " seconds" + if JailTime == 0 then time = "indefinitely" end + end + end + + if JailType == "unjail" or string.lower(cmd) == "unjail" then + FAdmin.Messages.FireNotification("unjail", ply, targets) + else + FAdmin.Messages.FireNotification("jail", ply, targets, {JailTime}) + end + + return true, targets, JailType, time +end + +FAdmin.StartHooks["Jail"] = function() + FAdmin.Commands.AddCommand("Jail", Jail) + FAdmin.Commands.AddCommand("UnJail", Jail) + + FAdmin.Access.AddPrivilege("Jail", 2) +end + +hook.Add("PlayerSpawn", "FAdmin_jail", function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then + timer.Simple(0.1, function() if IsValid(ply) then ply:SetPos(ply.FAdminJailPos) end end) + end +end) + +hook.Add("PlayerSpawnObject", "FAdmin_jailed", function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then + return false + end +end) + +hook.Add("CanPlayerEnterVehicle", "FAdmin_jailed", function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then + return false + end +end) + +--Kill stupid addons that does not call CanPlayerEnterVehicle (like Sit Anywhere script) +hook.Add("PlayerEnteredVehicle", "FAdmin_jailed", function(ply) + if ply:FAdmin_GetGlobal("fadmin_jailed") then + ply:ExitVehicle() + end +end) diff --git a/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/kickban/cl_init.lua b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/kickban/cl_init.lua new file mode 100644 index 0000000..7eaf903 --- /dev/null +++ b/gamemodes/darkrp/gamemode/modules/fadmin/fadmin/playeractions/kickban/cl_init.lua @@ -0,0 +1,377 @@ +--[[ +WHEN GETTING KICKED +]] +local KickText = "" +usermessage.Hook("FAdmin_kick_start", function() + hook.Add("HUDPaint", "FAdmin_kick", function() + draw.RoundedBox(0,0,0, ScrW(), ScrH(), Color(0,0,0,255)) + draw.DrawNonParsedText("You are getting kicked\nReason: " .. KickText .. "\nLeaving voluntarily is also an option.", "HUDNumber5", ScrW() / 2, ScrH() / 2, Color(255, 0, 0, 255), TEXT_ALIGN_CENTER) + end) +end) + +usermessage.Hook("FAdmin_kick_cancel", function() + hook.Remove("HUDPaint", "FAdmin_kick") + FAdmin.Messages.AddMessage(4, "Kick was canceled by admin") +end) + +usermessage.Hook("FAdmin_kick_update", function(um) + KickText = um:ReadString() +end) + +local BanText = "No reason" +local BanTimeText = "permanent" + +usermessage.Hook("FAdmin_ban_start", function() + hook.Add("HUDPaint", "FAdmin_ban", function() + draw.RoundedBox(0,0,0, ScrW(), ScrH(), Color(0,0,0,255)) + draw.DrawNonParsedText("You are getting banned\nReason: " .. BanText .. "\nTime: " .. " " .. BanTimeText .. "\nLeaving voluntarily or rejoining will not prevent banning.", "HUDNumber5", ScrW() / 2, ScrH() / 2, Color(0, 0, 255, 255), TEXT_ALIGN_CENTER) + end) +end) + +usermessage.Hook("FAdmin_ban_cancel", function() + hook.Remove("HUDPaint", "FAdmin_ban") + FAdmin.Messages.AddMessage(4, "Ban was canceled by admin") +end) + +usermessage.Hook("FAdmin_ban_update", function(um) + BanTimeText = FAdmin.PlayerActions.ConvertBanTime(um:ReadLong()) + BanText = um:ReadString() +end) + +--[[--------------------------------------------------------------------------- +Show the window for banning +---------------------------------------------------------------------------]] +local function showBanWindow(SteamID, NICK, time, reason) + local BanTime = time or 60 + NICK = NICK or "" + reason = reason or "No reason" + local M,H,D,W,Y = BanTime % 60, + math.floor(BanTime / 60) % 24, + math.floor(BanTime / 1440) % 7, + math.floor(BanTime / 10080) % 53, + math.floor(BanTime / 525948) + + RunConsoleCommand("_FAdmin", "ban", SteamID, "start") + + local Window = vgui.Create("DFrame") + Window:SetTitle("Ban Details") + Window:SetDraggable(false) + Window:ShowCloseButton(false) + Window:SetBackgroundBlur(true) + Window:SetDrawOnTop(true) + + local InnerPanel = vgui.Create("DPanel", Window) + InnerPanel:SetPaintBackground(false) + + local Text = vgui.Create("DLabel", InnerPanel) + Text:SetText("Ban " .. NICK .. "") + Text:SizeToContents() + Text:SetContentAlignment(5) + + + local TimePanel = vgui.Create("DPanel", Window) + TimePanel:SetPaintBackground(false) + + local TextEntry = vgui.Create("DTextEntry", TimePanel) + function TextEntry:OnTextChanged() + RunConsoleCommand("_FAdmin", "ban", SteamID, "update", BanTime, self:GetValue()) + end + TextEntry:SetText(DarkRP.deLocalise(reason)) + TextEntry.OnEnter = function() Window:Close() RunConsoleCommand("_FAdmin", "ban", SteamID, "execute", BanTime, TextEntry:GetValue()) end + function TextEntry:OnFocusChanged(changed) + self:RequestFocus() + self:SelectAllText(true) + end + + local Minutes = vgui.Create("DNumberWang", TimePanel) + Minutes:SetMinMax(0, 59) + Minutes:SetDecimals(0) + Minutes:SetValue(M) + + local Hours = vgui.Create("DNumberWang", TimePanel) + Hours:SetMinMax(0, 23) + Hours:SetValue(H) + Hours:SetDecimals(0) + + local Days = vgui.Create("DNumberWang", TimePanel) + Days:SetMinMax(0, 6) + Days:SetValue(D) + Days:SetDecimals(0) + + local Weeks = vgui.Create("DNumberWang", TimePanel) + Weeks:SetMinMax(0, 53) + Weeks:SetValue(W) + Weeks:SetDecimals(0) + + local Years = vgui.Create("DNumberWang", TimePanel) + Years:SetMinMax(0, 3) + Years:SetValue(Y) + Years:SetDecimals(0) + + local MinLabel, HourLabel, DayLabel, WeekLabel, YearLabel = vgui.Create("DLabel", TimePanel), vgui.Create("DLabel", TimePanel), + vgui.Create("DLabel", TimePanel), vgui.Create("DLabel", TimePanel), vgui.Create("DLabel", TimePanel) + MinLabel:SetText("Minutes:") + HourLabel:SetText("Hours:") + DayLabel:SetText("Days:") + WeekLabel:SetText("Weeks:") + YearLabel:SetText("Years:") + + + MinLabel:SetPos(370, 0) + HourLabel:SetPos(280, 0) + DayLabel:SetPos(190, 0) + WeekLabel:SetPos(100, 0) + YearLabel:SetPos(10, 0) + + local function update() + BanTime = M + H * 60 + D * 1440 + W * 10080 + Y * 525948 + RunConsoleCommand("_FAdmin", "ban", SteamID, "update", BanTime, TextEntry:GetValue()) + end + + function Minutes:OnValueChanged(val) if val == M then return end M = val update() end + function Hours:OnValueChanged(val) if val == H then return end H = val update() end + function Days:OnValueChanged(val) if val == D then return end D = val update() end + function Weeks:OnValueChanged(val) if val == W then return end W = val update() end + function Years:OnValueChanged(val) if val == Y then return end Y = val update() end + + local ButtonPanel = vgui.Create("DPanel", Window) + ButtonPanel:SetTall(25) + ButtonPanel:SetPaintBackground(false) + + local Button = vgui.Create("DButton", ButtonPanel) + Button:SetText("OK") + Button:SizeToContents() + Button:SetTall(20) + Button:SetWide(Button:GetWide() + 20) + Button:SetPos(5, 3) + Button.DoClick = function() + Window:Close() + M, H, D, W, Y = Minutes:GetValue(), Hours:GetValue(), Days:GetValue(), Weeks:GetValue(), Years:GetValue() + update() + RunConsoleCommand("_FAdmin", "ban", SteamID, BanTime, (TextEntry and TextEntry:GetValue()) or "") + end + + local ButtonCancel = vgui.Create("DButton", ButtonPanel) + ButtonCancel:SetText("Cancel") + ButtonCancel:SizeToContents() + ButtonCancel:SetTall(20) + ButtonCancel:SetWide(Button:GetWide() + 20) + ButtonCancel:SetPos(5, 3) + ButtonCancel.DoClick = function() Window:Close() RunConsoleCommand("_FAdmin", "ban", SteamID, "cancel") end + ButtonCancel:MoveRightOf(Button, 5) + + ButtonPanel:SetWide(Button:GetWide() + 5 + ButtonCancel:GetWide() + 10) + + Window:SetSize(450, 111 + 75 + 20) + Window:Center() + + InnerPanel:StretchToParent(5, 25, 5, 125) + TimePanel:StretchToParent(5, 83, 5, 37) + + Minutes:SetPos(370, 20) + Hours:SetPos(280, 20) + Days:SetPos(190, 20) + Weeks:SetPos(100, 20) + Years:SetPos(10, 20) + + Text:StretchToParent(5, 5, 5, nil) + + TextEntry:StretchToParent(5, nil, 5, nil) + TextEntry:AlignBottom(5) + + TextEntry:RequestFocus() + + ButtonPanel:CenterHorizontal() + ButtonPanel:AlignBottom(7) + + Window:MakePopup() + Window:DoModal() +end + +--[[ +ADD BUTTONS ETC. TO MENU +]] +FAdmin.StartHooks["CL_KickBan"] = function() + FAdmin.Access.AddPrivilege("Kick", 2) + FAdmin.Access.AddPrivilege("Ban", 2) + FAdmin.Access.AddPrivilege("UnBan", 2) + + FAdmin.Commands.AddCommand("kick", nil, "", "[Reason]") + FAdmin.Commands.AddCommand("ban", nil, "", "